Migrate empty and item docs to Basecoat APIs

This commit is contained in:
Ronan Berder
2026-06-03 18:59:21 +08:00
parent 317c490e0c
commit 7e7d509e90
164 changed files with 15779 additions and 2940 deletions
+2
View File
@@ -0,0 +1,2 @@
@import "tailwindcss" source(none);
@import "./basecoat-luma.css";
+34
View File
@@ -0,0 +1,34 @@
@import "./base/base.css";
/* Components */
@import "./components/alert.css";
@import "./components/badge.css";
@import "./components/button.css";
@import "./components/button-group.css";
@import "./components/card.css";
@import "./components/collapsible.css";
@import "./components/command.css";
@import "./components/combobox.css";
@import "./components/dialog.css";
@import "./components/dropdown-menu.css";
@import "./components/empty.css";
@import "./components/field.css";
@import "./components/checkbox.css";
@import "./components/input.css";
@import "./components/input-group.css";
@import "./components/item.css";
@import "./components/kbd.css";
@import "./components/label.css";
@import "./components/native-select.css";
@import "./components/popover.css";
@import "./components/radio.css";
@import "./components/range.css";
@import "./components/select.css";
@import "./components/sidebar.css";
@import "./components/switch.css";
@import "./components/table.css";
@import "./components/tabs.css";
@import "./components/textarea.css";
@import "./components/toast.css";
@import "./components/tooltip.css";
@import "./styles/style-luma.css";
+2
View File
@@ -0,0 +1,2 @@
@import "tailwindcss" source(none);
@import "./basecoat-lyra.css";
+34
View File
@@ -0,0 +1,34 @@
@import "./base/base.css";
/* Components */
@import "./components/alert.css";
@import "./components/badge.css";
@import "./components/button.css";
@import "./components/button-group.css";
@import "./components/card.css";
@import "./components/collapsible.css";
@import "./components/command.css";
@import "./components/combobox.css";
@import "./components/dialog.css";
@import "./components/dropdown-menu.css";
@import "./components/empty.css";
@import "./components/field.css";
@import "./components/checkbox.css";
@import "./components/input.css";
@import "./components/input-group.css";
@import "./components/item.css";
@import "./components/kbd.css";
@import "./components/label.css";
@import "./components/native-select.css";
@import "./components/popover.css";
@import "./components/radio.css";
@import "./components/range.css";
@import "./components/select.css";
@import "./components/sidebar.css";
@import "./components/switch.css";
@import "./components/table.css";
@import "./components/tabs.css";
@import "./components/textarea.css";
@import "./components/toast.css";
@import "./components/tooltip.css";
@import "./styles/style-lyra.css";
+2
View File
@@ -0,0 +1,2 @@
@import "tailwindcss" source(none);
@import "./basecoat-maia.css";
+34
View File
@@ -0,0 +1,34 @@
@import "./base/base.css";
/* Components */
@import "./components/alert.css";
@import "./components/badge.css";
@import "./components/button.css";
@import "./components/button-group.css";
@import "./components/card.css";
@import "./components/collapsible.css";
@import "./components/command.css";
@import "./components/combobox.css";
@import "./components/dialog.css";
@import "./components/dropdown-menu.css";
@import "./components/empty.css";
@import "./components/field.css";
@import "./components/checkbox.css";
@import "./components/input.css";
@import "./components/input-group.css";
@import "./components/item.css";
@import "./components/kbd.css";
@import "./components/label.css";
@import "./components/native-select.css";
@import "./components/popover.css";
@import "./components/radio.css";
@import "./components/range.css";
@import "./components/select.css";
@import "./components/sidebar.css";
@import "./components/switch.css";
@import "./components/table.css";
@import "./components/tabs.css";
@import "./components/textarea.css";
@import "./components/toast.css";
@import "./components/tooltip.css";
@import "./styles/style-maia.css";
+2
View File
@@ -0,0 +1,2 @@
@import "tailwindcss" source(none);
@import "./basecoat-mira.css";
+34
View File
@@ -0,0 +1,34 @@
@import "./base/base.css";
/* Components */
@import "./components/alert.css";
@import "./components/badge.css";
@import "./components/button.css";
@import "./components/button-group.css";
@import "./components/card.css";
@import "./components/collapsible.css";
@import "./components/command.css";
@import "./components/combobox.css";
@import "./components/dialog.css";
@import "./components/dropdown-menu.css";
@import "./components/empty.css";
@import "./components/field.css";
@import "./components/checkbox.css";
@import "./components/input.css";
@import "./components/input-group.css";
@import "./components/item.css";
@import "./components/kbd.css";
@import "./components/label.css";
@import "./components/native-select.css";
@import "./components/popover.css";
@import "./components/radio.css";
@import "./components/range.css";
@import "./components/select.css";
@import "./components/sidebar.css";
@import "./components/switch.css";
@import "./components/table.css";
@import "./components/tabs.css";
@import "./components/textarea.css";
@import "./components/toast.css";
@import "./components/tooltip.css";
@import "./styles/style-mira.css";
+2
View File
@@ -0,0 +1,2 @@
@import "tailwindcss" source(none);
@import "./basecoat-nova.css";
+34
View File
@@ -0,0 +1,34 @@
@import "./base/base.css";
/* Components */
@import "./components/alert.css";
@import "./components/badge.css";
@import "./components/button.css";
@import "./components/button-group.css";
@import "./components/card.css";
@import "./components/collapsible.css";
@import "./components/command.css";
@import "./components/combobox.css";
@import "./components/dialog.css";
@import "./components/dropdown-menu.css";
@import "./components/empty.css";
@import "./components/field.css";
@import "./components/checkbox.css";
@import "./components/input.css";
@import "./components/input-group.css";
@import "./components/item.css";
@import "./components/kbd.css";
@import "./components/label.css";
@import "./components/native-select.css";
@import "./components/popover.css";
@import "./components/radio.css";
@import "./components/range.css";
@import "./components/select.css";
@import "./components/sidebar.css";
@import "./components/switch.css";
@import "./components/table.css";
@import "./components/tabs.css";
@import "./components/textarea.css";
@import "./components/toast.css";
@import "./components/tooltip.css";
@import "./styles/style-nova.css";
+2
View File
@@ -0,0 +1,2 @@
@import "tailwindcss" source(none);
@import "./basecoat-rhea.css";
+34
View File
@@ -0,0 +1,34 @@
@import "./base/base.css";
/* Components */
@import "./components/alert.css";
@import "./components/badge.css";
@import "./components/button.css";
@import "./components/button-group.css";
@import "./components/card.css";
@import "./components/collapsible.css";
@import "./components/command.css";
@import "./components/combobox.css";
@import "./components/dialog.css";
@import "./components/dropdown-menu.css";
@import "./components/empty.css";
@import "./components/field.css";
@import "./components/checkbox.css";
@import "./components/input.css";
@import "./components/input-group.css";
@import "./components/item.css";
@import "./components/kbd.css";
@import "./components/label.css";
@import "./components/native-select.css";
@import "./components/popover.css";
@import "./components/radio.css";
@import "./components/range.css";
@import "./components/select.css";
@import "./components/sidebar.css";
@import "./components/switch.css";
@import "./components/table.css";
@import "./components/tabs.css";
@import "./components/textarea.css";
@import "./components/toast.css";
@import "./components/tooltip.css";
@import "./styles/style-rhea.css";
+2
View File
@@ -0,0 +1,2 @@
@import "tailwindcss" source(none);
@import "./basecoat-sera.css";
+34
View File
@@ -0,0 +1,34 @@
@import "./base/base.css";
/* Components */
@import "./components/alert.css";
@import "./components/badge.css";
@import "./components/button.css";
@import "./components/button-group.css";
@import "./components/card.css";
@import "./components/collapsible.css";
@import "./components/command.css";
@import "./components/combobox.css";
@import "./components/dialog.css";
@import "./components/dropdown-menu.css";
@import "./components/empty.css";
@import "./components/field.css";
@import "./components/checkbox.css";
@import "./components/input.css";
@import "./components/input-group.css";
@import "./components/item.css";
@import "./components/kbd.css";
@import "./components/label.css";
@import "./components/native-select.css";
@import "./components/popover.css";
@import "./components/radio.css";
@import "./components/range.css";
@import "./components/select.css";
@import "./components/sidebar.css";
@import "./components/switch.css";
@import "./components/table.css";
@import "./components/tabs.css";
@import "./components/textarea.css";
@import "./components/toast.css";
@import "./components/tooltip.css";
@import "./styles/style-sera.css";
+2
View File
@@ -0,0 +1,2 @@
@import "tailwindcss" source(none);
@import "./basecoat-vega.css";
+34
View File
@@ -0,0 +1,34 @@
@import "./base/base.css";
/* Components */
@import "./components/alert.css";
@import "./components/badge.css";
@import "./components/button.css";
@import "./components/button-group.css";
@import "./components/card.css";
@import "./components/collapsible.css";
@import "./components/command.css";
@import "./components/combobox.css";
@import "./components/dialog.css";
@import "./components/dropdown-menu.css";
@import "./components/empty.css";
@import "./components/field.css";
@import "./components/checkbox.css";
@import "./components/input.css";
@import "./components/input-group.css";
@import "./components/item.css";
@import "./components/kbd.css";
@import "./components/label.css";
@import "./components/native-select.css";
@import "./components/popover.css";
@import "./components/radio.css";
@import "./components/range.css";
@import "./components/select.css";
@import "./components/sidebar.css";
@import "./components/switch.css";
@import "./components/table.css";
@import "./components/tabs.css";
@import "./components/textarea.css";
@import "./components/toast.css";
@import "./components/tooltip.css";
@import "./styles/style-vega.css";
+4
View File
@@ -8,11 +8,15 @@
@import "./components/card.css";
@import "./components/collapsible.css";
@import "./components/command.css";
@import "./components/combobox.css";
@import "./components/dialog.css";
@import "./components/dropdown-menu.css";
@import "./components/empty.css";
@import "./components/field.css";
@import "./components/checkbox.css";
@import "./components/input.css";
@import "./components/input-group.css";
@import "./components/item.css";
@import "./components/kbd.css";
@import "./components/label.css";
@import "./components/native-select.css";
+12 -26
View File
@@ -2,34 +2,20 @@
@layer components {
.alert,
.alert-destructive {
@apply relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current;
> h2,
> h3,
> h4,
> h5,
> h6,
> strong,
> [data-title] {
@apply col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight;
}
> section {
@apply text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed;
ul {
@apply list-inside list-disc text-sm;
}
}
@apply relative w-full;
}
.alert {
@apply bg-card text-card-foreground;
}
.alert-destructive {
@apply text-destructive bg-card [&>svg]:text-current;
> section {
@apply text-destructive;
.alert > :is(h2, h3, h4, h5, h6, strong, [data-title]),
.alert-destructive > :is(h2, h3, h4, h5, h6, strong, [data-title]) {
@apply min-h-4;
}
.alert > section,
.alert-destructive > section {
@apply grid justify-items-start gap-1;
ul {
@apply list-inside list-disc;
}
}
}
+4 -16
View File
@@ -4,21 +4,9 @@
.badge-primary,
.badge-secondary,
.badge-destructive,
.badge-outline {
@apply inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden;
}
.badge,
.badge-primary {
@apply border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90;
}
.badge-secondary {
@apply border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90;
}
.badge-destructive {
@apply border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60;
}
.badge-outline {
@apply text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground;
.badge-outline,
.badge-ghost,
.badge-link {
@apply inline-flex w-fit shrink-0 items-center justify-center overflow-hidden whitespace-nowrap focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none;
}
}
+19 -9
View File
@@ -1,33 +1,36 @@
/* Button Group */
@layer components {
.button-group {
@apply inline-flex w-fit items-stretch;
/* isolate */
@apply isolate inline-flex w-fit items-stretch;
> .input {
@apply flex-1;
}
> *:focus-visible,
> :is(.dropdown-menu, .popover, .select) > button:focus-visible {
@apply relative z-10;
}
> hr[role='separator'] {
@apply w-0 h-auto self-stretch border border-input shrink-0 m-0;
> :is(hr[role='separator'], .button-group-separator) {
@apply relative w-px h-auto self-stretch shrink-0 border-0 my-px mx-0;
}
&:not([data-orientation='vertical']) {
> *:not(:first-child),
> :is(.dropdown-menu, .popover, .select):not(:first-child) > button {
@apply rounded-l-none border-l-0;
@apply rounded-s-none border-s-0;
}
> *:not(:last-child),
> :is(.dropdown-menu, .popover, .select):not(:last-child) > button {
@apply rounded-r-none;
@apply rounded-e-none;
}
}
&[data-orientation='vertical'] {
@apply flex-col;
> hr[role='separator'] {
@apply w-auto h-px;
> :is(hr[role='separator'], .button-group-separator) {
@apply w-auto h-px mx-px my-0;
}
> *:not(:first-child),
@@ -40,5 +43,12 @@
}
}
}
}
.button-group-text {
@apply flex items-center [&_svg]:pointer-events-none;
}
.button-group-separator {
@apply relative self-stretch shrink-0;
}
}
+1 -1
View File
@@ -56,6 +56,6 @@
.btn-lg-icon-ghost,
.btn-lg-icon-link,
.btn-lg-icon-destructive {
@apply inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 cursor-pointer;
@apply inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0;
}
}
+10 -13
View File
@@ -1,25 +1,22 @@
/* Card */
@layer components {
.card {
@apply bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm;
@apply flex flex-col;
> header {
@apply @container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6;
/* TODO: CLEAN has-data-[slot=card-action] */
@apply @container/card-header grid auto-rows-min items-start has-[>[data-slot=card-action],>.card-action]:grid-cols-[1fr_auto] has-[>p]:grid-rows-[auto_auto];
}
h2 {
@apply leading-none font-semibold;
}
p {
@apply text-muted-foreground text-sm;
}
> header > :is(h2, h3, [data-title], .card-title) {
@apply leading-none;
}
> section {
@apply px-6;
> header > :is([data-slot='card-action'], .card-action) {
@apply col-start-2 row-span-2 row-start-1 self-start justify-self-end;
}
> footer {
@apply flex items-center px-6 [.border-t]:pt-6;
@apply flex items-center;
}
}
}
+3 -4
View File
@@ -1,13 +1,12 @@
/* Checkbox */
@layer components {
:is(.form, .field) input[type='checkbox']:not([role='switch']),
.field > input[type='checkbox']:not([role='switch']),
.input[type='checkbox']:not([role='switch']) {
@apply appearance-none border-input dark:bg-input/30 checked:bg-primary dark:checked:bg-primary checked:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50;
@apply relative shrink-0 appearance-none outline-none disabled:cursor-not-allowed disabled:opacity-50;
&:checked:after {
@apply content-[''] block size-3.5 bg-primary-foreground;
@apply content-[''] block bg-current;
@apply mask-[image:var(--check-icon)] mask-size-[0.875rem] mask-no-repeat mask-center;
}
}
}
+78
View File
@@ -0,0 +1,78 @@
/* Combobox */
@layer components {
.combobox {
@apply relative inline-flex w-fit flex-col;
}
.combobox > input[role='combobox'] {
@apply w-full min-w-0 outline-hidden placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50;
}
.combobox > svg,
.combobox-trigger-icon {
@apply pointer-events-none absolute shrink-0;
}
.combobox [data-popover] {
@apply isolate overflow-hidden;
min-width: max(100%, 9rem);
}
.combobox [role='listbox'] {
@apply outline-hidden overflow-y-auto;
}
.combobox [role='listbox'] [role='group']:not(:has([role='option']:not([aria-hidden='true']))) {
@apply hidden;
}
.combobox [role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply flex items-center justify-center truncate;
}
.combobox [role='listbox'][data-empty]:not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply content-[attr(data-empty)];
}
.combobox [role='listbox']:not([data-empty]):not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply content-['No_results_found'];
}
.combobox [role='option'] {
@apply aria-hidden:hidden relative flex w-full cursor-default items-center truncate outline-hidden select-none aria-disabled:pointer-events-none aria-disabled:opacity-50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0;
}
.combobox [role='option'][aria-selected='true'] {
@apply bg-[image:var(--check-icon)] bg-no-repeat bg-position-[center_right_0.5rem] bg-size-[0.875rem];
}
.combobox [role='option'][aria-selected='true']:dir(rtl) {
@apply bg-position-[center_left_0.5rem];
}
.combobox [role='separator'] {
@apply border-0;
}
.combobox [role='option'].active,
.combobox [role='option']:focus-visible,
.combobox:not([data-combobox-initialized]) [role='option']:not([aria-disabled='true']):not(:disabled):hover {
@apply bg-accent text-accent-foreground;
}
.combobox-chips {
@apply flex w-full min-w-0 flex-wrap items-center;
}
.combobox-chips > input[role='combobox'] {
@apply min-w-16 flex-1 bg-transparent outline-hidden placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50;
}
.combobox-chip {
@apply inline-flex w-fit shrink-0 items-center whitespace-nowrap;
}
.combobox-chip-remove {
@apply inline-flex items-center justify-center;
}
}
+91 -69
View File
@@ -3,12 +3,13 @@
.command-dialog {
@apply inset-y-0 opacity-0 transition-all transition-discrete;
&:is([open],:popover-open) {
&:is([open], :popover-open) {
@apply opacity-100;
&::backdrop {
@apply opacity-100;
}
> * {
@apply scale-100;
}
@@ -19,91 +20,112 @@
&::backdrop {
@apply opacity-0;
}
> * {
@apply scale-95;
}
}
}
&::backdrop {
@apply bg-black/50 opacity-0 transition-all transition-discrete;
@apply opacity-0 transition-all transition-discrete;
}
> * {
@apply bg-background fixed top-[50%] left-[50%] z-50 flex flex-col w-full max-w-[calc(100%_-_2rem)] -translate-x-1/2 -translate-y-1/2 overflow-hidden rounded-lg border shadow-lg sm:max-w-lg max-h-[min(640px,calc(100%_-_2rem))];
@apply transition-all scale-95;
> button,
> form[method='dialog'] {
@apply absolute top-4 right-4;
}
> button,
> form[method='dialog'] > button {
@apply ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4;
}
@apply fixed top-[50%] left-[50%] z-50 flex max-h-[min(640px,calc(100%_-_2rem))] w-full max-w-[calc(100%_-_2rem)] -translate-x-1/2 -translate-y-1/2 scale-95 flex-col overflow-hidden transition-all sm:max-w-lg;
}
.command {
> header input {
@apply h-12;
}
[role='menu'] {
@apply px-2;
> * > :is(button, form[method='dialog']) {
@apply absolute;
}
[role='menuitem'] {
@apply py-3 px-2 [&_svg]:size-5;
}
[role='heading'] {
@apply px-2 py-1.5 font-medium text-muted-foreground;
}
}
> * > :is(button, form[method='dialog'] > button) {
@apply inline-flex items-center justify-center opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0;
}
> * > :is(button, form[method='dialog'] > button) > svg:not([class*='size-']) {
@apply size-4;
}
}
.command {
> header {
@apply flex items-center border-b px-3 gap-2;
@apply flex w-full flex-col overflow-hidden;
}
svg {
@apply size-4 shrink-0 opacity-50;
}
input {
@apply placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50;
}
}
[role='menu'] {
@apply max-h-[300px] overflow-y-auto overflow-x-hidden p-1;
.command > header {
@apply flex items-center;
}
[role='menuitem'] {
@apply aria-hidden:hidden relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none aria-disabled:opacity-50 aria-disabled:pointer-events-none disabled:opacity-50 disabled:pointer-events-none w-full truncate [&_svg:not([class*='text-'])]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4;
.command > header svg {
@apply shrink-0;
}
&.active {
@apply bg-accent text-accent-foreground;
}
}
[role='heading'] {
@apply text-muted-foreground flex px-2 py-1.5 text-xs font-medium;
}
[role='group']:not(:has([role='menuitem']:not([aria-hidden='true']))) {
@apply hidden;
}
[role='separator'] {
@apply border-border -mx-1 my-1;
}
&:not(:has([role='menuitem']:not([aria-hidden='true'])))::before {
@apply flex items-center justify-center py-6 px-3 text-sm truncate -m-1;
}
&[data-empty]:not(:has([role='menuitem']:not([aria-hidden='true'])))::before {
@apply content-[attr(data-empty)];
}
&:not([data-empty]):not(:has([role='menuitem']:not([aria-hidden='true'])))::before {
@apply content-['No_results_found'];
}
}
&:not([data-command-initialized]) [role='menuitem'] {
@apply hover:bg-accent hover:text-accent-foreground;
}
&:has(> header input:not(:placeholder-shown)) [role='separator'] {
@apply hidden;
}
.command > header input {
@apply w-full bg-transparent outline-hidden disabled:cursor-not-allowed disabled:opacity-50;
}
.command [role='menu'] {
@apply overflow-x-hidden overflow-y-auto outline-hidden;
}
.command [role='menuitem'] {
@apply aria-hidden:hidden relative flex w-full cursor-default items-center truncate outline-hidden select-none disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0;
}
.command [role='menuitem'].active {
@apply bg-accent text-accent-foreground;
}
.command [role='group'] {
@apply overflow-hidden;
}
.command [role='group']:not(:has([role='menuitem']:not([aria-hidden='true']))) {
@apply hidden;
}
.command [role='heading'] {
@apply flex;
}
.command [role='separator'] {
@apply border-0;
}
.command [data-shortcut] {
@apply ms-auto;
}
.command [data-indicator] {
@apply ms-auto opacity-0;
}
.command [role='menuitem'][data-checked='true'] [data-indicator],
.command [role='menuitem'][aria-selected='true'] [data-indicator] {
@apply opacity-100;
}
.command [role='menuitem']:has([data-shortcut]) [data-indicator] {
@apply hidden;
}
.command [role='menu']:not(:has([role='menuitem']:not([aria-hidden='true'])))::before {
@apply flex items-center justify-center truncate;
}
.command [role='menu'][data-empty]:not(:has([role='menuitem']:not([aria-hidden='true'])))::before {
@apply content-[attr(data-empty)];
}
.command [role='menu']:not([data-empty]):not(:has([role='menuitem']:not([aria-hidden='true'])))::before {
@apply content-['No_results_found'];
}
.command:not([data-command-initialized]) [role='menuitem']:not([aria-disabled='true']):not(:disabled):hover {
@apply bg-accent text-accent-foreground;
}
.command:has(> header input:not(:placeholder-shown)) [role='separator'] {
@apply hidden;
}
}
+30 -39
View File
@@ -1,61 +1,52 @@
/* Dialog */
@layer components {
.dialog {
@apply inset-y-0 opacity-0 transition-all transition-discrete;
@apply bg-transparent p-0 text-inherit opacity-0 transition-all transition-discrete backdrop:opacity-0 backdrop:transition-all backdrop:transition-discrete;
max-width: none;
max-height: none;
&:is([open],:popover-open) {
@apply opacity-100;
&:is([open], :popover-open) {
@apply opacity-100 backdrop:opacity-100;
&::backdrop {
@apply opacity-100;
}
> * {
@apply scale-100;
}
@starting-style {
@apply opacity-0;
@apply opacity-0 backdrop:opacity-0;
&::backdrop {
@apply opacity-0;
}
> * {
@apply scale-95;
}
}
}
&::backdrop {
@apply bg-black/50 opacity-0 transition-all transition-discrete;
}
> * {
@apply bg-background fixed top-[50%] left-[50%] z-50 flex flex-col w-full max-w-[calc(100%_-_2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-lg border p-6 shadow-lg sm:max-w-lg max-h-[calc(100%_-_2rem)];
@apply transition-all scale-95;
@apply fixed top-1/2 left-1/2 z-50 flex max-h-[calc(100%_-_2rem)] w-full -translate-x-1/2 -translate-y-1/2 scale-95 flex-col overflow-hidden transition-all outline-hidden;
}
> header {
@apply flex flex-col gap-2 text-center sm:text-left;
> * > header {
@apply flex flex-col;
}
> h2 {
@apply text-lg leading-none font-semibold;
}
> p {
@apply text-muted-foreground text-sm;
}
}
> section {
@apply flex-1 -mx-6 px-6;
}
> footer {
@apply flex flex-col-reverse gap-2 sm:flex-row sm:justify-end;
}
> button,
> form[method='dialog'] {
@apply absolute top-4 right-4;
}
> button,
> form[method='dialog'] > button {
@apply ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4;
}
> * > section {
@apply flex-1 overflow-y-auto;
}
> * > footer {
@apply flex flex-col-reverse sm:flex-row sm:justify-end;
}
> * > :is(button, form[method='dialog']) {
@apply absolute;
}
> * > :is(button, form[method='dialog'] > button) {
@apply inline-flex items-center justify-center opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0;
}
> * > :is(button, form[method='dialog'] > button) > svg:not([class*='size-']) {
@apply size-4;
}
}
}
+37 -28
View File
@@ -2,38 +2,47 @@
@layer components {
.dropdown-menu {
@apply relative inline-flex;
}
[data-popover] {
@apply p-1;
min-width: anchor-size(width);
.dropdown-menu > [data-popover] {
min-width: anchor-size(width);
}
[role='menuitem'],
[role='menuitemcheckbox'],
[role='menuitemradio'] {
@apply aria-hidden:hidden [&_svg]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none [&_svg]:shrink-0 [&_svg]:size-4 aria-disabled:opacity-50 aria-disabled:pointer-events-none disabled:opacity-50 disabled:pointer-events-none w-full truncate;
.dropdown-menu [role='menu'] {
@apply outline-hidden;
}
&:not([aria-disabled='true']) {
@apply focus-visible:bg-accent focus-visible:text-accent-foreground;
}
.dropdown-menu :is([role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']) {
@apply aria-hidden:hidden relative flex w-full cursor-default items-center truncate outline-hidden select-none disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0;
}
&.active {
@apply bg-accent text-accent-foreground;
}
}
[role='menu'] [role='heading'] {
@apply flex px-2 py-1.5 text-sm font-medium;
}
[role='separator'] {
@apply border-border -mx-1 my-1;
}
}
&:not([data-dropdown-menu-initialized]) [data-popover] {
[role='menuitem'],
[role='menuitemcheckbox'],
[role='menuitemradio'] {
@apply hover:bg-accent hover:text-accent-foreground;
}
.dropdown-menu [role='menu'] [role='heading'] {
@apply flex;
}
.dropdown-menu [role='separator'] {
@apply border-0;
}
.dropdown-menu [data-shortcut] {
@apply ms-auto;
}
.dropdown-menu [data-indicator] {
@apply pointer-events-none invisible;
}
.dropdown-menu :is([role='menuitemcheckbox'], [role='menuitemradio'])[aria-checked='true'] [data-indicator] {
@apply visible;
}
.dropdown-menu [data-rtl-flip] {
@apply rtl:rotate-180;
}
.dropdown-menu:not([data-dropdown-menu-initialized]) :is([role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']) {
&:not([aria-disabled='true']):not(:disabled):hover {
@apply bg-accent text-accent-foreground;
}
}
}
+26
View File
@@ -0,0 +1,26 @@
/* Empty */
@layer components {
.empty {
@apply flex w-full min-w-0 flex-1 flex-col items-center justify-center text-center text-balance;
}
.empty > header {
@apply flex max-w-sm flex-col items-center;
}
.empty figure {
@apply flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0;
}
.empty figure:has(img) {
@apply overflow-hidden [&_img]:size-full [&_img]:object-cover;
}
.empty > :is(section, footer) {
@apply flex w-full max-w-sm min-w-0 flex-col items-center text-balance;
}
.empty p {
@apply [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary;
}
}
+69 -27
View File
@@ -1,44 +1,86 @@
/* Field */
@layer components {
.fieldset {
@apply flex flex-col gap-6;
> legend {
@apply text-base font-medium mb-3;
}
@apply flex flex-col;
}
.field {
@apply flex flex-col w-full gap-3 data-[invalid=true]:text-destructive [&>*]:w-full [&>.sr-only]:w-auto;
h2,
h3 {
@apply flex w-fit items-center gap-2 text-sm leading-snug font-medium;
}
[role="alert"] {
@apply text-sm text-destructive font-normal;
ul {
@apply ml-4 flex list-disc flex-col gap-1;
}
}
section {
@apply flex flex-1 flex-col gap-1.5 leading-snug;
}
.fieldset > :is([role='group'], [role='radiogroup']):not(.field),
.field > :is(fieldset, [role='group'], [role='radiogroup']):not(.field) {
@apply @container/field-group flex w-full flex-col;
}
.field {
@apply flex w-full flex-col data-[invalid=true]:text-destructive [&>*]:w-full [&>.sr-only]:w-auto;
&[data-orientation='horizontal'] {
@apply flex-row items-center [&>label]:flex-auto has-[>section]:items-start has-[>section]:[&>[type=checkbox],[type=radio]]:mt-px [&_p]:text-balance;
}
&[data-orientation='responsive'] {
@apply flex-col @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto @md/field-group:has-[>section]:items-start @md/field-group:[&>label]:flex-auto @md/field-group:has-[>section]:[&>[type=checkbox],[type=radio]]:mt-px;
}
section,
.field-content {
@apply flex flex-1 flex-col leading-snug;
}
h2,
h3,
.field-title {
@apply flex w-fit items-center;
}
[role='alert'],
.field-error {
@apply font-normal;
ul {
@apply ms-4 flex list-disc flex-col gap-1;
}
}
}
.field > label,
label:has(> .field) {
@apply w-fit;
}
.field > label:has(> .field),
.field > label:has(input[type='radio']),
.field > label:has(input[type='checkbox']),
.field > label:has(input[role='switch']),
label:has(> .field) {
@apply w-full;
}
.field > label:has(> .field),
label:has(> .field) {
@apply flex-col;
}
.field-description,
.fieldset legend + p,
.field > p,
.field section > p {
@apply text-muted-foreground text-sm leading-normal font-normal [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4;
@apply leading-normal font-normal [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4;
}
.fieldset legend + p {
@apply -mt-1.5;
}
.field > p {
.field > p,
.field section > p,
.field-description {
@apply last:mt-0 nth-last-2:-mt-1;
}
}
.field-separator {
@apply relative;
}
.field-separator > hr[role='separator'] {
@apply absolute inset-x-0 top-1/2;
}
.field-separator > span {
@apply relative mx-auto block w-fit bg-background;
}
}
+50
View File
@@ -0,0 +1,50 @@
/* Input Group */
@layer components {
.input-group {
@apply relative isolate flex w-full min-w-0 items-center overflow-hidden outline-none;
&[data-orientation='vertical'] {
@apply h-auto flex-col items-stretch;
}
}
.input-group > :is(input, textarea, select, [data-control]) {
@apply text-foreground placeholder:text-muted-foreground block min-w-0 flex-1 appearance-none rounded-none border-0 bg-transparent shadow-none outline-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 disabled:bg-transparent;
}
.input-group > :is(input, select, [data-control]) {
@apply h-full;
}
.input-group > [data-control] {
@apply w-full;
}
.input-group[data-orientation='vertical'] > :is(input, select, [data-control]) {
@apply h-auto flex-none;
}
.input-group > textarea {
@apply field-sizing-content min-h-20 w-full resize-none;
}
.input-group > :is(span, kbd, svg, [role='group'], .popover, .dropdown-menu, button, footer, header, hr[role='separator']) {
@apply shrink-0;
}
.input-group > :is(span, [role='group'], footer, header) {
@apply inline-flex items-center;
}
.input-group > svg {
@apply pointer-events-none;
}
.input-group > [data-align='start'] {
@apply order-first;
}
.input-group > [data-align='end'] {
@apply order-last;
}
}
+16 -18
View File
@@ -1,18 +1,18 @@
/* Input */
@layer components {
:is(.form, .field) input[type='text']:not(:is(.select [data-popover] > header input[role='combobox'], .command > header input, .dialog > * > header input)),
:is(.form, .field) input[type='email'],
:is(.form, .field) input[type='password'],
:is(.form, .field) input[type='number'],
:is(.form, .field) input[type='file'],
:is(.form, .field) input[type='tel'],
:is(.form, .field) input[type='url'],
:is(.form, .field) input[type='search']:not(:is(.select [data-popover] > header input[role='combobox'], .command > header input, .dialog > * > header input)),
:is(.form, .field) input[type='date'],
:is(.form, .field) input[type='datetime-local'],
:is(.form, .field) input[type='month'],
:is(.form, .field) input[type='week'],
:is(.form, .field) input[type='time'],
.field > input[type='text']:not(:is(.command > header input, .dialog > * > header input)),
.field > input[type='email'],
.field > input[type='password'],
.field > input[type='number'],
.field > input[type='file'],
.field > input[type='tel'],
.field > input[type='url'],
.field > input[type='search']:not(:is(.command > header input, .dialog > * > header input)),
.field > input[type='date'],
.field > input[type='datetime-local'],
.field > input[type='month'],
.field > input[type='week'],
.field > input[type='time'],
.input[type='text'],
.input[type='email'],
.input[type='password'],
@@ -25,10 +25,8 @@
.input[type='datetime-local'],
.input[type='month'],
.input[type='week'],
.input[type='time'] {
@apply appearance-none file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm;
@apply focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px];
@apply aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive;
.input[type='time'],
.input:not([type]) {
@apply w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50;
}
}
+50
View File
@@ -0,0 +1,50 @@
/* Item */
@layer components {
.item-group {
@apply flex w-full flex-col;
}
.item-group > hr {
@apply w-full shrink-0 border-0 border-t;
}
.item {
@apply flex w-full flex-wrap items-center border transition-colors duration-100 outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50;
}
a.item {
@apply hover:bg-muted;
}
.item > figure {
@apply flex shrink-0 items-center justify-center [&_svg]:pointer-events-none;
}
.item > figure:has(img) {
@apply overflow-hidden [&_img]:size-full [&_img]:object-cover;
}
.item > section {
@apply flex flex-1 flex-col;
}
.item > section + section {
@apply flex-none;
}
.item > aside {
@apply flex items-center;
}
.item > :is(header, footer) {
@apply flex basis-full items-center justify-between;
}
.item :is(h2, h3, h4) {
@apply line-clamp-1 flex w-fit items-center;
}
.item p {
@apply text-muted-foreground line-clamp-2 text-start font-normal [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary;
}
}
+5 -2
View File
@@ -1,7 +1,10 @@
/* Kbd */
@layer components {
.kbd {
@apply bg-muted text-muted-foreground pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium select-none [&_svg:not([class*='size-'])]:size-3;
@apply pointer-events-none inline-flex items-center justify-center select-none;
}
.kbd-group {
@apply inline-flex items-center;
}
}
+6 -7
View File
@@ -1,13 +1,12 @@
/* Label */
@layer components {
:is(.form, .field) label,
.field > label,
.label {
@apply flex items-center gap-2 text-sm leading-none font-medium select-none peer-disabled:pointer-events-none peer-disabled:opacity-50;
@apply flex items-center select-none peer-disabled:pointer-events-none;
&:has(>*:disabled),
&:has(+*:disabled) {
@apply opacity-50 pointer-events-none;
}
&:has(>*:disabled),
&:has(+*:disabled) {
@apply pointer-events-none;
}
}
}
+3 -4
View File
@@ -1,13 +1,12 @@
/* Native Select */
@layer components {
:is(.form, .field) select,
.field > select,
select.select {
@apply appearance-none border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent pl-3 pr-9 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 h-9;
@apply bg-[image:var(--chevron-down-icon-50)] bg-no-repeat bg-position-[center_right_0.75rem] bg-size-[1rem];
@apply min-w-0 appearance-none outline-none select-none disabled:cursor-not-allowed disabled:opacity-50;
option,
optgroup {
@apply bg-popover text-popover-foreground;
@apply bg-[Canvas] text-[CanvasText];
}
}
}
+22 -13
View File
@@ -1,72 +1,81 @@
/* Popover */
@layer components {
[data-popover] {
@apply absolute bg-popover text-popover-foreground overflow-x-hidden overflow-y-auto rounded-md border shadow-md z-50 visible opacity-100 scale-100 min-w-full w-max transition-all;
@apply absolute z-50 min-w-full w-max overflow-x-hidden overflow-y-auto visible opacity-100 scale-100 transition-all outline-hidden;
&[aria-hidden='true'] {
@apply invisible opacity-0 scale-95;
&:not([data-side]),
&[data-side='bottom'] {
@apply -translate-y-2;
}
&[data-side='top'] {
@apply translate-y-2;
}
&[data-side='left'] {
@apply translate-x-2;
}
&[data-side='right'] {
@apply -translate-x-2;
}
}
&:not([data-side]),
&[data-side='bottom'] {
@apply mt-1 top-full;
}
&[data-side='top'] {
@apply mb-1 bottom-full;
}
&[data-side='left'] {
@apply mr-1 right-full;
@apply me-1 end-full;
}
&[data-side='right'] {
@apply ml-1 left-full;
@apply ms-1 start-full;
}
&:not([data-side]),
&[data-side='bottom'],
&[data-side='top'] {
&:not([data-align]),
&[data-align='start'] {
@apply left-0;
@apply start-0;
}
&[data-align='end'] {
@apply right-0;
@apply end-0;
}
&[data-align='center'] {
@apply left-1/2 -translate-x-1/2;
@apply start-1/2 -translate-x-1/2;
}
}
&[data-side='left'],
&[data-side='right'] {
&:not([data-align]),
&[data-align='start'] {
@apply top-0;
}
&[data-align='end'] {
@apply bottom-0;
}
&[data-align='center'] {
@apply top-1/2 -translate-y-1/2;
}
}
}
.popover {
@apply relative inline-flex;
[data-popover] {
@apply p-4;
}
}
}
}
+3 -4
View File
@@ -1,12 +1,11 @@
/* Radio */
@layer components {
:is(.form, .field) input[type='radio'],
.field > input[type='radio'],
.input[type='radio'] {
@apply appearance-none border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 relative;
@apply relative shrink-0 appearance-none outline-none disabled:cursor-not-allowed disabled:opacity-50;
&:checked:before {
@apply absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 content-[''] rounded-full size-2 bg-primary;
@apply absolute top-1/2 left-1/2 content-[''] -translate-x-1/2 -translate-y-1/2 rounded-full;
}
}
}
+26 -25
View File
@@ -1,49 +1,50 @@
/* Range */
@layer components {
:is(.form, .field) input[type='range'],
.field > input[type='range'],
.input[type='range'] {
@apply appearance-none flex items-center p-0 outline-none;
@apply flex appearance-none items-center p-0 outline-none disabled:pointer-events-none disabled:opacity-50;
--slider-value: 20%;
&:hover,
&:focus-visible {
&::-webkit-slider-thumb {
@apply ring-4;
}
&::-moz-range-thumb {
@apply ring-4;
}
&::-ms-thumb {
@apply ring-4;
}
&::-webkit-slider-thumb {
@apply block shrink-0 appearance-none;
}
&::-webkit-slider-thumb {
@apply appearance-none border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm -mt-1.25;
}
&::-webkit-slider-runnable-track {
@apply appearance-none rounded-full h-1.5 w-full;
@apply w-full appearance-none;
background: linear-gradient(to right, var(--primary) var(--slider-value), var(--muted) var(--slider-value));
}
}
&:dir(rtl)::-webkit-slider-runnable-track {
background: linear-gradient(to left, var(--primary) var(--slider-value), var(--muted) var(--slider-value));
}
&::-moz-range-thumb {
@apply appearance-none border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm -mt-1.25;
@apply block shrink-0 appearance-none;
}
&::-moz-range-track {
@apply appearance-none rounded-full h-1.5 w-full;
@apply w-full appearance-none;
background: linear-gradient(to right, var(--primary) var(--slider-value), var(--muted) var(--slider-value));
}
&:dir(rtl)::-moz-range-track {
background: linear-gradient(to left, var(--primary) var(--slider-value), var(--muted) var(--slider-value));
}
&::-ms-thumb {
@apply appearance-none border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm -mt-1.25;
@apply block shrink-0 appearance-none;
}
&::-ms-track {
@apply appearance-none rounded-full h-1.5 w-full;
@apply w-full appearance-none;
}
&::-ms-fill-lower {
@apply bg-primary rounded-full;
@apply bg-primary;
}
&::-ms-fill-upper {
@apply bg-muted rounded-full;
@apply bg-muted;
}
}
}
+54 -54
View File
@@ -2,63 +2,63 @@
@layer components {
*:not(select).select {
@apply relative inline-flex;
& > button {
@apply justify-between font-normal;
}
&[aria-invalid='true'] {
@apply ring-destructive/20 dark:ring-destructive/40 border-destructive;
}
}
*:not(select).select > button {
@apply flex w-fit items-center justify-between whitespace-nowrap font-normal outline-hidden disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0;
}
[data-popover] {
@apply p-1;
*:not(select).select > button > span {
@apply flex flex-1 truncate text-start;
}
[role='option'] {
@apply aria-hidden:hidden [&_svg]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm pl-2 py-1.5 pr-7.5 text-sm outline-hidden select-none [&_svg]:shrink-0 [&_svg]:size-4 aria-disabled:opacity-50 aria-disabled:pointer-events-none disabled:opacity-50 disabled:pointer-events-none w-full truncate;
&[aria-selected='true'] {
@apply bg-[image:var(--check-icon)] bg-no-repeat bg-position-[center_right_0.5rem] bg-size-[0.875rem];
}
&.active,
&:focus-visible {
@apply bg-accent text-accent-foreground;
}
}
[role='listbox'] [role='heading'] {
@apply flex text-muted-foreground px-2 py-1.5 text-xs;
}
[role='listbox'] [role='group']:not(:has([role='option']:not([aria-hidden='true']))) {
@apply hidden;
}
[role='separator'] {
@apply border-border -mx-1 my-1;
}
> header {
@apply flex h-9 items-center gap-2 border-b px-3 -mx-1 -mt-1 mb-1;
*:not(select).select > button > svg {
@apply shrink-0;
}
svg {
@apply size-4 shrink-0 opacity-50;
}
input[role='combobox'] {
@apply placeholder:text-muted-foreground flex h-10 flex-1 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50 min-w-0;
}
}
[role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply flex items-center justify-center p-6 text-sm truncate;
}
[role='listbox'][data-empty]:not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply content-[attr(data-empty)];
}
[role='listbox']:not([data-empty]):not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply content-['No_results_found'];
}
}
[data-popover]:has(> header input:not(:placeholder-shown)) [role='separator'] {
@apply hidden;
}
&:not([data-select-initialized]) [data-popover] [role='option'] {
@apply hover:bg-accent hover:text-accent-foreground;
}
*:not(select).select [data-popover] {
@apply isolate overflow-x-hidden;
min-width: max(100%, 9rem);
}
*:not(select).select [role='listbox'] {
@apply outline-hidden;
}
*:not(select).select [role='listbox'] [role='group']:not(:has([role='option']:not([aria-hidden='true']))) {
@apply hidden;
}
*:not(select).select [role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply flex items-center justify-center truncate;
}
*:not(select).select [role='listbox'][data-empty]:not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply content-[attr(data-empty)];
}
*:not(select).select [role='listbox']:not([data-empty]):not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply content-['No_results_found'];
}
*:not(select).select [role='option'] {
@apply aria-hidden:hidden relative flex w-full cursor-default items-center truncate outline-hidden select-none aria-disabled:pointer-events-none aria-disabled:opacity-50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0;
}
*:not(select).select [role='option'][aria-selected='true'] {
@apply bg-[image:var(--check-icon)] bg-no-repeat bg-position-[center_right_0.5rem] bg-size-[0.875rem];
}
*:not(select).select [role='option'][aria-selected='true']:dir(rtl) {
@apply bg-position-[center_left_0.5rem];
}
*:not(select).select [role='separator'] {
@apply border-0;
}
*:not(select).select:not([data-select-initialized]) [role='option']:not([aria-disabled='true']):not(:disabled):hover,
*:not(select).select [role='option'].active {
@apply bg-accent text-accent-foreground;
}
}
+12 -33
View File
@@ -9,7 +9,7 @@
@apply max-md:bg-black/50 max-md:fixed max-md:inset-0 max-md:z-40;
}
nav {
@apply bg-sidebar text-sidebar-foreground flex flex-col w-(--sidebar-mobile-width) md:w-(--sidebar-width) fixed inset-y-0 z-50 transition-transform ease-in-out duration-300;
@apply fixed inset-y-0 z-50 flex w-(--sidebar-mobile-width) flex-col transition-transform duration-300 ease-in-out md:w-(--sidebar-width);
}
& + * {
@apply transition-[margin] ease-in-out duration-300;
@@ -50,73 +50,52 @@
nav {
> header,
> footer {
@apply flex flex-col gap-2 p-2;
@apply flex flex-col;
}
[role=separator] {
@apply border-sidebar-border mx-2 w-auto;
@apply w-auto;
}
> section {
@apply flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto;
@apply flex min-h-0 flex-1 flex-col overflow-y-auto;
> [role=group] {
@apply relative flex w-full min-w-0 flex-col p-2;
@apply relative flex w-full min-w-0 flex-col;
}
h3 {
@apply text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0;
@apply flex shrink-0 items-center outline-hidden transition-[margin,opacity] duration-200 ease-linear [&>svg]:shrink-0;
}
ul {
@apply flex w-full min-w-0 flex-col gap-1;
@apply flex w-full min-w-0 flex-col;
li {
@apply relative;
> a,
> button,
> details > summary {
@apply flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&[aria-current=page]]:bg-sidebar-accent [&[aria-current=page]]:font-medium [&[aria-current=page]]:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0;
&:not([data-variant]),
&[data-variant=default] {
@apply hover:bg-sidebar-accent hover:text-sidebar-accent-foreground;
}
&[data-variant=outline] {
@apply bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))];
}
&:not([data-size]),
&[data-size=default] {
@apply h-8 text-sm;
}
&[data-size=sm] {
@apply h-7 text-xs;
}
&[data-size=lg] {
@apply h-12 text-sm group-data-[collapsible=icon]:p-0!;
}
@apply flex w-full items-center overflow-hidden text-left outline-hidden transition-[width,height,padding] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:shrink-0;
}
> details {
&:not([open]) {
> summary {
&::after {
@apply -rotate-90;
@apply -rotate-90 rtl:rotate-90;
}
}
}
> summary {
&::after {
@apply content-[''] block size-3.5 bg-primary ml-auto transition-transform ease-linear;
@apply ms-auto block size-3.5 bg-current content-[''] transition-transform ease-linear;
@apply mask-[image:var(--chevron-down-icon)] mask-size-[1rem] mask-no-repeat mask-center;
}
}
&::details-content {
@apply px-3.5;
}
}
}
ul {
@apply border-sidebar-border flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5 w-full;
@apply flex min-w-0 flex-col;
}
}
}
}
}
}
+6
View File
@@ -0,0 +1,6 @@
/* Skeleton */
@layer components {
.skeleton {
@apply animate-pulse;
}
}
+3 -7
View File
@@ -1,12 +1,8 @@
/* Switch */
@layer components {
:is(.form, .field) input[type='checkbox'][role='switch'],
.field > input[type='checkbox'][role='switch'],
.input[type='checkbox'][role='switch'] {
@apply appearance-none focus-visible:border-ring focus-visible:ring-ring/50 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50;
@apply bg-input dark:bg-input/80 checked:bg-primary dark:checked:bg-primary;
@apply before:content-[''] before:pointer-events-none before:block before:size-4 before:rounded-full before:ring-0 before:transition-all;
@apply before:bg-background dark:before:bg-foreground;
@apply dark:checked:before:bg-primary-foreground checked:before:ms-3.5;
@apply inline-flex shrink-0 appearance-none items-center transition-all outline-none disabled:cursor-not-allowed disabled:opacity-50;
@apply before:pointer-events-none before:block before:content-[''] before:ring-0 before:transition-transform;
}
}
+15 -9
View File
@@ -1,28 +1,34 @@
/* Tables */
@layer components {
.table-container {
@apply relative w-full overflow-x-auto;
}
.table {
@apply w-full caption-bottom text-sm;
@apply w-full caption-bottom;
thead {
@apply [&_tr]:border-b;
}
tbody {
@apply [&_tr:last-child]:border-0;
}
tfoot {
@apply bg-muted/50 border-t font-medium [&>tr]:last:border-b-0;
@apply border-t font-medium [&>tr]:last:border-b-0;
}
tr {
@apply hover:bg-muted/50 border-b transition-colors;
@apply border-b transition-colors data-[state=selected]:bg-muted;
}
th {
@apply text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px];
@apply align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pe-0 [&>[role=checkbox]]:translate-y-[2px];
}
td {
@apply p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px];
}
caption {
@apply text-muted-foreground mt-4 text-sm;
@apply align-middle whitespace-nowrap [&:has([role=checkbox])]:pe-0 [&>[role=checkbox]]:translate-y-[2px];
}
}
}
+20 -11
View File
@@ -1,22 +1,31 @@
/* Tabs */
@layer components {
.tabs {
@apply flex flex-col gap-2;
[role='tablist'] {
@apply bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px];
@apply flex;
[role='tab'] {
@apply focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground dark:text-muted-foreground inline-flex h-[calc(100%_-_1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4;
&:has(> [role='tablist'][aria-orientation='horizontal']),
&:not(:has(> [role='tablist'][aria-orientation='vertical'])) {
@apply flex-col;
}
&[aria-selected='true'] {
@apply bg-background dark:text-foreground dark:border-input dark:bg-input/30 shadow-sm;
}
&:has(> [role='tablist'][aria-orientation='vertical']) {
@apply flex-row;
}
> [role='tablist'] {
@apply text-muted-foreground inline-flex w-fit items-center justify-center;
&[aria-orientation='vertical'] {
@apply h-fit flex-col;
}
> [role='tab'] {
@apply relative inline-flex flex-1 items-center justify-center whitespace-nowrap transition-all outline-none disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0;
}
}
[role='tabpanel'] {
> [role='tabpanel'] {
@apply flex-1 outline-none;
}
}
}
+2 -3
View File
@@ -1,8 +1,7 @@
/* Textarea */
@layer components {
:is(.form, .field) textarea,
.field > textarea,
.textarea {
@apply border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm;
@apply flex field-sizing-content min-h-16 w-full outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50;
}
}
+5 -6
View File
@@ -1,7 +1,7 @@
/* Toasts */
@layer components {
.toaster {
@apply fixed bottom-0 p-4 pointer-events-none z-50 w-full sm:max-w-90 flex flex-col-reverse;
@apply pointer-events-none fixed bottom-0 z-50 flex w-full flex-col-reverse p-4 sm:max-w-90;
&:not([data-align]),
&[data-align='end'] {
@@ -14,10 +14,10 @@
@apply left-1/2 -translate-x-1/2;
}
.toast {
@apply pointer-events-auto w-full mt-4 animate-[toast-up_0.3s_ease-in-out] grid grid-rows-[1fr] transition-[grid-template-rows,opacity,margin] duration-300 ease-in-out;
@apply pointer-events-auto mt-4 grid w-full grid-rows-[1fr] animate-[toast-up_0.3s_ease-in-out] transition-[grid-template-rows,opacity,margin] duration-300 ease-in-out;
.toast-content {
@apply text-popover-foreground text-[13px] bg-popover border shadow-lg rounded-lg overflow-hidden flex gap-2.5 p-3 items-center;
@apply flex items-center gap-2.5 overflow-hidden p-3 text-[13px];
svg {
@apply size-4 shrink-0;
@@ -31,7 +31,7 @@
}
}
footer {
@apply ml-auto flex flex-col gap-2;
@apply ms-auto flex flex-col gap-2;
[data-toast-action],
[data-toast-cancel] {
@@ -40,7 +40,7 @@
}
}
&[aria-hidden='true'] {
@apply grid-rows-[0fr] opacity-0 m-0 border-0 p-0 overflow-hidden;
@apply m-0 grid-rows-[0fr] overflow-hidden border-0 p-0 opacity-0;
.toast-content {
@apply border-0;
@@ -55,4 +55,3 @@
transform: translateY(100%);
}
}
+31 -5
View File
@@ -4,21 +4,26 @@
@apply relative;
&:before {
@apply absolute content-[attr(data-tooltip)] bg-foreground text-background z-[60] truncate max-w-xs w-fit rounded-md px-3 py-1.5 text-xs invisible opacity-0 scale-95 transition-all pointer-events-none;
@apply absolute z-[60] w-fit max-w-xs origin-(--transform-origin) scale-95 truncate invisible opacity-0 transition-all pointer-events-none content-[attr(data-tooltip)];
}
&:hover:before {
@apply visible opacity-100 scale-100;
}
&:focus-visible:not(:hover):before {
@apply hidden;
}
&:not([data-side]),
&[data-side='top'] {
@apply before:bottom-full before:mb-1.5 before:translate-y-2 hover:before:translate-y-0 ;
@apply before:bottom-full before:mb-1.5 before:translate-y-2 hover:before:translate-y-0;
}
&[data-side='bottom'] {
@apply before:top-full before:mt-1.5 before:-translate-y-2 hover:before:translate-y-0 ;
@apply before:top-full before:mt-1.5 before:-translate-y-2 hover:before:translate-y-0;
}
&:not([data-side]),
&[data-side='top'],
&[data-side='bottom'] {
@@ -30,17 +35,38 @@
}
&:not([data-align]),
&[data-align='center'] {
@apply before:left-1/2 before:-translate-x-1/2;
@apply before:left-1/2 before:-translate-x-1/2;
}
}
&[data-side='left'] {
@apply before:right-full before:mr-1.5 before:translate-x-2 hover:before:translate-x-0;
}
&[data-side='right'] {
@apply before:left-full before:ml-1.5 before:-translate-x-2 hover:before:translate-x-0;
}
&[data-side='inline-start'] {
@apply before:right-full before:mr-1.5 before:translate-x-2 hover:before:translate-x-0;
}
&[data-side='inline-start']:dir(rtl) {
@apply before:right-auto before:left-full before:mr-0 before:ml-1.5 before:-translate-x-2 hover:before:translate-x-0;
}
&[data-side='inline-end'] {
@apply before:left-full before:ml-1.5 before:-translate-x-2 hover:before:translate-x-0;
}
&[data-side='inline-end']:dir(rtl) {
@apply before:left-auto before:right-full before:ml-0 before:mr-1.5 before:translate-x-2 hover:before:translate-x-0;
}
&[data-side='left'],
&[data-side='right'] {
&[data-side='right'],
&[data-side='inline-start'],
&[data-side='inline-end'] {
&[data-align='start'] {
@apply before:top-0;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+102
View File
@@ -0,0 +1,102 @@
{#
Renders an input-first combobox component.
#}
{% macro combobox(
id=None,
selected=None,
name=None,
multiple=false,
placeholder=None,
close_on_select=false,
auto_highlight=false,
main_attrs={},
input_attrs={},
popover_attrs={},
listbox_attrs={},
hidden_input_attrs={}
) %}
{% set id = id or ("combobox-" + (range(100000, 999999) | random | string)) %}
{% if selected is defined and selected is iterable and selected is not string %}
{% set selected_values = selected %}
{% elif selected is defined and selected %}
{% set selected_values = [selected] %}
{% else %}
{% set selected_values = [] %}
{% endif %}
{% set hidden_value = (selected_values | tojson if multiple else (selected_values[0] if selected_values else '')) %}
<div
id="{{ id }}"
class="combobox {{ main_attrs.class }}"
{% if multiple %}data-multiple="true"{% endif %}
{% if multiple and close_on_select %}data-close-on-select="true"{% endif %}
{% if auto_highlight %}data-auto-highlight="true"{% endif %}
{% for key, value in main_attrs.items() %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
{% if multiple %}
<div class="combobox-chips {{ input_attrs.class }}">
<input
type="text"
role="combobox"
placeholder="{{ placeholder or '' }}"
autocomplete="off"
autocorrect="off"
spellcheck="false"
aria-autocomplete="list"
aria-expanded="false"
aria-controls="{{ id }}-listbox"
{% for key, value in input_attrs.items() %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
</div>
{% else %}
<input
type="text"
role="combobox"
placeholder="{{ placeholder or '' }}"
autocomplete="off"
autocorrect="off"
spellcheck="false"
aria-autocomplete="list"
aria-expanded="false"
aria-controls="{{ id }}-listbox"
class="{{ input_attrs.class }}"
{% for key, value in input_attrs.items() %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
<svg aria-hidden="true" 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" class="combobox-trigger-icon"><path d="m6 9 6 6 6-6"/></svg>
{% endif %}
<div
id="{{ id }}-popover"
data-popover
aria-hidden="true"
{% for key, value in popover_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
>
<div
role="listbox"
id="{{ id }}-listbox"
aria-orientation="vertical"
{% if multiple %}aria-multiselectable="true"{% endif %}
{% for key, value in listbox_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
>
{{ caller() if caller else '' }}
</div>
</div>
<input
type="hidden"
{% if name %}name="{{ name }}"{% endif %}
value='{{ hidden_value }}'
{% for key, value in hidden_input_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
>
</div>
{% endmacro %}
+2 -2
View File
@@ -89,7 +89,7 @@
{% set id = id or ("command-dialog-" ~ range(100000, 999999) | random | string) %}
<dialog
id="{{ id }}"
class="dialog command-dialog {{ dialog_attrs.class }}"
class="command-dialog {{ dialog_attrs.class }}"
aria-label="Command menu"
{% if open %}open{% endif %}
onclick="if (event.target === this) this.close()"
@@ -132,7 +132,7 @@
{{ caller() if caller }}
{% endif %}
</div>
<button type="button" aria-label="Close dialog" onclick="this.closest('dialog').close()">
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
<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" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
</button>
</div>
+1 -1
View File
@@ -83,7 +83,7 @@
</footer>
{% endif %}
{% if close_button %}
<button type="button" aria-label="Close dialog" onclick="this.closest('dialog').close()">
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
<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" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
</button>
{% endif %}
+3 -29
View File
@@ -1,5 +1,5 @@
{#
Renders a select or combobox component.
Renders a select component.
@param id {string} [optional] - Unique identifier for the select component.
@param selected {string|array} [optional] - The initially selected value(s).
@@ -12,8 +12,6 @@
@param popover_attrs {object} [optional] - Additional HTML attributes for the popover content div.
@param listbox_attrs {object} [optional] - Additional HTML attributes for the listbox div.
@param input_attrs {object} [optional] - Additional HTML attributes for the hidden input.
@param search_placeholder {string} [optional] [default="Search entries..."] - Placeholder text for the search input (combobox only).
@param is_combobox {boolean} [optional] [default=false] - Renders a combobox with search functionality if true.
#}
{% macro select(
id=None,
@@ -27,9 +25,7 @@
trigger_attrs={},
popover_attrs={},
listbox_attrs={},
input_attrs={},
search_placeholder="Search entries...",
is_combobox=false
input_attrs={}
) %}
{% set id = id or ("select-" + (range(100000, 999999) | random | string)) %}
@@ -85,7 +81,7 @@
>
<button
type="button"
class="btn-outline {{ trigger_attrs.class }}"
class="{{ trigger_attrs.class }}"
id="{{ id }}-trigger"
aria-haspopup="listbox"
aria-expanded="false"
@@ -95,11 +91,7 @@
{% endfor %}
>
<span class="truncate">{{ default_label }}</span>
{% if is_combobox %}
<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" class="lucide lucide-chevrons-up-down-icon lucide-chevrons-up-down text-muted-foreground opacity-50 shrink-0"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg>
{% else %}
<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" class="lucide lucide-chevron-down-icon lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6"/></svg>
{% endif %}
</button>
<div
id="{{ id }}-popover"
@@ -109,24 +101,6 @@
{{ key }}="{{ value }}"
{% endfor %}
>
{% if is_combobox %}
<header>
<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" class="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<input
type="text"
value=""
placeholder="{{ search_placeholder }}"
autocomplete="off"
autocorrect="off"
spellcheck="false"
aria-autocomplete="list"
role="combobox"
aria-expanded="false"
aria-controls="{{ id }}-listbox"
aria-labelledby="{{ id }}-trigger"
>
</header>
{% endif %}
<div
role="listbox"
id="{{ id }}-listbox"
+2 -2
View File
@@ -117,7 +117,7 @@
>
<summary aria-controls="submenu-{{ item_id }}-content">
{% if item.icon %}{{ item.icon | safe }}{% endif %}
{{ item.label | safe }}
<span>{{ item.label | safe }}</span>
</summary>
<ul id="submenu-{{ item_id }}-content">
{{ render_sidebar_content(item["items"], item_id) if item["items"] }}
@@ -141,4 +141,4 @@
</li>
{% endif %}
{% endfor %}
{% endmacro %}
{% endmacro %}
+6 -4
View File
@@ -11,6 +11,7 @@
- panel_attrs {object} [optional]: Additional HTML attributes for the tab panel div.
@param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
@param tablist_attrs {object} [optional] - Additional HTML attributes for the tablist nav element.
Set aria-orientation="vertical" for vertical tabs or data-variant="line" for line tabs.
@param default_tab_index {number} [optional] [default=1] - The 1-based index of the tab to be active initially.
#}
{% macro tabs(
@@ -22,6 +23,7 @@
)
%}
{% set id = id or ("tabs-" + (range(100000, 999999) | random | string)) %}
{% set orientation = tablist_attrs['aria-orientation'] or 'horizontal' %}
<div
class="tabs {{ main_attrs.class }}"
id="{{ id }}"
@@ -31,9 +33,9 @@
>
<nav
role="tablist"
aria-orientation="horizontal"
aria-orientation="{{ orientation }}"
{% for key, value in tablist_attrs.items() %}
{{ key }}="{{ value }}"
{% if key != 'aria-orientation' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
{% for tabset in tabsets %}
@@ -43,7 +45,7 @@
id="{{ id }}-tab-{{ loop.index }}"
aria-controls="{{ id }}-panel-{{ loop.index }}"
aria-selected="{{ 'true' if loop.index == default_tab_index else 'false' }}"
tabindex="0"
tabindex="{{ '0' if loop.index == default_tab_index and not tabset.tab_attrs.disabled and tabset.tab_attrs['aria-disabled'] != 'true' else '-1' }}"
{% if tabset.tab_attrs %}
{% for key, value in tabset.tab_attrs.items() %}
{{ key }}="{{ value }}"
@@ -75,4 +77,4 @@
{% endif %}
{% endfor %}
</div>
{% endmacro %}
{% endmacro %}
+323
View File
@@ -0,0 +1,323 @@
(() => {
const initCombobox = (combobox) => {
const input = combobox.querySelector(':scope > input[role="combobox"], :scope > .combobox-chips input[role="combobox"]');
const chips = combobox.querySelector(':scope > .combobox-chips');
const popover = combobox.querySelector(':scope > [data-popover]');
const listbox = popover ? popover.querySelector('[role="listbox"]') : null;
const hiddenInput = combobox.querySelector(':scope > input[type="hidden"]');
if (!input || !popover || !listbox || !hiddenInput) {
const missing = [];
if (!input) missing.push('input');
if (!popover) missing.push('popover');
if (!listbox) missing.push('listbox');
if (!hiddenInput) missing.push('hidden input');
console.error(`Combobox initialisation failed. Missing element(s): ${missing.join(', ')}`, combobox);
return;
}
const allOptions = Array.from(listbox.querySelectorAll('[role="option"]'));
const options = allOptions.filter(option => option.getAttribute('aria-disabled') !== 'true');
const isMultiple = listbox.getAttribute('aria-multiselectable') === 'true' || combobox.dataset.multiple === 'true';
const closeOnSelect = combobox.dataset.closeOnSelect === 'true';
const autoHighlight = combobox.dataset.autoHighlight === 'true';
const selectedOptions = isMultiple ? new Set() : null;
let visibleOptions = [...options];
let activeIndex = -1;
const getValue = option => option.dataset.value ?? option.textContent.trim();
const getLabel = option => option.dataset.label || option.textContent.trim();
const setActiveOption = (index) => {
if (activeIndex > -1 && options[activeIndex]) {
options[activeIndex].classList.remove('active');
}
activeIndex = index;
if (activeIndex > -1) {
const activeOption = options[activeIndex];
activeOption.classList.add('active');
if (!activeOption.id) activeOption.id = `${listbox.id || combobox.id || 'combobox'}-option-${activeIndex}`;
input.setAttribute('aria-activedescendant', activeOption.id);
} else {
input.removeAttribute('aria-activedescendant');
}
};
const syncEmptyState = () => {
popover.dataset.empty = String(visibleOptions.length === 0);
};
const filterOptions = ({ preserveActive = false } = {}) => {
const previousActive = activeIndex > -1 ? options[activeIndex] : null;
const search = input.value.trim().toLowerCase();
visibleOptions = [];
allOptions.forEach(option => {
if (option.hasAttribute('data-force')) {
option.setAttribute('aria-hidden', 'false');
if (options.includes(option)) visibleOptions.push(option);
return;
}
const optionText = (option.dataset.filter || option.dataset.label || option.textContent).trim().toLowerCase();
const keywords = (option.dataset.keywords || '').toLowerCase().split(/[\s,]+/).filter(Boolean);
const matches = !search || optionText.includes(search) || keywords.some(keyword => keyword.includes(search));
option.setAttribute('aria-hidden', String(!matches));
if (matches && options.includes(option)) visibleOptions.push(option);
});
if (preserveActive && previousActive && visibleOptions.includes(previousActive)) {
setActiveOption(options.indexOf(previousActive));
} else {
setActiveOption(autoHighlight && visibleOptions.length > 0 ? options.indexOf(visibleOptions[0]) : -1);
}
syncEmptyState();
};
const openPopover = () => {
if (popover.getAttribute('aria-hidden') === 'false') return;
document.dispatchEvent(new CustomEvent('basecoat:popover', {
detail: { source: combobox }
}));
filterOptions();
popover.setAttribute('aria-hidden', 'false');
input.setAttribute('aria-expanded', 'true');
};
const closePopover = (focusInput = false) => {
if (popover.getAttribute('aria-hidden') === 'true') return;
popover.setAttribute('aria-hidden', 'true');
input.setAttribute('aria-expanded', 'false');
setActiveOption(-1);
if (focusInput) input.focus();
};
const renderChips = () => {
if (!chips) return;
chips.querySelectorAll('.combobox-chip').forEach(chip => chip.remove());
const selected = options.filter(option => selectedOptions.has(option));
selected.forEach(option => {
const chip = document.createElement('span');
chip.className = 'combobox-chip';
chip.dataset.value = getValue(option);
chip.textContent = getLabel(option);
const remove = document.createElement('button');
remove.type = 'button';
remove.className = 'combobox-chip-remove';
remove.setAttribute('aria-label', `Remove ${getLabel(option)}`);
remove.innerHTML = '<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"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>';
remove.addEventListener('click', () => deselect(getValue(option)));
chip.appendChild(remove);
chips.insertBefore(chip, input);
});
};
const updateValue = (optionOrOptions, triggerEvent = true) => {
let value;
if (isMultiple) {
const selected = Array.isArray(optionOrOptions) ? optionOrOptions : [];
selectedOptions.clear();
selected.forEach(option => selectedOptions.add(option));
options.forEach(option => {
if (selectedOptions.has(option)) {
option.setAttribute('aria-selected', 'true');
} else {
option.removeAttribute('aria-selected');
}
});
value = options.filter(option => selectedOptions.has(option)).map(getValue);
hiddenInput.value = JSON.stringify(value);
input.value = '';
renderChips();
filterOptions({ preserveActive: true });
} else {
const option = optionOrOptions;
if (!option) return;
options.forEach(opt => {
if (opt === option) {
opt.setAttribute('aria-selected', 'true');
} else {
opt.removeAttribute('aria-selected');
}
});
value = getValue(option);
hiddenInput.value = value;
input.value = getLabel(option);
}
if (triggerEvent) {
combobox.dispatchEvent(new CustomEvent('change', {
detail: { value },
bubbles: true
}));
}
};
const select = (value) => {
const option = options.find(opt => getValue(opt) === value);
if (!option) return;
if (isMultiple) {
const selected = new Set(selectedOptions);
selected.add(option);
updateValue(options.filter(opt => selected.has(opt)));
if (closeOnSelect) closePopover(true);
} else {
updateValue(option);
closePopover(true);
}
};
const deselect = (value) => {
if (!isMultiple) return;
const option = options.find(opt => getValue(opt) === value);
if (!option || !selectedOptions.has(option)) return;
const selected = new Set(selectedOptions);
selected.delete(option);
updateValue(options.filter(opt => selected.has(opt)));
};
const toggle = (value) => {
if (!isMultiple) return;
const option = options.find(opt => getValue(opt) === value);
if (!option) return;
selectedOptions.has(option) ? deselect(value) : select(value);
};
const handleKeydown = (event) => {
if (!['ArrowDown', 'ArrowUp', 'Enter', 'Home', 'End', 'Escape', 'Backspace'].includes(event.key)) return;
const isOpen = popover.getAttribute('aria-hidden') === 'false';
if (event.key === 'Backspace' && isMultiple && input.value === '') {
const selected = options.filter(option => selectedOptions.has(option));
const last = selected[selected.length - 1];
if (last) deselect(getValue(last));
return;
}
if (event.key === 'Escape') {
closePopover(true);
return;
}
if (!isOpen && ['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) {
event.preventDefault();
openPopover();
}
if (popover.getAttribute('aria-hidden') === 'true') return;
if (event.key === 'Enter') {
if (activeIndex > -1) {
event.preventDefault();
const option = options[activeIndex];
isMultiple ? toggle(getValue(option)) : select(getValue(option));
}
return;
}
if (!['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key) || visibleOptions.length === 0) return;
event.preventDefault();
const currentVisibleIndex = activeIndex > -1 ? visibleOptions.indexOf(options[activeIndex]) : -1;
let nextVisibleIndex = currentVisibleIndex;
if (event.key === 'ArrowDown') nextVisibleIndex = Math.min(currentVisibleIndex + 1, visibleOptions.length - 1);
if (event.key === 'ArrowUp') nextVisibleIndex = currentVisibleIndex <= 0 ? 0 : currentVisibleIndex - 1;
if (event.key === 'Home') nextVisibleIndex = 0;
if (event.key === 'End') nextVisibleIndex = visibleOptions.length - 1;
const nextOption = visibleOptions[nextVisibleIndex];
setActiveOption(options.indexOf(nextOption));
nextOption.scrollIntoView({ block: 'nearest' });
};
input.addEventListener('focus', openPopover);
input.addEventListener('click', openPopover);
input.addEventListener('input', () => {
openPopover();
filterOptions();
if (!isMultiple) {
hiddenInput.value = '';
options.forEach(option => option.removeAttribute('aria-selected'));
}
});
input.addEventListener('keydown', handleKeydown);
listbox.addEventListener('mousemove', (event) => {
const option = event.target.closest('[role="option"]');
if (option && visibleOptions.includes(option)) setActiveOption(options.indexOf(option));
});
listbox.addEventListener('click', (event) => {
const option = event.target.closest('[role="option"]');
if (!option || !options.includes(option)) return;
isMultiple ? toggle(getValue(option)) : select(getValue(option));
if (isMultiple && !closeOnSelect) input.focus();
});
document.addEventListener('click', (event) => {
if (!combobox.contains(event.target)) closePopover(false);
});
document.addEventListener('basecoat:popover', (event) => {
if (event.detail.source !== combobox) closePopover(false);
});
if (isMultiple) {
let initialValues = [];
try {
initialValues = JSON.parse(hiddenInput.value || '[]');
} catch (_) {
initialValues = [];
}
if (!Array.isArray(initialValues)) initialValues = [];
const ariaSelected = options.filter(option => option.getAttribute('aria-selected') === 'true').map(getValue);
const values = initialValues.length ? initialValues : ariaSelected;
updateValue(options.filter(option => values.includes(getValue(option))), false);
} else {
const initialOption = options.find(option => getValue(option) === hiddenInput.value || option.getAttribute('aria-selected') === 'true');
if (initialOption) updateValue(initialOption, false);
}
popover.setAttribute('aria-hidden', 'true');
input.setAttribute('aria-expanded', 'false');
Object.defineProperty(combobox, 'value', {
get: () => isMultiple ? options.filter(option => selectedOptions.has(option)).map(getValue) : hiddenInput.value,
set: (value) => {
if (isMultiple) {
const values = Array.isArray(value) ? value : (value == null ? [] : [value]);
updateValue(options.filter(option => values.includes(getValue(option))));
} else {
select(value);
}
}
});
combobox.select = select;
combobox.selectByValue = select;
if (isMultiple) {
combobox.deselect = deselect;
combobox.toggle = toggle;
combobox.selectAll = () => updateValue(options);
combobox.selectNone = () => updateValue([]);
}
combobox.dataset.comboboxInitialized = true;
combobox.dispatchEvent(new CustomEvent('basecoat:initialized'));
};
if (window.basecoat) {
window.basecoat.register('combobox', '.combobox:not([data-combobox-initialized])', initCombobox);
}
})();
+11 -8
View File
@@ -16,6 +16,9 @@
let menuItems = [];
let activeIndex = -1;
const isDisabled = (item) =>
item.hasAttribute('disabled') || item.getAttribute('aria-disabled') === 'true';
const closePopover = (focusOnTrigger = true) => {
if (trigger.getAttribute('aria-expanded') === 'false') return;
trigger.setAttribute('aria-expanded', 'false');
@@ -36,10 +39,7 @@
trigger.setAttribute('aria-expanded', 'true');
popover.setAttribute('aria-hidden', 'false');
menuItems = Array.from(menu.querySelectorAll('[role^="menuitem"]')).filter(item =>
!item.hasAttribute('disabled') &&
item.getAttribute('aria-disabled') !== 'true'
);
menuItems = Array.from(menu.querySelectorAll('[role^="menuitem"]')).filter(item => !isDisabled(item));
if (menuItems.length > 0 && initialSelection) {
if (initialSelection === 'first') {
@@ -58,7 +58,9 @@
if (activeIndex > -1 && menuItems[activeIndex]) {
const activeItem = menuItems[activeIndex];
activeItem.classList.add('active');
trigger.setAttribute('aria-activedescendant', activeItem.id);
if (activeItem.id) {
trigger.setAttribute('aria-activedescendant', activeItem.id);
}
} else {
trigger.removeAttribute('aria-activedescendant');
}
@@ -131,7 +133,7 @@
menu.addEventListener('mousemove', (event) => {
const menuItem = event.target.closest('[role^="menuitem"]');
if (menuItem && menuItems.includes(menuItem)) {
if (menuItem && !isDisabled(menuItem) && menuItems.includes(menuItem)) {
const index = menuItems.indexOf(menuItem);
if (index !== activeIndex) {
setActiveItem(index);
@@ -144,7 +146,8 @@
});
menu.addEventListener('click', (event) => {
if (event.target.closest('[role^="menuitem"]')) {
const menuItem = event.target.closest('[role^="menuitem"]');
if (menuItem && !isDisabled(menuItem)) {
closePopover();
}
});
@@ -168,4 +171,4 @@
if (window.basecoat) {
window.basecoat.register('dropdown-menu', '.dropdown-menu:not([data-dropdown-menu-initialized])', initDropdownMenu);
}
})();
})();
+21
View File
@@ -0,0 +1,21 @@
(() => {
function updateRange(element) {
const min = parseFloat(element.min || '0');
const max = parseFloat(element.max || '100');
const value = parseFloat(element.value || '0');
const percent = max === min ? 0 : ((value - min) / (max - min)) * 100;
element.style.setProperty('--slider-value', `${percent}%`);
}
function initRange(element) {
if (element.dataset.rangeInitialized) return;
updateRange(element);
element.addEventListener('input', () => updateRange(element));
element.dataset.rangeInitialized = 'true';
}
if (window.basecoat) {
window.basecoat.register('range', 'input[type="range"]:not([data-range-initialized])', initRange);
}
})();
+1 -72
View File
@@ -5,7 +5,6 @@
const popover = selectComponent.querySelector(':scope > [data-popover]');
const listbox = popover ? popover.querySelector('[role="listbox"]') : null;
const input = selectComponent.querySelector(':scope > input[type="hidden"]');
const filter = selectComponent.querySelector('header input[type="text"]');
if (!trigger || !popover || !listbox || !input) {
const missing = [];
@@ -48,11 +47,6 @@
}
};
const hasTransition = () => {
const style = getComputedStyle(popover);
return parseFloat(style.transitionDuration) > 0 || parseFloat(style.transitionDelay) > 0;
};
const updateValue = (optionOrOptions, triggerEvent = true) => {
let value;
@@ -101,20 +95,6 @@
const closePopover = (focusOnTrigger = true) => {
if (popover.getAttribute('aria-hidden') === 'true') return;
if (filter) {
const resetFilter = () => {
filter.value = '';
visibleOptions = [...options];
allOptions.forEach(opt => opt.setAttribute('aria-hidden', 'false'));
};
if (hasTransition()) {
popover.addEventListener('transitionend', resetFilter, { once: true });
} else {
resetFilter();
}
}
if (focusOnTrigger) trigger.focus();
popover.setAttribute('aria-hidden', 'true');
trigger.setAttribute('aria-expanded', 'false');
@@ -161,39 +141,6 @@
toggleMultipleValue(option);
};
if (filter) {
const filterOptions = () => {
const searchTerm = filter.value.trim().toLowerCase();
setActiveOption(-1);
visibleOptions = [];
allOptions.forEach(option => {
if (option.hasAttribute('data-force')) {
option.setAttribute('aria-hidden', 'false');
if (options.includes(option)) {
visibleOptions.push(option);
}
return;
}
const optionText = (option.dataset.filter || option.textContent).trim().toLowerCase();
const keywordList = (option.dataset.keywords || '')
.toLowerCase()
.split(/[\s,]+/)
.filter(Boolean);
const matchesKeyword = keywordList.some(keyword => keyword.includes(searchTerm));
const matches = optionText.includes(searchTerm) || matchesKeyword;
option.setAttribute('aria-hidden', String(!matches));
if (matches && options.includes(option)) {
visibleOptions.push(option);
}
});
};
filter.addEventListener('input', filterOptions);
}
// Initialization
if (isMultiple) {
const ariaSelected = options.filter(opt => opt.getAttribute('aria-selected') === 'true');
@@ -315,25 +262,11 @@
});
trigger.addEventListener('keydown', handleKeyNavigation);
if (filter) {
filter.addEventListener('keydown', handleKeyNavigation);
}
const openPopover = () => {
document.dispatchEvent(new CustomEvent('basecoat:popover', {
detail: { source: selectComponent }
}));
if (filter) {
if (hasTransition()) {
popover.addEventListener('transitionend', () => {
filter.focus();
}, { once: true });
} else {
filter.focus();
}
}
popover.setAttribute('aria-hidden', 'false');
trigger.setAttribute('aria-expanded', 'true');
@@ -366,11 +299,7 @@
closePopover();
} else {
setActiveOption(options.indexOf(option));
if (filter) {
filter.focus();
} else {
trigger.focus();
}
trigger.focus();
}
} else {
if (input.value !== getValue(option)) {
+24 -6
View File
@@ -6,7 +6,12 @@
const tabs = Array.from(tablist.querySelectorAll('[role="tab"]'));
const panels = tabs.map(tab => document.getElementById(tab.getAttribute('aria-controls'))).filter(Boolean);
const isDisabled = (tab) => tab.disabled || tab.getAttribute('aria-disabled') === 'true';
const getEnabledTabs = () => tabs.filter(tab => !isDisabled(tab));
const selectTab = (tabToSelect) => {
if (isDisabled(tabToSelect)) return;
tabs.forEach((tab, index) => {
tab.setAttribute('aria-selected', 'false');
tab.setAttribute('tabindex', '-1');
@@ -29,20 +34,33 @@
if (!tabs.includes(currentTab)) return;
let nextTab;
const currentIndex = tabs.indexOf(currentTab);
const enabledTabs = getEnabledTabs();
const currentIndex = enabledTabs.indexOf(currentTab);
const orientation = tablist.getAttribute('aria-orientation') || 'horizontal';
if (currentIndex === -1) return;
switch (event.key) {
case 'ArrowRight':
nextTab = tabs[(currentIndex + 1) % tabs.length];
if (orientation !== 'horizontal') return;
nextTab = enabledTabs[(currentIndex + 1) % enabledTabs.length];
break;
case 'ArrowLeft':
nextTab = tabs[(currentIndex - 1 + tabs.length) % tabs.length];
if (orientation !== 'horizontal') return;
nextTab = enabledTabs[(currentIndex - 1 + enabledTabs.length) % enabledTabs.length];
break;
case 'ArrowDown':
if (orientation !== 'vertical') return;
nextTab = enabledTabs[(currentIndex + 1) % enabledTabs.length];
break;
case 'ArrowUp':
if (orientation !== 'vertical') return;
nextTab = enabledTabs[(currentIndex - 1 + enabledTabs.length) % enabledTabs.length];
break;
case 'Home':
nextTab = tabs[0];
nextTab = enabledTabs[0];
break;
case 'End':
nextTab = tabs[tabs.length - 1];
nextTab = enabledTabs[enabledTabs.length - 1];
break;
default:
return;
@@ -60,4 +78,4 @@
if (window.basecoat) {
window.basecoat.register('tabs', '.tabs:not([data-tabs-initialized])', initTabs);
}
})();
})();
+114
View File
@@ -0,0 +1,114 @@
{#
Renders an input-first combobox component.
@param id {string} [optional] - Unique identifier for the combobox component.
@param selected {string|array} [optional] - The initially selected value(s).
@param name {string} [optional] - The name attribute for the hidden input storing the selected value.
@param multiple {boolean} [optional] [default=false] - Enables multiple selection mode with chips.
@param placeholder {string} [optional] - Placeholder text for the visible input.
@param close_on_select {boolean} [optional] [default=false] - Closes the popover when selecting an option in multiple mode.
@param auto_highlight {boolean} [optional] [default=false] - Highlights the first matching option while filtering.
@param main_attrs {object} [optional] - Additional HTML attributes for the root div.
@param input_attrs {object} [optional] - Additional HTML attributes for the visible input.
@param popover_attrs {object} [optional] - Additional HTML attributes for the popover div.
@param listbox_attrs {object} [optional] - Additional HTML attributes for the listbox div.
@param hidden_input_attrs {object} [optional] - Additional HTML attributes for the hidden input.
#}
{% macro combobox(
id=None,
selected=None,
name=None,
multiple=false,
placeholder=None,
close_on_select=false,
auto_highlight=false,
main_attrs={},
input_attrs={},
popover_attrs={},
listbox_attrs={},
hidden_input_attrs={}
) %}
{% set id = id or ("combobox-" + (range(100000, 999999) | random | string)) %}
{% set selected_values = [] %}
{% if selected is defined and selected is iterable and selected is not string %}
{% set selected_values = selected %}
{% elif selected is defined and selected %}
{% set selected_values = [selected] %}
{% endif %}
{% set hidden_value = (selected_values | dump if multiple else (selected_values[0] if selected_values[0] else '')) %}
<div
id="{{ id }}"
class="combobox {{ main_attrs.class }}"
{% if multiple %}data-multiple="true"{% endif %}
{% if multiple and close_on_select %}data-close-on-select="true"{% endif %}
{% if auto_highlight %}data-auto-highlight="true"{% endif %}
{% for key, value in main_attrs %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
{% if multiple %}
<div class="combobox-chips {{ input_attrs.class }}">
<input
type="text"
role="combobox"
placeholder="{{ placeholder or '' }}"
autocomplete="off"
autocorrect="off"
spellcheck="false"
aria-autocomplete="list"
aria-expanded="false"
aria-controls="{{ id }}-listbox"
{% for key, value in input_attrs %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
</div>
{% else %}
<input
type="text"
role="combobox"
placeholder="{{ placeholder or '' }}"
autocomplete="off"
autocorrect="off"
spellcheck="false"
aria-autocomplete="list"
aria-expanded="false"
aria-controls="{{ id }}-listbox"
class="{{ input_attrs.class }}"
{% for key, value in input_attrs %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
<svg aria-hidden="true" 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" class="combobox-trigger-icon"><path d="m6 9 6 6 6-6"/></svg>
{% endif %}
<div
id="{{ id }}-popover"
data-popover
aria-hidden="true"
{% for key, value in popover_attrs %}
{{ key }}="{{ value }}"
{% endfor %}
>
<div
role="listbox"
id="{{ id }}-listbox"
aria-orientation="vertical"
{% if multiple %}aria-multiselectable="true"{% endif %}
{% for key, value in listbox_attrs %}
{{ key }}="{{ value }}"
{% endfor %}
>
{{ caller() if caller else '' }}
</div>
</div>
<input
type="hidden"
{% if name %}name="{{ name }}"{% endif %}
value='{{ hidden_value }}'
{% for key, value in hidden_input_attrs %}
{{ key }}="{{ value }}"
{% endfor %}
>
</div>
{% endmacro %}
+1 -1
View File
@@ -132,7 +132,7 @@
{{ caller() if caller }}
{% endif %}
</div>
<button type="button" aria-label="Close dialog" onclick="this.closest('dialog').close()">
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
<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" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
</button>
</div>
+2 -2
View File
@@ -83,10 +83,10 @@
</footer>
{% endif %}
{% if close_button %}
<button type="button" aria-label="Close dialog" onclick="this.closest('dialog').close()">
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
<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" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
</button>
{% endif %}
</div>
</dialog>
{% endmacro %}
{% endmacro %}
+3 -29
View File
@@ -1,5 +1,5 @@
{#
Renders a select or combobox component.
Renders a select component.
@param id {string} [optional] - Unique identifier for the select component.
@param selected {string|array} [optional] - The initially selected value(s).
@@ -12,8 +12,6 @@
@param popover_attrs {object} [optional] - Additional HTML attributes for the popover content div.
@param listbox_attrs {object} [optional] - Additional HTML attributes for the listbox div.
@param input_attrs {object} [optional] - Additional HTML attributes for the hidden input.
@param search_placeholder {string} [optional] [default="Search entries..."] - Placeholder text for the search input (combobox only).
@param is_combobox {boolean} [optional] [default=false] - Renders a combobox with search functionality if true.
#}
{% macro select(
id=None,
@@ -27,9 +25,7 @@
trigger_attrs={},
popover_attrs={},
listbox_attrs={},
input_attrs={},
search_placeholder="Search entries...",
is_combobox=false
input_attrs={}
) %}
{% set id = id or ("select-" + (range(100000, 999999) | random | string)) %}
@@ -85,7 +81,7 @@
>
<button
type="button"
class="btn-outline {{ trigger_attrs.class }}"
class="{{ trigger_attrs.class }}"
id="{{ id }}-trigger"
aria-haspopup="listbox"
aria-expanded="false"
@@ -95,11 +91,7 @@
{% endfor %}
>
<span class="truncate">{{ default_label }}</span>
{% if is_combobox %}
<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" class="lucide lucide-chevrons-up-down-icon lucide-chevrons-up-down text-muted-foreground opacity-50 shrink-0"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg>
{% else %}
<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" class="lucide lucide-chevron-down-icon lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6"/></svg>
{% endif %}
</button>
<div
id="{{ id }}-popover"
@@ -109,24 +101,6 @@
{{ key }}="{{ value }}"
{% endfor %}
>
{% if is_combobox %}
<header>
<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" class="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<input
type="text"
value=""
placeholder="{{ search_placeholder }}"
autocomplete="off"
autocorrect="off"
spellcheck="false"
aria-autocomplete="list"
role="combobox"
aria-expanded="false"
aria-controls="{{ id }}-listbox"
aria-labelledby="{{ id }}-trigger"
>
</header>
{% endif %}
<div
role="listbox"
id="{{ id }}-listbox"
+2 -2
View File
@@ -117,7 +117,7 @@
>
<summary aria-controls="submenu-{{ item_id }}-content">
{% if item.icon %}{{ item.icon | safe }}{% endif %}
{{ item.label | safe }}
<span>{{ item.label | safe }}</span>
</summary>
<ul id="submenu-{{ item_id }}-content">
{{ render_sidebar_content(item.items, item_id) if item.items }}
@@ -141,4 +141,4 @@
</li>
{% endif %}
{% endfor %}
{% endmacro %}
{% endmacro %}
+6 -4
View File
@@ -11,6 +11,7 @@
- panel_attrs {object} [optional]: Additional HTML attributes for the tab panel div.
@param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
@param tablist_attrs {object} [optional] - Additional HTML attributes for the tablist nav element.
Set aria-orientation="vertical" for vertical tabs or data-variant="line" for line tabs.
@param default_tab_index {number} [optional] [default=1] - The 1-based index of the tab to be active initially.
#}
{% macro tabs(
@@ -22,6 +23,7 @@
)
%}
{% set id = id or ("tabs-" + (range(100000, 999999) | random | string)) %}
{% set orientation = tablist_attrs['aria-orientation'] or 'horizontal' %}
<div
class="tabs {{ main_attrs.class }}"
id="{{ id }}"
@@ -31,9 +33,9 @@
>
<nav
role="tablist"
aria-orientation="horizontal"
aria-orientation="{{ orientation }}"
{% for key, value in tablist_attrs %}
{{ key }}="{{ value }}"
{% if key != 'aria-orientation' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
{% for tabset in tabsets %}
@@ -43,7 +45,7 @@
id="{{ id }}-tab-{{ loop.index }}"
aria-controls="{{ id }}-panel-{{ loop.index }}"
aria-selected="{{ 'true' if loop.index == default_tab_index else 'false' }}"
tabindex="0"
tabindex="{{ '0' if loop.index == default_tab_index and not tabset.tab_attrs.disabled and tabset.tab_attrs['aria-disabled'] != 'true' else '-1' }}"
{% if tabset.tab_attrs %}
{% for key, value in tabset.tab_attrs %}
{{ key }}="{{ value }}"
@@ -75,4 +77,4 @@
{% endif %}
{% endfor %}
</div>
{% endmacro %}
{% endmacro %}