diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bd0854600..f909fa245 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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:///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. diff --git a/src/FluentCMS.Admin/Components/Icons/CheckIcon.razor b/src/FluentCMS.Admin/Components/Icons/CheckIcon.razor new file mode 100644 index 000000000..59d1e0ca6 --- /dev/null +++ b/src/FluentCMS.Admin/Components/Icons/CheckIcon.razor @@ -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. *@ + + +@code { + [Parameter] public string? Class { get; set; } + [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; } +} diff --git a/src/FluentCMS.Admin/Components/Icons/ChevronDownIcon.razor b/src/FluentCMS.Admin/Components/Icons/ChevronDownIcon.razor new file mode 100644 index 000000000..420624435 --- /dev/null +++ b/src/FluentCMS.Admin/Components/Icons/ChevronDownIcon.razor @@ -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. *@ + + +@code { + [Parameter] public string? Class { get; set; } + [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; } +} diff --git a/src/FluentCMS.Admin/Components/Icons/CircleIcon.razor b/src/FluentCMS.Admin/Components/Icons/CircleIcon.razor new file mode 100644 index 000000000..9e88ece82 --- /dev/null +++ b/src/FluentCMS.Admin/Components/Icons/CircleIcon.razor @@ -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. *@ + + +@code { + [Parameter] public string? Class { get; set; } + [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; } +} diff --git a/src/FluentCMS.Admin/Components/Icons/MoonIcon.razor b/src/FluentCMS.Admin/Components/Icons/MoonIcon.razor new file mode 100644 index 000000000..4195d0361 --- /dev/null +++ b/src/FluentCMS.Admin/Components/Icons/MoonIcon.razor @@ -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. *@ + + +@code { + [Parameter] public string? Class { get; set; } + [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; } +} diff --git a/src/FluentCMS.Admin/Components/Icons/PlusIcon.razor b/src/FluentCMS.Admin/Components/Icons/PlusIcon.razor new file mode 100644 index 000000000..3cf2a9aaa --- /dev/null +++ b/src/FluentCMS.Admin/Components/Icons/PlusIcon.razor @@ -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. *@ + + +@code { + [Parameter] public string? Class { get; set; } + [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; } +} diff --git a/src/FluentCMS.Admin/Components/Icons/SearchIcon.razor b/src/FluentCMS.Admin/Components/Icons/SearchIcon.razor new file mode 100644 index 000000000..38ffd94ad --- /dev/null +++ b/src/FluentCMS.Admin/Components/Icons/SearchIcon.razor @@ -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. *@ + + +@code { + [Parameter] public string? Class { get; set; } + [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; } +} diff --git a/src/FluentCMS.Admin/Components/Icons/SunIcon.razor b/src/FluentCMS.Admin/Components/Icons/SunIcon.razor new file mode 100644 index 000000000..ab04d7e3f --- /dev/null +++ b/src/FluentCMS.Admin/Components/Icons/SunIcon.razor @@ -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. *@ + + +@code { + [Parameter] public string? Class { get; set; } + [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; } +} diff --git a/src/FluentCMS.Admin/Components/Icons/XIcon.razor b/src/FluentCMS.Admin/Components/Icons/XIcon.razor new file mode 100644 index 000000000..cf26f7087 --- /dev/null +++ b/src/FluentCMS.Admin/Components/Icons/XIcon.razor @@ -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. *@ + + +@code { + [Parameter] public string? Class { get; set; } + [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; } +} diff --git a/src/FluentCMS.Admin/Components/Layout/ThemeToggle.razor b/src/FluentCMS.Admin/Components/Layout/ThemeToggle.razor index e168b81c6..81d75ded7 100644 --- a/src/FluentCMS.Admin/Components/Layout/ThemeToggle.razor +++ b/src/FluentCMS.Admin/Components/Layout/ThemeToggle.razor @@ -4,8 +4,8 @@ on static pages and while the circuit is down; "system" clears the choice. *@ - - + + @L["Toggle theme"] diff --git a/src/FluentCMS.Admin/Components/Pages/Showcase.razor b/src/FluentCMS.Admin/Components/Pages/Showcase.razor index 92bfcff04..088cc8402 100644 --- a/src/FluentCMS.Admin/Components/Pages/Showcase.razor +++ b/src/FluentCMS.Admin/Components/Pages/Showcase.razor @@ -56,6 +56,21 @@ @Hints(([L["Tab"]], L["Passes over it; a badge labels, it takes no focus."])) +
+

@L["Icons"]

+

@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."]

+
    + @foreach (var icon in Icons) + { +
  • + + @icon.Name +
  • + } +
+ @Hints(([L["Tab"]], L["Passes over them; an icon is hidden from assistive technology, so the control around it carries the name."])) +
+

@L["Kbd"]

@@ -310,7 +325,7 @@ - + @L["Save"] @L["Save"] ⌘S @@ -442,6 +457,13 @@
@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 IconParameters = new() { ["Class"] = "size-5 text-foreground" }; + private string? _lastAction; private ChangeSetDiff? _largeDiff; diff --git a/src/FluentCMS.Admin/Components/Ui/Checkbox.razor b/src/FluentCMS.Admin/Components/Ui/Checkbox.razor index b64f2ccc6..8f72aff08 100644 --- a/src/FluentCMS.Admin/Components/Ui/Checkbox.razor +++ b/src/FluentCMS.Admin/Components/Ui/Checkbox.razor @@ -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" /> - +