Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ Signed in, `/ui` shows every component in `src/FluentCMS.Admin/Components/Ui` wi

A shadcn example translates into Razor with the same class names. `ClassNames.Cn` is shadcn's `cn()`: it joins the classes and merges them as tailwind-merge does, so a later class replaces an earlier one that sets the same property and a component's `Class` parameter overrides its own classes. `Variants` is shadcn's `cva()`, with the base classes, the classes for each value of each variant, and the defaults; `ButtonVariants` and `Badge` use it.

Icons are Lucide's, one component each in `src/FluentCMS.Admin/Components/Icons`, named as in lucide-react (`XIcon`, `ChevronDownIcon`), hidden from assistive technology and sized by the classes around them. To add one, put its Lucide name in `src/FluentCMS.Admin/Icons/icons.txt`, run `dotnet build src/FluentCMS.Admin -t:GenerateIcons` and commit the generated component; a normal build does not download or generate anything. `Icons.targets` pins the Lucide release and the SHA-256 of its archive and of its licence. Lucide is ISC licensed, and the icons it took from Feather are MIT; both notices are in `Icons/LICENSE`.

`src/FluentCMS.Admin/wwwroot/js/admin.js` is the admin's only JavaScript, and no component calls into it: it listens on the document and reads the page when an event arrives, so behaviour keeps working while the circuit is down and after a reconnect. A keyboard shortcut is declared on the element it triggers with `aria-keyshortcuts`, for example `aria-keyshortcuts="Control+K Meta+K"`, which assistive technology announces as well. The module clicks a button or link and focuses anything else. A shortcut without Control, Alt or Meta waits while someone types in a field, and while a modal dialog is open only the shortcuts inside it apply.

People can also sign in through one OpenID Connect provider (Keycloak, Authentik, Microsoft Entra, Google and similar). Set `Authentication__OpenIdConnect__Authority`, `Authentication__OpenIdConnect__ClientId` and `Authentication__OpenIdConnect__ClientSecret`, optionally `Authentication__OpenIdConnect__DisplayName` for the button, and register `https://<host>/signin-oidc` as the redirect URI at the provider. Without an authority and client id the option does not appear. Users are not created from the provider: the first sign-in links the provider's account to the user with the same email, and only if the provider marks the email as verified. Later sign-ins match on the provider's issuer and subject. A provider that never sends `email_verified`, such as Microsoft Entra, needs `Authentication__OpenIdConnect__AssumeVerifiedEmail=true`; set it only if that provider issues verified addresses alone.
Expand Down
7 changes: 7 additions & 0 deletions src/FluentCMS.Admin/Components/Icons/CheckIcon.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@* Lucide "check" 1.59.0, generated by Icons.targets from Icons/icons.txt; do not edit. ISC licence, see Icons/LICENSE. *@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="@ClassNames.Cn("lucide lucide-check", Class)" @attributes="AdditionalAttributes"><path d="M20 6 9 17l-5-5" /></svg>

@code {
[Parameter] public string? Class { get; set; }
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }
}
7 changes: 7 additions & 0 deletions src/FluentCMS.Admin/Components/Icons/ChevronDownIcon.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@* Lucide "chevron-down" 1.59.0, generated by Icons.targets from Icons/icons.txt; do not edit. ISC licence, see Icons/LICENSE. *@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="@ClassNames.Cn("lucide lucide-chevron-down", Class)" @attributes="AdditionalAttributes"><path d="m6 9 6 6 6-6" /></svg>

@code {
[Parameter] public string? Class { get; set; }
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }
}
7 changes: 7 additions & 0 deletions src/FluentCMS.Admin/Components/Icons/CircleIcon.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@* Lucide "circle" 1.59.0, generated by Icons.targets from Icons/icons.txt; do not edit. ISC licence, see Icons/LICENSE. *@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="@ClassNames.Cn("lucide lucide-circle", Class)" @attributes="AdditionalAttributes"><circle cx="12" cy="12" r="10" /></svg>

@code {
[Parameter] public string? Class { get; set; }
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }
}
7 changes: 7 additions & 0 deletions src/FluentCMS.Admin/Components/Icons/MoonIcon.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@* Lucide "moon" 1.59.0, generated by Icons.targets from Icons/icons.txt; do not edit. ISC licence, see Icons/LICENSE. *@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="@ClassNames.Cn("lucide lucide-moon", Class)" @attributes="AdditionalAttributes"><path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401" /></svg>

@code {
[Parameter] public string? Class { get; set; }
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }
}
7 changes: 7 additions & 0 deletions src/FluentCMS.Admin/Components/Icons/PlusIcon.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@* Lucide "plus" 1.59.0, generated by Icons.targets from Icons/icons.txt; do not edit. ISC licence, see Icons/LICENSE. *@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="@ClassNames.Cn("lucide lucide-plus", Class)" @attributes="AdditionalAttributes"><path d="M5 12h14" /><path d="M12 5v14" /></svg>

@code {
[Parameter] public string? Class { get; set; }
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }
}
7 changes: 7 additions & 0 deletions src/FluentCMS.Admin/Components/Icons/SearchIcon.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@* Lucide "search" 1.59.0, generated by Icons.targets from Icons/icons.txt; do not edit. ISC licence, see Icons/LICENSE. *@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="@ClassNames.Cn("lucide lucide-search", Class)" @attributes="AdditionalAttributes"><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>

@code {
[Parameter] public string? Class { get; set; }
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }
}
7 changes: 7 additions & 0 deletions src/FluentCMS.Admin/Components/Icons/SunIcon.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@* Lucide "sun" 1.59.0, generated by Icons.targets from Icons/icons.txt; do not edit. ISC licence, see Icons/LICENSE. *@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="@ClassNames.Cn("lucide lucide-sun", Class)" @attributes="AdditionalAttributes"><circle cx="12" cy="12" r="4" /><path d="M12 2v2" /><path d="M12 20v2" /><path d="m4.93 4.93 1.41 1.41" /><path d="m17.66 17.66 1.41 1.41" /><path d="M2 12h2" /><path d="M20 12h2" /><path d="m6.34 17.66-1.41 1.41" /><path d="m19.07 4.93-1.41 1.41" /></svg>

@code {
[Parameter] public string? Class { get; set; }
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }
}
7 changes: 7 additions & 0 deletions src/FluentCMS.Admin/Components/Icons/XIcon.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@* Lucide "x" 1.59.0, generated by Icons.targets from Icons/icons.txt; do not edit. ISC licence, see Icons/LICENSE. *@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="@ClassNames.Cn("lucide lucide-x", Class)" @attributes="AdditionalAttributes"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>

@code {
[Parameter] public string? Class { get; set; }
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }
}
4 changes: 2 additions & 2 deletions src/FluentCMS.Admin/Components/Layout/ThemeToggle.razor
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
on static pages and while the circuit is down; "system" clears the choice. *@
<DropdownMenu>
<DropdownMenuTrigger Size="icon" Class="@Class" data-testid="theme-toggle">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" aria-hidden="true"><circle cx="12" cy="12" r="4" /><path d="M12 2v2" /><path d="M12 20v2" /><path d="m4.93 4.93 1.41 1.41" /><path d="m17.66 17.66 1.41 1.41" /><path d="M2 12h2" /><path d="M20 12h2" /><path d="m6.34 17.66-1.41 1.41" /><path d="m19.07 4.93-1.41 1.41" /></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="absolute size-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" aria-hidden="true"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" /></svg>
<SunIcon Class="size-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
<MoonIcon Class="absolute size-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
<span class="sr-only">@L["Toggle theme"]</span>
</DropdownMenuTrigger>
<DropdownMenuContent Class="[position-area:block-end_span-inline-start]">
Expand Down
24 changes: 23 additions & 1 deletion src/FluentCMS.Admin/Components/Pages/Showcase.razor
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,21 @@
@Hints(([L["Tab"]], L["Passes over it; a badge labels, it takes no focus."]))
</section>

<section class="grid gap-4" aria-labelledby="icons-heading">
<h2 id="icons-heading" class="text-lg font-semibold">@L["Icons"]</h2>
<p class="text-sm text-muted-foreground">@L["Lucide icons, generated from the pinned release. A new one is a name in Icons/icons.txt and a run of the GenerateIcons target."]</p>
<ul class="flex flex-wrap gap-3" data-testid="icons">
@foreach (var icon in Icons)
{
<li class="flex w-28 flex-col items-center gap-2 rounded-md border p-3 text-xs text-muted-foreground">
<DynamicComponent Type="icon" Parameters="IconParameters" />
<code dir="ltr">@icon.Name</code>
</li>
}
</ul>
@Hints(([L["Tab"]], L["Passes over them; an icon is hidden from assistive technology, so the control around it carries the name."]))
</section>

<section class="grid gap-4" aria-labelledby="kbd-heading">
<h2 id="kbd-heading" class="text-lg font-semibold">@L["Kbd"]</h2>
<div class="flex flex-wrap items-center gap-4 text-sm">
Expand Down Expand Up @@ -310,7 +325,7 @@
</Tooltip>
<Tooltip>
<TooltipTrigger Variant="ghost" Size="icon" OnClick='() => Record(L["Save"])'>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14" /><path d="M12 5v14" /></svg>
<PlusIcon />
<span class="sr-only">@L["Save"]</span>
</TooltipTrigger>
<TooltipContent><span class="flex items-center gap-2">@L["Save"] <Kbd>⌘S</Kbd></span></TooltipContent>
Expand Down Expand Up @@ -442,6 +457,13 @@
</div>

@code {
private static readonly Type[] Icons = typeof(XIcon).Assembly.GetTypes()
.Where(type => type.Namespace == typeof(XIcon).Namespace && typeof(IComponent).IsAssignableFrom(type))
.OrderBy(type => type.Name, StringComparer.Ordinal)
.ToArray();

private static readonly Dictionary<string, object> IconParameters = new() { ["Class"] = "size-5 text-foreground" };

private string? _lastAction;
private ChangeSetDiff? _largeDiff;

Expand Down
5 changes: 1 addition & 4 deletions src/FluentCMS.Admin/Components/Ui/Checkbox.razor
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@
aria-invalid="@(Field?.HasError == true ? "true" : null)"
class="peer size-4 shrink-0 appearance-none rounded-[4px] border border-input shadow-xs transition-shadow outline-none checked:border-primary checked:bg-primary focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:bg-input/30 dark:checked:bg-primary dark:aria-invalid:ring-destructive/40"
@attributes="AdditionalAttributes" />
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"
aria-hidden="true" class="pointer-events-none absolute inset-0 m-auto hidden size-3.5 text-primary-foreground peer-checked:block">
<path d="M20 6 9 17l-5-5" />
</svg>
<CheckIcon stroke-width="3" Class="pointer-events-none absolute inset-0 m-auto hidden size-3.5 text-primary-foreground peer-checked:block" />
</span>

@code {
Expand Down
2 changes: 1 addition & 1 deletion src/FluentCMS.Admin/Components/Ui/CommandInput.razor
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div data-slot="command-input-wrapper" class="flex h-9 items-center gap-2 border-b px-3">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 shrink-0 opacity-50" aria-hidden="true"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
<SearchIcon Class="size-4 shrink-0 opacity-50" />
<input id="@Context.InputId"
type="text"
role="combobox"
Expand Down
2 changes: 1 addition & 1 deletion src/FluentCMS.Admin/Components/Ui/DialogContent.razor
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
command="close"
data-slot="dialog-close"
class="absolute top-4 end-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
<XIcon />
<span class="sr-only">@L["Close"]</span>
</button>
}
Expand Down
2 changes: 1 addition & 1 deletion src/FluentCMS.Admin/Components/Ui/DiffViewItem.razor
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<span>@KindLabel(Item.Operation.ItemKind)</span>
<bdi class="font-mono text-xs font-normal break-all text-muted-foreground">@Item.Operation.ItemId</bdi>
<span data-slot="diff-view-item-versions" class="ms-auto text-xs font-normal text-muted-foreground">@Versions(Item.Operation)</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 shrink-0 text-muted-foreground transition-transform duration-200 group-open:rotate-180" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
<ChevronDownIcon Class="size-4 shrink-0 text-muted-foreground transition-transform duration-200 group-open:rotate-180" />
</summary>
<div class="grid gap-4 border-t px-4 py-4">
@if (Item.Properties.Count == 0 && Item.Fields.Count == 0)
Expand Down
5 changes: 1 addition & 4 deletions src/FluentCMS.Admin/Components/Ui/RadioGroupItem.razor
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@
aria-invalid="@(Field?.HasError == true ? "true" : null)"
class="peer aspect-square size-4 shrink-0 appearance-none rounded-full border border-input text-primary shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:bg-input/30 dark:aria-invalid:ring-destructive/40"
@attributes="AdditionalAttributes" />
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
aria-hidden="true" data-slot="radio-group-indicator" class="pointer-events-none absolute inset-0 m-auto hidden size-2 fill-primary text-primary peer-checked:block">
<circle cx="12" cy="12" r="10" />
</svg>
<CircleIcon data-slot="radio-group-indicator" Class="pointer-events-none absolute inset-0 m-auto hidden size-2 fill-primary text-primary peer-checked:block" />
</span>

@code {
Expand Down
5 changes: 1 addition & 4 deletions src/FluentCMS.Admin/Components/Ui/Select.razor
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@
@attributes="AdditionalAttributes">
@ChildContent
</select>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
aria-hidden="true" data-slot="select-icon" class="pointer-events-none absolute end-3.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground opacity-50 select-none">
<path d="m6 9 6 6 6-6" />
</svg>
<ChevronDownIcon data-slot="select-icon" Class="pointer-events-none absolute end-3.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground opacity-50 select-none" />
</div>

@code {
Expand Down
2 changes: 1 addition & 1 deletion src/FluentCMS.Admin/Components/Ui/SheetContent.razor
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
command="close"
data-slot="sheet-close"
class="absolute top-4 end-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
<XIcon />
<span class="sr-only">@L["Close"]</span>
</button>
</dialog>
Expand Down
2 changes: 1 addition & 1 deletion src/FluentCMS.Admin/Components/Ui/Toaster.razor
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
data-slot="toast-close"
class="absolute top-3 end-3 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-hidden [&_svg]:size-4"
@onclick="() => Toasts.Dismiss(toast.Id)">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
<XIcon />
<span class="sr-only">@L["Close"]</span>
</button>
</li>
Expand Down
1 change: 1 addition & 0 deletions src/FluentCMS.Admin/Components/_Imports.razor
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
@using FluentCMS.Admin
@using FluentCMS.Admin.Components
@using FluentCMS.Admin.Components.Accounts
@using FluentCMS.Admin.Components.Icons
@using FluentCMS.Admin.Components.Layout
@using FluentCMS.Admin.Components.Ui
@using FluentCMS.Core.Identity
1 change: 1 addition & 0 deletions src/FluentCMS.Admin/FluentCMS.Admin.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
</ItemGroup>

<Import Project="Tailwind.targets" />
<Import Project="Icons.targets" />

</Project>
Loading
Loading