@entifix/react-controls 0.18.2 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/i18n/catalog/en.d.ts.map +1 -1
- package/dist/i18n/catalog/en.js +13 -1
- package/dist/i18n/catalog/en.js.map +1 -1
- package/dist/i18n/catalog/es.d.ts +12 -0
- package/dist/i18n/catalog/es.d.ts.map +1 -1
- package/dist/i18n/catalog/es.js +13 -1
- package/dist/i18n/catalog/es.js.map +1 -1
- package/dist/i18n/catalog/index.d.ts +24 -0
- package/dist/i18n/catalog/index.d.ts.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/primitives.d.ts +7 -0
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +7 -0
- package/dist/primitives.js.map +1 -1
- package/dist/theme/theme-context.d.ts +23 -7
- package/dist/theme/theme-context.d.ts.map +1 -1
- package/dist/theme/theme-context.js +76 -14
- package/dist/theme/theme-context.js.map +1 -1
- package/dist/theme/theme-script.d.ts +4 -3
- package/dist/theme/theme-script.d.ts.map +1 -1
- package/dist/theme/theme-script.js +8 -3
- package/dist/theme/theme-script.js.map +1 -1
- package/dist/theme/types.d.ts +19 -3
- package/dist/theme/types.d.ts.map +1 -1
- package/dist/theme/types.js.map +1 -1
- package/dist/ui/atoms/alert/alert-dismiss.d.ts +9 -0
- package/dist/ui/atoms/alert/alert-dismiss.d.ts.map +1 -0
- package/dist/ui/atoms/alert/alert-dismiss.js +28 -0
- package/dist/ui/atoms/alert/alert-dismiss.js.map +1 -0
- package/dist/ui/atoms/alert/alert.d.ts +24 -0
- package/dist/ui/atoms/alert/alert.d.ts.map +1 -0
- package/dist/ui/atoms/alert/alert.js +50 -0
- package/dist/ui/atoms/alert/alert.js.map +1 -0
- package/dist/ui/atoms/alert/index.d.ts +3 -0
- package/dist/ui/atoms/alert/index.d.ts.map +1 -0
- package/dist/ui/atoms/alert/index.js +4 -0
- package/dist/ui/atoms/alert/index.js.map +1 -0
- package/dist/ui/atoms/alert/tone.d.ts +8 -0
- package/dist/ui/atoms/alert/tone.d.ts.map +1 -0
- package/dist/ui/atoms/alert/tone.js +11 -0
- package/dist/ui/atoms/alert/tone.js.map +1 -0
- package/dist/ui/atoms/button/button-recipe.js +1 -1
- package/dist/ui/atoms/button/button-recipe.js.map +1 -1
- package/dist/ui/atoms/button/index.d.ts +1 -0
- package/dist/ui/atoms/button/index.d.ts.map +1 -1
- package/dist/ui/atoms/button/index.js +1 -0
- package/dist/ui/atoms/button/index.js.map +1 -1
- package/dist/ui/atoms/button/submit-button.d.ts +13 -0
- package/dist/ui/atoms/button/submit-button.d.ts.map +1 -0
- package/dist/ui/atoms/button/submit-button.js +29 -0
- package/dist/ui/atoms/button/submit-button.js.map +1 -0
- package/dist/ui/atoms/cell-value/cell-value.d.ts +6 -1
- package/dist/ui/atoms/cell-value/cell-value.d.ts.map +1 -1
- package/dist/ui/atoms/cell-value/cell-value.js +20 -2
- package/dist/ui/atoms/cell-value/cell-value.js.map +1 -1
- package/dist/ui/atoms/field/checkbox.d.ts.map +1 -1
- package/dist/ui/atoms/field/checkbox.js +21 -1
- package/dist/ui/atoms/field/checkbox.js.map +1 -1
- package/dist/ui/atoms/field/field.js +1 -1
- package/dist/ui/atoms/field/field.js.map +1 -1
- package/dist/ui/atoms/field-control/field-control.d.ts +6 -1
- package/dist/ui/atoms/field-control/field-control.d.ts.map +1 -1
- package/dist/ui/atoms/field-control/field-control.js +23 -3
- package/dist/ui/atoms/field-control/field-control.js.map +1 -1
- package/dist/ui/atoms/money/index.d.ts +4 -0
- package/dist/ui/atoms/money/index.d.ts.map +1 -0
- package/dist/ui/atoms/money/index.js +5 -0
- package/dist/ui/atoms/money/index.js.map +1 -0
- package/dist/ui/atoms/money/money-input.d.ts +19 -0
- package/dist/ui/atoms/money/money-input.d.ts.map +1 -0
- package/dist/ui/atoms/money/money-input.js +75 -0
- package/dist/ui/atoms/money/money-input.js.map +1 -0
- package/dist/ui/atoms/money/money-math.d.ts +21 -0
- package/dist/ui/atoms/money/money-math.d.ts.map +1 -0
- package/dist/ui/atoms/money/money-math.js +36 -0
- package/dist/ui/atoms/money/money-math.js.map +1 -0
- package/dist/ui/atoms/money/money.d.ts +19 -0
- package/dist/ui/atoms/money/money.d.ts.map +1 -0
- package/dist/ui/atoms/money/money.js +26 -0
- package/dist/ui/atoms/money/money.js.map +1 -0
- package/dist/ui/atoms/number-input/index.d.ts +3 -0
- package/dist/ui/atoms/number-input/index.d.ts.map +1 -0
- package/dist/ui/atoms/number-input/index.js +4 -0
- package/dist/ui/atoms/number-input/index.js.map +1 -0
- package/dist/ui/atoms/number-input/number-input.d.ts +10 -0
- package/dist/ui/atoms/number-input/number-input.d.ts.map +1 -0
- package/dist/ui/atoms/number-input/number-input.js +23 -0
- package/dist/ui/atoms/number-input/number-input.js.map +1 -0
- package/dist/ui/atoms/number-input/quantity-input.d.ts +22 -0
- package/dist/ui/atoms/number-input/quantity-input.d.ts.map +1 -0
- package/dist/ui/atoms/number-input/quantity-input.js +99 -0
- package/dist/ui/atoms/number-input/quantity-input.js.map +1 -0
- package/dist/ui/atoms/skeleton/skeleton.js +1 -1
- package/dist/ui/atoms/skeleton/skeleton.js.map +1 -1
- package/dist/ui/atoms/status-tag/index.d.ts +2 -0
- package/dist/ui/atoms/status-tag/index.d.ts.map +1 -0
- package/dist/ui/atoms/status-tag/index.js +3 -0
- package/dist/ui/atoms/status-tag/index.js.map +1 -0
- package/dist/ui/atoms/status-tag/status-tag.d.ts +17 -0
- package/dist/ui/atoms/status-tag/status-tag.d.ts.map +1 -0
- package/dist/ui/atoms/status-tag/status-tag.js +37 -0
- package/dist/ui/atoms/status-tag/status-tag.js.map +1 -0
- package/dist/ui/atoms/switch/switch.d.ts.map +1 -1
- package/dist/ui/atoms/switch/switch.js +6 -3
- package/dist/ui/atoms/switch/switch.js.map +1 -1
- package/dist/ui/molecules/breadcrumbs/breadcrumbs.d.ts +11 -2
- package/dist/ui/molecules/breadcrumbs/breadcrumbs.d.ts.map +1 -1
- package/dist/ui/molecules/breadcrumbs/breadcrumbs.js +9 -7
- package/dist/ui/molecules/breadcrumbs/breadcrumbs.js.map +1 -1
- package/dist/ui/molecules/column-settings/column-settings.js +1 -1
- package/dist/ui/molecules/column-settings/column-settings.js.map +1 -1
- package/dist/ui/molecules/confirm-dialog/confirm-dialog.js +2 -2
- package/dist/ui/molecules/confirm-dialog/confirm-dialog.js.map +1 -1
- package/dist/ui/molecules/description-list/description-list.d.ts +17 -0
- package/dist/ui/molecules/description-list/description-list.d.ts.map +1 -0
- package/dist/ui/molecules/description-list/description-list.js +36 -0
- package/dist/ui/molecules/description-list/description-list.js.map +1 -0
- package/dist/ui/molecules/description-list/index.d.ts +2 -0
- package/dist/ui/molecules/description-list/index.d.ts.map +1 -0
- package/dist/ui/molecules/description-list/index.js +3 -0
- package/dist/ui/molecules/description-list/index.js.map +1 -0
- package/dist/ui/molecules/drawer/drawer.js +3 -3
- package/dist/ui/molecules/drawer/drawer.js.map +1 -1
- package/dist/ui/molecules/empty-state/empty-state.d.ts +15 -0
- package/dist/ui/molecules/empty-state/empty-state.d.ts.map +1 -0
- package/dist/ui/molecules/empty-state/empty-state.js +29 -0
- package/dist/ui/molecules/empty-state/empty-state.js.map +1 -0
- package/dist/ui/molecules/empty-state/index.d.ts +2 -0
- package/dist/ui/molecules/empty-state/index.d.ts.map +1 -0
- package/dist/ui/molecules/empty-state/index.js +3 -0
- package/dist/ui/molecules/empty-state/index.js.map +1 -0
- package/dist/ui/molecules/entity-link-input/entity-link-input.d.ts.map +1 -1
- package/dist/ui/molecules/entity-link-input/entity-link-input.js +7 -4
- package/dist/ui/molecules/entity-link-input/entity-link-input.js.map +1 -1
- package/dist/ui/molecules/entity-link-picker/entity-link-picker.js +2 -2
- package/dist/ui/molecules/entity-link-picker/entity-link-picker.js.map +1 -1
- package/dist/ui/molecules/media-gallery/index.d.ts +2 -0
- package/dist/ui/molecules/media-gallery/index.d.ts.map +1 -0
- package/dist/ui/molecules/media-gallery/index.js +3 -0
- package/dist/ui/molecules/media-gallery/index.js.map +1 -0
- package/dist/ui/molecules/media-gallery/media-gallery.d.ts +31 -0
- package/dist/ui/molecules/media-gallery/media-gallery.d.ts.map +1 -0
- package/dist/ui/molecules/media-gallery/media-gallery.js +64 -0
- package/dist/ui/molecules/media-gallery/media-gallery.js.map +1 -0
- package/dist/ui/molecules/menu/menu.d.ts.map +1 -1
- package/dist/ui/molecules/menu/menu.js +6 -4
- package/dist/ui/molecules/menu/menu.js.map +1 -1
- package/dist/ui/molecules/nav-menu/nav-menu.js +4 -4
- package/dist/ui/molecules/nav-menu/nav-menu.js.map +1 -1
- package/dist/ui/molecules/segmented-nav/segment-classes.d.ts +3 -3
- package/dist/ui/molecules/segmented-nav/segment-classes.d.ts.map +1 -1
- package/dist/ui/molecules/segmented-nav/segment-classes.js +2 -2
- package/dist/ui/molecules/segmented-nav/segment-classes.js.map +1 -1
- package/dist/ui/molecules/segmented-nav/segmented-nav.d.ts +14 -4
- package/dist/ui/molecules/segmented-nav/segmented-nav.d.ts.map +1 -1
- package/dist/ui/molecules/segmented-nav/segmented-nav.js +6 -5
- package/dist/ui/molecules/segmented-nav/segmented-nav.js.map +1 -1
- package/dist/ui/molecules/split-view/split-view.js +3 -3
- package/dist/ui/molecules/split-view/split-view.js.map +1 -1
- package/dist/ui/molecules/tab-strip/tab-strip.d.ts +51 -12
- package/dist/ui/molecules/tab-strip/tab-strip.d.ts.map +1 -1
- package/dist/ui/molecules/tab-strip/tab-strip.js +125 -34
- package/dist/ui/molecules/tab-strip/tab-strip.js.map +1 -1
- package/dist/ui/molecules/toast-region/toast-region.js +1 -1
- package/dist/ui/molecules/toast-region/toast-region.js.map +1 -1
- package/dist/ui/organisms/command-palette/command-palette.js +6 -6
- package/dist/ui/organisms/command-palette/command-palette.js.map +1 -1
- package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.d.ts.map +1 -1
- package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.js +8 -2
- package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.js.map +1 -1
- package/dist/ui/organisms/entity-form/entity-form.js +4 -2
- package/dist/ui/organisms/entity-form/entity-form.js.map +1 -1
- package/dist/ui/organisms/entity-table/entity-table.d.ts.map +1 -1
- package/dist/ui/organisms/entity-table/entity-table.js +2 -1
- package/dist/ui/organisms/entity-table/entity-table.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/description-list/description-list.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef, ReactNode } from 'react';\n\nimport { cn } from '../../utils/cn.js';\n\nexport interface DescriptionListItem {\n /** A stable key; the term itself when it is unique. */\n readonly key?: string;\n readonly term: ReactNode;\n readonly description: ReactNode;\n}\n\nexport interface DescriptionListProps extends Omit<\n ComponentPropsWithoutRef<'dl'>,\n 'children'\n> {\n items: readonly DescriptionListItem[];\n}\n\n/**\n * Terms and what they are — a record's facts, a product's specifications —\n * as a real `<dl>`, so each value is announced with the term it belongs to.\n * Two columns where there is room, stacked where there is not. Server-safe.\n */\nexport function DescriptionList({\n items,\n className,\n ...props\n}: DescriptionListProps) {\n return (\n <dl\n className={cn(\n 'm-0 sm:grid-cols-[minmax(0,1fr)_minmax(0,2fr)] grid grid-cols-1 gap-x-m gap-y-2xs text-step-sm',\n className,\n )}\n {...props}\n >\n {items.map((item, index) => (\n <div key={item.key ?? index} className=\"contents\">\n <dt className=\"font-medium text-content-muted\">{item.term}</dt>\n <dd className=\"m-0 text-content\">{item.description}</dd>\n </div>\n ))}\n </dl>\n );\n}\n"],"names":["cn","DescriptionList","items","className","props","dl","map","item","index","div","dt","term","dd","description","key"],"mappings":";;;AAEA,SAASA,EAAE,QAAQ,oBAAoB;AAgBvC;;;;CAIC,GACD,OAAO,SAASC,gBAAgB,EAIT;QAJS,EAC9BC,KAAK,EACLC,SAAS,EAEY,OADlBC;;;;IAEH,qBACE,KAACC;QACCF,WAAWH,GACT,kGACAG;OAEEC;kBAEHF,MAAMI,GAAG,CAAC,CAACC,MAAMC;gBACND;iCAAV,MAACE;gBAA4BN,WAAU;;kCACrC,KAACO;wBAAGP,WAAU;kCAAkCI,KAAKI,IAAI;;kCACzD,KAACC;wBAAGT,WAAU;kCAAoBI,KAAKM,WAAW;;;gBAF1CN,YAAAA,KAAKO,GAAG,YAARP,YAAYC;;;AAO9B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/description-list/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/description-list/index.ts"],"sourcesContent":["export * from './description-list.js';\n"],"names":[],"mappings":"AAAA,cAAc,wBAAwB"}
|
|
@@ -18,11 +18,11 @@ import { cn } from '../../utils/cn.js';
|
|
|
18
18
|
return /*#__PURE__*/ _jsxs(Dialog, {
|
|
19
19
|
open: open,
|
|
20
20
|
onClose: onClose,
|
|
21
|
-
className: "relative z-
|
|
21
|
+
className: "relative z-(--z-overlay)",
|
|
22
22
|
children: [
|
|
23
23
|
/*#__PURE__*/ _jsx("div", {
|
|
24
24
|
"aria-hidden": true,
|
|
25
|
-
className: "inset-0 bg-
|
|
25
|
+
className: "inset-0 fixed bg-scrim"
|
|
26
26
|
}),
|
|
27
27
|
/*#__PURE__*/ _jsx("div", {
|
|
28
28
|
className: "inset-0 fixed flex",
|
|
@@ -30,7 +30,7 @@ import { cn } from '../../utils/cn.js';
|
|
|
30
30
|
"data-testid": "drawer",
|
|
31
31
|
className: cn(// 200ms is `--default-transition-duration`, the beat every other
|
|
32
32
|
// transition in the system already uses.
|
|
33
|
-
'w-72 flex max-w-
|
|
33
|
+
'w-72 flex max-w-(--overlay-max-inline) flex-col gap-m overflow-y-auto border-r border-border bg-surface-elevated p-s transition duration-200', className),
|
|
34
34
|
children: [
|
|
35
35
|
/*#__PURE__*/ _jsxs(DialogTitle, {
|
|
36
36
|
className: "flex items-center justify-between gap-2xs text-step-1 font-semibold text-content",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/ui/molecules/drawer/drawer.tsx"],"sourcesContent":["'use client';\n\nimport { Dialog, DialogPanel, DialogTitle } from '@headlessui/react';\nimport type { ReactNode } from 'react';\n\nimport { Button } from '../../atoms/button/index.js';\nimport { cn } from '../../utils/cn.js';\n\nexport interface DrawerProps {\n open: boolean;\n onClose: () => void;\n /** Names the dialog. A dialog with no accessible name is announced as \"dialog\". */\n title: ReactNode;\n /** Accessible name for the close control. */\n closeLabel: string;\n children: ReactNode;\n className?: string;\n}\n\n/**\n * An off-canvas panel anchored to the inline start of the viewport.\n *\n * Built on Headless UI's `Dialog`, like {@link ConfirmDialog} and the entity link\n * picker, so the focus trap, `Escape`, the backdrop click and focus restoration\n * to the trigger are the library's rather than four hand-written behaviours that\n * each have to be got right — and a drawer that traps focus badly is worse than\n * no drawer, because a keyboard visitor cannot leave it.\n *\n * It carries no viewport logic. *When* a drawer is the right shape is the page\n * shell's decision (`useViewportMode`); this is only the shape.\n */\nexport function Drawer({\n open,\n onClose,\n title,\n closeLabel,\n children,\n className,\n}: DrawerProps) {\n return (\n <Dialog open={open} onClose={onClose} className=\"relative z-
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/drawer/drawer.tsx"],"sourcesContent":["'use client';\n\nimport { Dialog, DialogPanel, DialogTitle } from '@headlessui/react';\nimport type { ReactNode } from 'react';\n\nimport { Button } from '../../atoms/button/index.js';\nimport { cn } from '../../utils/cn.js';\n\nexport interface DrawerProps {\n open: boolean;\n onClose: () => void;\n /** Names the dialog. A dialog with no accessible name is announced as \"dialog\". */\n title: ReactNode;\n /** Accessible name for the close control. */\n closeLabel: string;\n children: ReactNode;\n className?: string;\n}\n\n/**\n * An off-canvas panel anchored to the inline start of the viewport.\n *\n * Built on Headless UI's `Dialog`, like {@link ConfirmDialog} and the entity link\n * picker, so the focus trap, `Escape`, the backdrop click and focus restoration\n * to the trigger are the library's rather than four hand-written behaviours that\n * each have to be got right — and a drawer that traps focus badly is worse than\n * no drawer, because a keyboard visitor cannot leave it.\n *\n * It carries no viewport logic. *When* a drawer is the right shape is the page\n * shell's decision (`useViewportMode`); this is only the shape.\n */\nexport function Drawer({\n open,\n onClose,\n title,\n closeLabel,\n children,\n className,\n}: DrawerProps) {\n return (\n <Dialog open={open} onClose={onClose} className=\"relative z-(--z-overlay)\">\n <div aria-hidden className=\"inset-0 fixed bg-scrim\" />\n <div className=\"inset-0 fixed flex\">\n <DialogPanel\n data-testid=\"drawer\"\n className={cn(\n // 200ms is `--default-transition-duration`, the beat every other\n // transition in the system already uses.\n 'w-72 flex max-w-(--overlay-max-inline) flex-col gap-m overflow-y-auto border-r border-border bg-surface-elevated p-s transition duration-200',\n className,\n )}\n >\n <DialogTitle className=\"flex items-center justify-between gap-2xs text-step-1 font-semibold text-content\">\n {title}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={closeLabel}\n onClick={onClose}\n >\n <span aria-hidden=\"true\">✕</span>\n </Button>\n </DialogTitle>\n {children}\n </DialogPanel>\n </div>\n </Dialog>\n );\n}\n"],"names":["Dialog","DialogPanel","DialogTitle","Button","cn","Drawer","open","onClose","title","closeLabel","children","className","div","aria-hidden","data-testid","type","variant","size","aria-label","onClick","span"],"mappings":"AAAA;;AAEA,SAASA,MAAM,EAAEC,WAAW,EAAEC,WAAW,QAAQ,oBAAoB;AAGrE,SAASC,MAAM,QAAQ,8BAA8B;AACrD,SAASC,EAAE,QAAQ,oBAAoB;AAavC;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,OAAO,EACrBC,IAAI,EACJC,OAAO,EACPC,KAAK,EACLC,UAAU,EACVC,QAAQ,EACRC,SAAS,EACG;IACZ,qBACE,MAACX;QAAOM,MAAMA;QAAMC,SAASA;QAASI,WAAU;;0BAC9C,KAACC;gBAAIC,aAAW;gBAACF,WAAU;;0BAC3B,KAACC;gBAAID,WAAU;0BACb,cAAA,MAACV;oBACCa,eAAY;oBACZH,WAAWP,GACT,iEAAiE;oBACjE,yCAAyC;oBACzC,gJACAO;;sCAGF,MAACT;4BAAYS,WAAU;;gCACpBH;8CACD,KAACL;oCACCY,MAAK;oCACLC,SAAQ;oCACRC,MAAK;oCACLC,cAAYT;oCACZU,SAASZ;8CAET,cAAA,KAACa;wCAAKP,eAAY;kDAAO;;;;;wBAG5BH;;;;;;AAKX"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
2
|
+
export interface EmptyStateProps extends Omit<ComponentPropsWithoutRef<'div'>, 'children'> {
|
|
3
|
+
/** One sentence: what is not here, and why when it is not obvious. */
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
/** The one thing to do about it — create the first record, clear a filter. */
|
|
6
|
+
action?: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* What a region shows when it holds nothing: one sentence, and at most one way
|
|
10
|
+
* forward. Not a failure — a region that could not load says so with an
|
|
11
|
+
* `Alert` instead, because an empty result and a dead backend must never look
|
|
12
|
+
* alike. Server-safe.
|
|
13
|
+
*/
|
|
14
|
+
export declare function EmptyState({ action, className, children, ...props }: EmptyStateProps): import("react").JSX.Element;
|
|
15
|
+
//# sourceMappingURL=empty-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"empty-state.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/empty-state/empty-state.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIjE,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,wBAAwB,CAAC,KAAK,CAAC,EAC/B,UAAU,CACX;IACC,sEAAsE;IACtE,QAAQ,EAAE,SAAS,CAAC;IACpB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,eAAe,+BAejB"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
|
|
3
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { cn } from '../../utils/cn.js';
|
|
5
|
+
/**
|
|
6
|
+
* What a region shows when it holds nothing: one sentence, and at most one way
|
|
7
|
+
* forward. Not a failure — a region that could not load says so with an
|
|
8
|
+
* `Alert` instead, because an empty result and a dead backend must never look
|
|
9
|
+
* alike. Server-safe.
|
|
10
|
+
*/ export function EmptyState(_0) {
|
|
11
|
+
let { action, className, children } = _0, props = _object_without_properties_loose(_0, [
|
|
12
|
+
"action",
|
|
13
|
+
"className",
|
|
14
|
+
"children"
|
|
15
|
+
]);
|
|
16
|
+
return /*#__PURE__*/ _jsxs("div", _extends({
|
|
17
|
+
className: cn('flex flex-col items-center gap-s rounded-lg border border-dashed border-border px-m py-l text-center', className)
|
|
18
|
+
}, props, {
|
|
19
|
+
children: [
|
|
20
|
+
/*#__PURE__*/ _jsx("p", {
|
|
21
|
+
className: "m-0 max-w-(--measure) text-step-sm text-content-muted",
|
|
22
|
+
children: children
|
|
23
|
+
}),
|
|
24
|
+
action
|
|
25
|
+
]
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=empty-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/empty-state/empty-state.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef, ReactNode } from 'react';\n\nimport { cn } from '../../utils/cn.js';\n\nexport interface EmptyStateProps extends Omit<\n ComponentPropsWithoutRef<'div'>,\n 'children'\n> {\n /** One sentence: what is not here, and why when it is not obvious. */\n children: ReactNode;\n /** The one thing to do about it — create the first record, clear a filter. */\n action?: ReactNode;\n}\n\n/**\n * What a region shows when it holds nothing: one sentence, and at most one way\n * forward. Not a failure — a region that could not load says so with an\n * `Alert` instead, because an empty result and a dead backend must never look\n * alike. Server-safe.\n */\nexport function EmptyState({\n action,\n className,\n children,\n ...props\n}: EmptyStateProps) {\n return (\n <div\n className={cn(\n 'flex flex-col items-center gap-s rounded-lg border border-dashed border-border px-m py-l text-center',\n className,\n )}\n {...props}\n >\n <p className=\"m-0 max-w-(--measure) text-step-sm text-content-muted\">\n {children}\n </p>\n {action}\n </div>\n );\n}\n"],"names":["cn","EmptyState","action","className","children","props","div","p"],"mappings":";;;AAEA,SAASA,EAAE,QAAQ,oBAAoB;AAYvC;;;;;CAKC,GACD,OAAO,SAASC,WAAW,EAKT;QALS,EACzBC,MAAM,EACNC,SAAS,EACTC,QAAQ,EAEQ,OADbC;;;;;IAEH,qBACE,MAACC;QACCH,WAAWH,GACT,wGACAG;OAEEE;;0BAEJ,KAACE;gBAAEJ,WAAU;0BACVC;;YAEFF;;;AAGP"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/empty-state/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/empty-state/index.ts"],"sourcesContent":["export * from './empty-state.js';\n"],"names":[],"mappings":"AAAA,cAAc,mBAAmB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-link-input.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/entity-link-input/entity-link-input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,MAAM,EACN,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,eAAe,CAAC;AAcvB,MAAM,WAAW,oBAAoB,CAAC,OAAO,SAAS,MAAM;IAC1D,+DAA+D;IAC/D,UAAU,EAAE,qBAAqB,CAAC;IAClC,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAClC,4CAA4C;IAC5C,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACpC,gCAAgC;IAChC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,0CAA0C;IAC1C,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAsBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAAC,OAAO,SAAS,MAAM,EAAE,EACtD,UAAU,EACV,KAAK,EACL,MAAM,EACN,QAAQ,EACR,OAAO,EACP,EAAE,EACF,QAAgB,GACjB,EAAE,oBAAoB,CAAC,OAAO,CAAC,+
|
|
1
|
+
{"version":3,"file":"entity-link-input.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/entity-link-input/entity-link-input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,MAAM,EACN,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,eAAe,CAAC;AAcvB,MAAM,WAAW,oBAAoB,CAAC,OAAO,SAAS,MAAM;IAC1D,+DAA+D;IAC/D,UAAU,EAAE,qBAAqB,CAAC;IAClC,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAClC,4CAA4C;IAC5C,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACpC,gCAAgC;IAChC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,0CAA0C;IAC1C,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAsBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAAC,OAAO,SAAS,MAAM,EAAE,EACtD,UAAU,EACV,KAAK,EACL,MAAM,EACN,QAAQ,EACR,OAAO,EACP,EAAE,EACF,QAAgB,GACjB,EAAE,oBAAoB,CAAC,OAAO,CAAC,+BAoJ/B"}
|
|
@@ -97,7 +97,7 @@ import { LoadingBoundary } from '../loading-boundary/index.js';
|
|
|
97
97
|
disabled: disabled,
|
|
98
98
|
onChange: (event)=>source.quick.setTerm(event.currentTarget.value),
|
|
99
99
|
className: [
|
|
100
|
-
'w-full rounded-lg border border-border bg-surface-elevated px-2xs py-3xs',
|
|
100
|
+
'min-h-(--target-min) w-full rounded-lg border border-border-control bg-surface-elevated px-2xs py-3xs',
|
|
101
101
|
'text-step-sm text-content',
|
|
102
102
|
'focus-ring',
|
|
103
103
|
'disabled:cursor-not-allowed disabled:opacity-50'
|
|
@@ -112,7 +112,7 @@ import { LoadingBoundary } from '../loading-boundary/index.js';
|
|
|
112
112
|
}),
|
|
113
113
|
/*#__PURE__*/ _jsxs(ComboboxOptions, {
|
|
114
114
|
className: [
|
|
115
|
-
'max-h-64 absolute z-
|
|
115
|
+
'max-h-64 absolute z-(--z-dropdown) mt-3xs w-full overflow-auto',
|
|
116
116
|
'rounded-lg border border-border bg-surface-elevated p-3xs shadow-overlay',
|
|
117
117
|
// The panel itself is a focus-managed CONTAINER, not a target: the ring
|
|
118
118
|
// belongs on the item inside it. Stripping the outline here is
|
|
@@ -134,8 +134,11 @@ import { LoadingBoundary } from '../loading-boundary/index.js';
|
|
|
134
134
|
source.quick.options.map((option)=>/*#__PURE__*/ _jsx(ComboboxOption, {
|
|
135
135
|
value: option,
|
|
136
136
|
className: [
|
|
137
|
-
'cursor-pointer rounded-md px-2xs py-3xs text-step-sm text-content',
|
|
138
|
-
|
|
137
|
+
'flex min-h-(--target-min) cursor-pointer items-center rounded-md border-l-2 border-transparent px-2xs py-3xs text-step-sm text-content',
|
|
138
|
+
// Active: the focus ring. Chosen but not under the
|
|
139
|
+
// keyboard: a `selected` edge, 3:1 on the panel.
|
|
140
|
+
'focus-ring data-focus:bg-surface data-focus:-outline-offset-2',
|
|
141
|
+
'data-selected:border-selected'
|
|
139
142
|
].join(' '),
|
|
140
143
|
children: source.labelOf(option)
|
|
141
144
|
}, String(option.id)))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/ui/molecules/entity-link-input/entity-link-input.tsx"],"sourcesContent":["'use client';\n\nimport type {\n Entity,\n EntityFieldDescriptor,\n EntityLinkSource,\n} from '@entifix/core';\nimport {\n Combobox,\n ComboboxButton,\n ComboboxInput,\n ComboboxOption,\n ComboboxOptions,\n} from '@headlessui/react';\n\nimport { useErrorMessage, useT } from '../../../i18n/index.js';\nimport { Button } from '../../atoms/button/index.js';\nimport { EntityLinkPicker } from '../entity-link-picker/index.js';\nimport { LoadingBoundary } from '../loading-boundary/index.js';\n\nexport interface EntityLinkInputProps<TTarget extends Entity> {\n /** The relation being edited. Its label names the controls. */\n descriptor: EntityFieldDescriptor;\n /** The held foreign key, as the draft carries it. `''` means unassigned. */\n value: string;\n source: EntityLinkSource<TTarget>;\n /** A target was chosen — in either mode. */\n onSelect: (entity: TTarget) => void;\n /** The relation was emptied. */\n onClear: () => void;\n /** The id the field's label points at. */\n id?: string;\n disabled?: boolean;\n}\n\n/** Chevron — the quick search. Inline so the package ships no icon dependency. */\nfunction ChevronIcon() {\n return (\n <svg aria-hidden viewBox=\"0 0 20 20\" className=\"size-4 fill-current\">\n <path d=\"M5.5 7.5 10 12l4.5-4.5z\" />\n </svg>\n );\n}\n\n/** Ellipsis — the browse dialog, the same affordance a desktop app uses. */\nfunction EllipsisIcon() {\n return (\n <svg aria-hidden viewBox=\"0 0 20 20\" className=\"size-4 fill-current\">\n <circle cx=\"4\" cy=\"10\" r=\"1.5\" />\n <circle cx=\"10\" cy=\"10\" r=\"1.5\" />\n <circle cx=\"16\" cy=\"10\" r=\"1.5\" />\n </svg>\n );\n}\n\n/**\n * The editor for a to-one relation: what the value *is*, plus the two ways to\n * change it.\n *\n * - the chevron opens a type-ahead over the target entity — \"I know roughly what\n * it is called\";\n * - the ellipsis opens the target's full table in a dialog — \"I need to filter\n * the catalog to find it\".\n *\n * It is presentational. Every option, loading flag and error arrives through\n * {@link EntityLinkSource}, which the integration layer produces; this component\n * cannot fetch, and never decides how the relation is written back — it reports\n * the chosen instance and the form's reconstruction step applies the entity's own\n * `linkSerialization` policy.\n *\n * The displayed text is the target's label when one is known and the bare foreign\n * key when it is not, so a draft restored before its label resolves still shows\n * *something* true rather than an empty box.\n */\nexport function EntityLinkInput<TTarget extends Entity>({\n descriptor,\n value,\n source,\n onSelect,\n onClear,\n id,\n disabled = false,\n}: EntityLinkInputProps<TTarget>) {\n const t = useT();\n const errorMessage = useErrorMessage();\n\n const assigned = value !== '';\n const label = source.selected.label ?? (assigned ? value : '');\n const display = source.selected.isLoading\n ? t('link.loading')\n : label === ''\n ? t('link.empty')\n : label;\n\n /**\n * Headless UI reports `null` when the search is abandoned (escape, blur). That\n * is not a request to empty the relation — clearing is the explicit action\n * beside the input — so it leaves the held value alone.\n */\n const handleSelect = (entity: TTarget | null) => {\n if (entity === null) return;\n onSelect(entity);\n source.quick.setTerm('');\n };\n\n return (\n <div className=\"flex flex-col gap-3xs\">\n <div className=\"flex items-stretch gap-3xs\">\n <div className=\"relative flex-1\">\n <Combobox<TTarget | null>\n value={null}\n onChange={handleSelect}\n disabled={disabled}\n immediate\n >\n {/*\n The input edits the *search term*, never the value: the value is a\n foreign key, which is not something a user types. So it displays the\n resolved label while idle and the term while searching.\n */}\n <ComboboxInput\n id={id}\n aria-label={t('link.search', { field: descriptor.label })}\n placeholder={display}\n value={source.quick.term}\n disabled={disabled}\n onChange={event =>\n source.quick.setTerm(event.currentTarget.value)\n }\n className={[\n 'w-full rounded-lg border border-border bg-surface-elevated px-2xs py-3xs',\n 'text-step-sm text-content',\n 'focus-ring',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n ].join(' ')}\n />\n <ComboboxButton\n aria-label={t('link.suggestAria', { field: descriptor.label })}\n className=\"inset-y-0 right-0 absolute flex items-center px-2xs text-content-muted\"\n >\n <ChevronIcon />\n </ComboboxButton>\n <ComboboxOptions\n className={[\n 'max-h-64 absolute z-50 mt-3xs w-full overflow-auto',\n 'rounded-lg border border-border bg-surface-elevated p-3xs shadow-overlay',\n // The panel itself is a focus-managed CONTAINER, not a target: the ring\n // belongs on the item inside it. Stripping the outline here is\n // correct, and is not a missing `focus-ring`.\n 'focus:outline-none',\n ].join(' ')}\n >\n {source.quick.isLoading && (\n <LoadingBoundary\n isLoading\n lines={2}\n label={t('link.loading')}\n className=\"px-2xs py-3xs\"\n >\n {null}\n </LoadingBoundary>\n )}\n {!source.quick.isLoading && source.quick.options.length === 0 && (\n <div className=\"px-2xs py-3xs text-step-sm text-content-muted\">\n {t('link.noResults')}\n </div>\n )}\n {source.quick.options.map(option => (\n <ComboboxOption\n key={String(option.id)}\n value={option}\n className={[\n 'cursor-pointer rounded-md px-2xs py-3xs text-step-sm text-content',\n 'data-focus:bg-surface',\n ].join(' ')}\n >\n {source.labelOf(option)}\n </ComboboxOption>\n ))}\n </ComboboxOptions>\n </Combobox>\n </div>\n\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n disabled={disabled}\n aria-label={t('link.browseAria', { field: descriptor.label })}\n onClick={source.browse.open}\n >\n <EllipsisIcon />\n </Button>\n {/* Clearing is only offered when there is something to clear — and a\n required relation still offers it, because `required` is the form's\n rule to report, not the input's to enforce. */}\n {assigned && !disabled && (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={onClear}>\n {t('link.clear', { field: descriptor.label })}\n </Button>\n )}\n </div>\n\n {/* The held value, always readable — the combobox input shows the term. */}\n <span\n data-testid={`entity-link-value-${descriptor.name}`}\n className=\"text-step-sm text-content\"\n >\n {display}\n </span>\n\n {source.quick.error && (\n <span role=\"alert\" className=\"text-step-sm text-danger\">\n {errorMessage(source.quick.error)}\n </span>\n )}\n\n <EntityLinkPicker<TTarget>\n descriptor={descriptor}\n source={source}\n onSelect={entity => {\n onSelect(entity);\n source.browse.close();\n }}\n />\n </div>\n );\n}\n"],"names":["Combobox","ComboboxButton","ComboboxInput","ComboboxOption","ComboboxOptions","useErrorMessage","useT","Button","EntityLinkPicker","LoadingBoundary","ChevronIcon","svg","aria-hidden","viewBox","className","path","d","EllipsisIcon","circle","cx","cy","r","EntityLinkInput","descriptor","value","source","onSelect","onClear","id","disabled","t","errorMessage","assigned","label","selected","display","isLoading","handleSelect","entity","quick","setTerm","div","onChange","immediate","aria-label","field","placeholder","term","event","currentTarget","join","lines","options","length","map","option","labelOf","String","type","variant","size","onClick","browse","open","span","data-testid","name","error","role","close"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/entity-link-input/entity-link-input.tsx"],"sourcesContent":["'use client';\n\nimport type {\n Entity,\n EntityFieldDescriptor,\n EntityLinkSource,\n} from '@entifix/core';\nimport {\n Combobox,\n ComboboxButton,\n ComboboxInput,\n ComboboxOption,\n ComboboxOptions,\n} from '@headlessui/react';\n\nimport { useErrorMessage, useT } from '../../../i18n/index.js';\nimport { Button } from '../../atoms/button/index.js';\nimport { EntityLinkPicker } from '../entity-link-picker/index.js';\nimport { LoadingBoundary } from '../loading-boundary/index.js';\n\nexport interface EntityLinkInputProps<TTarget extends Entity> {\n /** The relation being edited. Its label names the controls. */\n descriptor: EntityFieldDescriptor;\n /** The held foreign key, as the draft carries it. `''` means unassigned. */\n value: string;\n source: EntityLinkSource<TTarget>;\n /** A target was chosen — in either mode. */\n onSelect: (entity: TTarget) => void;\n /** The relation was emptied. */\n onClear: () => void;\n /** The id the field's label points at. */\n id?: string;\n disabled?: boolean;\n}\n\n/** Chevron — the quick search. Inline so the package ships no icon dependency. */\nfunction ChevronIcon() {\n return (\n <svg aria-hidden viewBox=\"0 0 20 20\" className=\"size-4 fill-current\">\n <path d=\"M5.5 7.5 10 12l4.5-4.5z\" />\n </svg>\n );\n}\n\n/** Ellipsis — the browse dialog, the same affordance a desktop app uses. */\nfunction EllipsisIcon() {\n return (\n <svg aria-hidden viewBox=\"0 0 20 20\" className=\"size-4 fill-current\">\n <circle cx=\"4\" cy=\"10\" r=\"1.5\" />\n <circle cx=\"10\" cy=\"10\" r=\"1.5\" />\n <circle cx=\"16\" cy=\"10\" r=\"1.5\" />\n </svg>\n );\n}\n\n/**\n * The editor for a to-one relation: what the value *is*, plus the two ways to\n * change it.\n *\n * - the chevron opens a type-ahead over the target entity — \"I know roughly what\n * it is called\";\n * - the ellipsis opens the target's full table in a dialog — \"I need to filter\n * the catalog to find it\".\n *\n * It is presentational. Every option, loading flag and error arrives through\n * {@link EntityLinkSource}, which the integration layer produces; this component\n * cannot fetch, and never decides how the relation is written back — it reports\n * the chosen instance and the form's reconstruction step applies the entity's own\n * `linkSerialization` policy.\n *\n * The displayed text is the target's label when one is known and the bare foreign\n * key when it is not, so a draft restored before its label resolves still shows\n * *something* true rather than an empty box.\n */\nexport function EntityLinkInput<TTarget extends Entity>({\n descriptor,\n value,\n source,\n onSelect,\n onClear,\n id,\n disabled = false,\n}: EntityLinkInputProps<TTarget>) {\n const t = useT();\n const errorMessage = useErrorMessage();\n\n const assigned = value !== '';\n const label = source.selected.label ?? (assigned ? value : '');\n const display = source.selected.isLoading\n ? t('link.loading')\n : label === ''\n ? t('link.empty')\n : label;\n\n /**\n * Headless UI reports `null` when the search is abandoned (escape, blur). That\n * is not a request to empty the relation — clearing is the explicit action\n * beside the input — so it leaves the held value alone.\n */\n const handleSelect = (entity: TTarget | null) => {\n if (entity === null) return;\n onSelect(entity);\n source.quick.setTerm('');\n };\n\n return (\n <div className=\"flex flex-col gap-3xs\">\n <div className=\"flex items-stretch gap-3xs\">\n <div className=\"relative flex-1\">\n <Combobox<TTarget | null>\n value={null}\n onChange={handleSelect}\n disabled={disabled}\n immediate\n >\n {/*\n The input edits the *search term*, never the value: the value is a\n foreign key, which is not something a user types. So it displays the\n resolved label while idle and the term while searching.\n */}\n <ComboboxInput\n id={id}\n aria-label={t('link.search', { field: descriptor.label })}\n placeholder={display}\n value={source.quick.term}\n disabled={disabled}\n onChange={event =>\n source.quick.setTerm(event.currentTarget.value)\n }\n className={[\n 'min-h-(--target-min) w-full rounded-lg border border-border-control bg-surface-elevated px-2xs py-3xs',\n 'text-step-sm text-content',\n 'focus-ring',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n ].join(' ')}\n />\n <ComboboxButton\n aria-label={t('link.suggestAria', { field: descriptor.label })}\n className=\"inset-y-0 right-0 absolute flex items-center px-2xs text-content-muted\"\n >\n <ChevronIcon />\n </ComboboxButton>\n <ComboboxOptions\n className={[\n 'max-h-64 absolute z-(--z-dropdown) mt-3xs w-full overflow-auto',\n 'rounded-lg border border-border bg-surface-elevated p-3xs shadow-overlay',\n // The panel itself is a focus-managed CONTAINER, not a target: the ring\n // belongs on the item inside it. Stripping the outline here is\n // correct, and is not a missing `focus-ring`.\n 'focus:outline-none',\n ].join(' ')}\n >\n {source.quick.isLoading && (\n <LoadingBoundary\n isLoading\n lines={2}\n label={t('link.loading')}\n className=\"px-2xs py-3xs\"\n >\n {null}\n </LoadingBoundary>\n )}\n {!source.quick.isLoading && source.quick.options.length === 0 && (\n <div className=\"px-2xs py-3xs text-step-sm text-content-muted\">\n {t('link.noResults')}\n </div>\n )}\n {source.quick.options.map(option => (\n <ComboboxOption\n key={String(option.id)}\n value={option}\n className={[\n 'flex min-h-(--target-min) cursor-pointer items-center rounded-md border-l-2 border-transparent px-2xs py-3xs text-step-sm text-content',\n // Active: the focus ring. Chosen but not under the\n // keyboard: a `selected` edge, 3:1 on the panel.\n 'focus-ring data-focus:bg-surface data-focus:-outline-offset-2',\n 'data-selected:border-selected',\n ].join(' ')}\n >\n {source.labelOf(option)}\n </ComboboxOption>\n ))}\n </ComboboxOptions>\n </Combobox>\n </div>\n\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n disabled={disabled}\n aria-label={t('link.browseAria', { field: descriptor.label })}\n onClick={source.browse.open}\n >\n <EllipsisIcon />\n </Button>\n {/* Clearing is only offered when there is something to clear — and a\n required relation still offers it, because `required` is the form's\n rule to report, not the input's to enforce. */}\n {assigned && !disabled && (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={onClear}>\n {t('link.clear', { field: descriptor.label })}\n </Button>\n )}\n </div>\n\n {/* The held value, always readable — the combobox input shows the term. */}\n <span\n data-testid={`entity-link-value-${descriptor.name}`}\n className=\"text-step-sm text-content\"\n >\n {display}\n </span>\n\n {source.quick.error && (\n <span role=\"alert\" className=\"text-step-sm text-danger\">\n {errorMessage(source.quick.error)}\n </span>\n )}\n\n <EntityLinkPicker<TTarget>\n descriptor={descriptor}\n source={source}\n onSelect={entity => {\n onSelect(entity);\n source.browse.close();\n }}\n />\n </div>\n );\n}\n"],"names":["Combobox","ComboboxButton","ComboboxInput","ComboboxOption","ComboboxOptions","useErrorMessage","useT","Button","EntityLinkPicker","LoadingBoundary","ChevronIcon","svg","aria-hidden","viewBox","className","path","d","EllipsisIcon","circle","cx","cy","r","EntityLinkInput","descriptor","value","source","onSelect","onClear","id","disabled","t","errorMessage","assigned","label","selected","display","isLoading","handleSelect","entity","quick","setTerm","div","onChange","immediate","aria-label","field","placeholder","term","event","currentTarget","join","lines","options","length","map","option","labelOf","String","type","variant","size","onClick","browse","open","span","data-testid","name","error","role","close"],"mappings":"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"}
|
|
@@ -23,11 +23,11 @@ import { EntityTable } from '../../organisms/entity-table/index.js';
|
|
|
23
23
|
/*#__PURE__*/ _jsxs(Dialog, {
|
|
24
24
|
open: browse.isOpen,
|
|
25
25
|
onClose: browse.close,
|
|
26
|
-
className: "inset-0 fixed z-
|
|
26
|
+
className: "inset-0 fixed z-(--z-overlay) flex items-start justify-center overflow-y-auto p-s",
|
|
27
27
|
children: [
|
|
28
28
|
/*#__PURE__*/ _jsx("div", {
|
|
29
29
|
"aria-hidden": true,
|
|
30
|
-
className: "inset-0 bg-
|
|
30
|
+
className: "inset-0 fixed bg-scrim"
|
|
31
31
|
}),
|
|
32
32
|
/*#__PURE__*/ _jsxs(DialogPanel, {
|
|
33
33
|
className: "max-w-4xl relative w-full rounded-lg border border-border bg-surface p-s shadow-overlay",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/ui/molecules/entity-link-picker/entity-link-picker.tsx"],"sourcesContent":["'use client';\n\nimport type {\n Entity,\n EntityFieldDescriptor,\n EntityLinkSource,\n} from '@entifix/core';\nimport { Dialog, DialogPanel, DialogTitle } from '@headlessui/react';\n\nimport { useT } from '../../../i18n/index.js';\nimport { Button } from '../../atoms/button/index.js';\nimport { EntityTable } from '../../organisms/entity-table/index.js';\n\nexport interface EntityLinkPickerProps<TTarget extends Entity> {\n /** The relation being set — its label names the dialog. */\n descriptor: EntityFieldDescriptor;\n source: EntityLinkSource<TTarget>;\n onSelect: (entity: TTarget) => void;\n}\n\n/**\n * The thorough half of setting a relation: the target entity's own table, with\n * its filters, sorting and paging, inside a dialog.\n *\n * It adds nothing to the table — that is the point. A picker that re-implemented\n * filtering would drift from the listing the user already knows, so this mounts\n * the same `EntityTable` and swaps navigation for selection through `onSelect`.\n * Everything it renders comes from `source.browse`, so it holds no fetch of its\n * own.\n */\nexport function EntityLinkPicker<TTarget extends Entity>({\n descriptor,\n source,\n onSelect,\n}: EntityLinkPickerProps<TTarget>) {\n const t = useT();\n const { browse } = source;\n\n return (\n // The dialog element itself fills the viewport rather than wrapping a\n // `fixed` child in a `relative` box: a zero-sized dialog is invisible to\n // anything measuring it — assistive tech and Playwright alike — even with a\n // painted panel inside it.\n <Dialog\n open={browse.isOpen}\n onClose={browse.close}\n className=\"inset-0 fixed z-
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/entity-link-picker/entity-link-picker.tsx"],"sourcesContent":["'use client';\n\nimport type {\n Entity,\n EntityFieldDescriptor,\n EntityLinkSource,\n} from '@entifix/core';\nimport { Dialog, DialogPanel, DialogTitle } from '@headlessui/react';\n\nimport { useT } from '../../../i18n/index.js';\nimport { Button } from '../../atoms/button/index.js';\nimport { EntityTable } from '../../organisms/entity-table/index.js';\n\nexport interface EntityLinkPickerProps<TTarget extends Entity> {\n /** The relation being set — its label names the dialog. */\n descriptor: EntityFieldDescriptor;\n source: EntityLinkSource<TTarget>;\n onSelect: (entity: TTarget) => void;\n}\n\n/**\n * The thorough half of setting a relation: the target entity's own table, with\n * its filters, sorting and paging, inside a dialog.\n *\n * It adds nothing to the table — that is the point. A picker that re-implemented\n * filtering would drift from the listing the user already knows, so this mounts\n * the same `EntityTable` and swaps navigation for selection through `onSelect`.\n * Everything it renders comes from `source.browse`, so it holds no fetch of its\n * own.\n */\nexport function EntityLinkPicker<TTarget extends Entity>({\n descriptor,\n source,\n onSelect,\n}: EntityLinkPickerProps<TTarget>) {\n const t = useT();\n const { browse } = source;\n\n return (\n // The dialog element itself fills the viewport rather than wrapping a\n // `fixed` child in a `relative` box: a zero-sized dialog is invisible to\n // anything measuring it — assistive tech and Playwright alike — even with a\n // painted panel inside it.\n <Dialog\n open={browse.isOpen}\n onClose={browse.close}\n className=\"inset-0 fixed z-(--z-overlay) flex items-start justify-center overflow-y-auto p-s\"\n >\n {/* Backdrop and panel are separate so a click outside closes the dialog\n without the table's own clicks bubbling into it. */}\n <div aria-hidden className=\"inset-0 fixed bg-scrim\" />\n <DialogPanel className=\"max-w-4xl relative w-full rounded-lg border border-border bg-surface p-s shadow-overlay\">\n <div className=\"flex items-center justify-between gap-s\">\n <DialogTitle className=\"text-step-1 font-semibold text-content\">\n {t('link.browseTitle', { field: descriptor.label })}\n </DialogTitle>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={browse.close}\n >\n {t('link.close')}\n </Button>\n </div>\n\n <EntityTable<TTarget>\n entityConstructor={source.entityConstructor}\n isLoading={browse.isLoading}\n error={browse.error}\n items={browse.items}\n totalItems={browse.totalItems}\n currentPage={browse.currentPage}\n pageSize={browse.pageSize}\n onPageChange={browse.onPageChange}\n onPageSizeChange={browse.onPageSizeChange}\n filtering={browse.filtering}\n sorting={browse.sorting}\n onFilteringChange={browse.onFilteringChange}\n onSortingChange={browse.onSortingChange}\n onSelect={onSelect}\n // Scoped so a picker's column personalization does not overwrite the\n // one the user set on the full listing of the same entity.\n preferencesKey={`link-picker:${descriptor.name}`}\n />\n </DialogPanel>\n </Dialog>\n );\n}\n"],"names":["Dialog","DialogPanel","DialogTitle","useT","Button","EntityTable","EntityLinkPicker","descriptor","source","onSelect","t","browse","open","isOpen","onClose","close","className","div","aria-hidden","field","label","type","variant","size","onClick","entityConstructor","isLoading","error","items","totalItems","currentPage","pageSize","onPageChange","onPageSizeChange","filtering","sorting","onFilteringChange","onSortingChange","preferencesKey","name"],"mappings":"AAAA;;AAOA,SAASA,MAAM,EAAEC,WAAW,EAAEC,WAAW,QAAQ,oBAAoB;AAErE,SAASC,IAAI,QAAQ,yBAAyB;AAC9C,SAASC,MAAM,QAAQ,8BAA8B;AACrD,SAASC,WAAW,QAAQ,wCAAwC;AASpE;;;;;;;;;CASC,GACD,OAAO,SAASC,iBAAyC,EACvDC,UAAU,EACVC,MAAM,EACNC,QAAQ,EACuB;IAC/B,MAAMC,IAAIP;IACV,MAAM,EAAEQ,MAAM,EAAE,GAAGH;IAEnB,OACE,sEAAsE;IACtE,yEAAyE;IACzE,4EAA4E;IAC5E,2BAA2B;kBAC3B,MAACR;QACCY,MAAMD,OAAOE,MAAM;QACnBC,SAASH,OAAOI,KAAK;QACrBC,WAAU;;0BAIV,KAACC;gBAAIC,aAAW;gBAACF,WAAU;;0BAC3B,MAACf;gBAAYe,WAAU;;kCACrB,MAACC;wBAAID,WAAU;;0CACb,KAACd;gCAAYc,WAAU;0CACpBN,EAAE,oBAAoB;oCAAES,OAAOZ,WAAWa,KAAK;gCAAC;;0CAEnD,KAAChB;gCACCiB,MAAK;gCACLC,SAAQ;gCACRC,MAAK;gCACLC,SAASb,OAAOI,KAAK;0CAEpBL,EAAE;;;;kCAIP,KAACL;wBACCoB,mBAAmBjB,OAAOiB,iBAAiB;wBAC3CC,WAAWf,OAAOe,SAAS;wBAC3BC,OAAOhB,OAAOgB,KAAK;wBACnBC,OAAOjB,OAAOiB,KAAK;wBACnBC,YAAYlB,OAAOkB,UAAU;wBAC7BC,aAAanB,OAAOmB,WAAW;wBAC/BC,UAAUpB,OAAOoB,QAAQ;wBACzBC,cAAcrB,OAAOqB,YAAY;wBACjCC,kBAAkBtB,OAAOsB,gBAAgB;wBACzCC,WAAWvB,OAAOuB,SAAS;wBAC3BC,SAASxB,OAAOwB,OAAO;wBACvBC,mBAAmBzB,OAAOyB,iBAAiB;wBAC3CC,iBAAiB1B,OAAO0B,eAAe;wBACvC5B,UAAUA;wBACV,qEAAqE;wBACrE,2DAA2D;wBAC3D6B,gBAAgB,CAAC,YAAY,EAAE/B,WAAWgC,IAAI,EAAE;;;;;;AAK1D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/media-gallery/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/media-gallery/index.ts"],"sourcesContent":["export * from './media-gallery.js';\n"],"names":[],"mappings":"AAAA,cAAc,qBAAqB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
2
|
+
export interface MediaGalleryItem {
|
|
3
|
+
/** Unique within the gallery; the slide's element id is derived from it. */
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly src: string;
|
|
6
|
+
/** What the image shows. Empty only for an image that adds nothing to the text beside it. */
|
|
7
|
+
readonly alt: string;
|
|
8
|
+
}
|
|
9
|
+
export interface MediaGalleryLabels {
|
|
10
|
+
/** The gallery's name — usually the product's. */
|
|
11
|
+
readonly region: string;
|
|
12
|
+
readonly previous: string;
|
|
13
|
+
readonly next: string;
|
|
14
|
+
/** A slide's position, e.g. `2 of 5`. */
|
|
15
|
+
readonly position: (index: number, total: number) => string;
|
|
16
|
+
}
|
|
17
|
+
export interface MediaGalleryProps extends Omit<ComponentPropsWithoutRef<'div'>, 'children'> {
|
|
18
|
+
items: readonly MediaGalleryItem[];
|
|
19
|
+
labels: MediaGalleryLabels;
|
|
20
|
+
/** Renders one image; a plain `<img>` when omitted (a Next host passes `next/image`). */
|
|
21
|
+
renderMedia?: (item: MediaGalleryItem) => ReactNode;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Product media as a horizontal scroller that works with scripting off and
|
|
25
|
+
* with a keyboard. The scroller is a named region and a Tab stop, so arrow
|
|
26
|
+
* keys scroll it (WCAG 2.1.1, axe `scrollable-region-focusable`); each slide is
|
|
27
|
+
* a figure with its position, and its previous and next links jump to the
|
|
28
|
+
* neighbouring slide's id, which the browser scrolls into view. Server-safe.
|
|
29
|
+
*/
|
|
30
|
+
export declare function MediaGallery({ items, labels, renderMedia, className, ...props }: MediaGalleryProps): import("react").JSX.Element;
|
|
31
|
+
//# sourceMappingURL=media-gallery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-gallery.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/media-gallery/media-gallery.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIjE,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,6FAA6F;IAC7F,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yCAAyC;IACzC,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CAC7D;AAED,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAC7C,wBAAwB,CAAC,KAAK,CAAC,EAC/B,UAAU,CACX;IACC,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACnC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,yFAAyF;IACzF,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,SAAS,CAAC;CACrD;AAOD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,MAAM,EACN,WAMC,EACD,SAAS,EACT,GAAG,KAAK,EACT,EAAE,iBAAiB,+BA8CnB"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
|
|
3
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { cn } from '../../utils/cn.js';
|
|
5
|
+
const slideId = (item)=>`media-${item.id}`;
|
|
6
|
+
const STEP = 'flex min-h-(--target-min) items-center rounded-md px-2xs text-step-sm text-content focus-ring hover:underline';
|
|
7
|
+
/**
|
|
8
|
+
* Product media as a horizontal scroller that works with scripting off and
|
|
9
|
+
* with a keyboard. The scroller is a named region and a Tab stop, so arrow
|
|
10
|
+
* keys scroll it (WCAG 2.1.1, axe `scrollable-region-focusable`); each slide is
|
|
11
|
+
* a figure with its position, and its previous and next links jump to the
|
|
12
|
+
* neighbouring slide's id, which the browser scrolls into view. Server-safe.
|
|
13
|
+
*/ export function MediaGallery(_0) {
|
|
14
|
+
let { items, labels, renderMedia = (item)=>/*#__PURE__*/ _jsx("img", {
|
|
15
|
+
src: item.src,
|
|
16
|
+
alt: item.alt,
|
|
17
|
+
className: "h-auto w-full object-contain"
|
|
18
|
+
}), className } = _0, props = _object_without_properties_loose(_0, [
|
|
19
|
+
"items",
|
|
20
|
+
"labels",
|
|
21
|
+
"renderMedia",
|
|
22
|
+
"className"
|
|
23
|
+
]);
|
|
24
|
+
return /*#__PURE__*/ _jsx("div", _extends({
|
|
25
|
+
role: "region",
|
|
26
|
+
"aria-label": labels.region,
|
|
27
|
+
// A scroller must take focus to be scrolled from the keyboard.
|
|
28
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
29
|
+
tabIndex: 0,
|
|
30
|
+
className: cn('flex snap-x snap-mandatory gap-s overflow-x-auto scroll-smooth rounded-lg focus-ring motion-reduce:scroll-auto', className)
|
|
31
|
+
}, props, {
|
|
32
|
+
children: items.map((item, index)=>{
|
|
33
|
+
const previous = items[index - 1];
|
|
34
|
+
const next = items[index + 1];
|
|
35
|
+
return /*#__PURE__*/ _jsxs("figure", {
|
|
36
|
+
id: slideId(item),
|
|
37
|
+
className: "m-0 flex w-full shrink-0 snap-center flex-col gap-2xs",
|
|
38
|
+
children: [
|
|
39
|
+
renderMedia(item),
|
|
40
|
+
/*#__PURE__*/ _jsxs("figcaption", {
|
|
41
|
+
className: "flex items-center justify-between gap-s text-step-xs text-content-muted",
|
|
42
|
+
children: [
|
|
43
|
+
previous ? /*#__PURE__*/ _jsx("a", {
|
|
44
|
+
href: `#${slideId(previous)}`,
|
|
45
|
+
className: STEP,
|
|
46
|
+
children: labels.previous
|
|
47
|
+
}) : /*#__PURE__*/ _jsx("span", {}),
|
|
48
|
+
/*#__PURE__*/ _jsx("span", {
|
|
49
|
+
children: labels.position(index + 1, items.length)
|
|
50
|
+
}),
|
|
51
|
+
next ? /*#__PURE__*/ _jsx("a", {
|
|
52
|
+
href: `#${slideId(next)}`,
|
|
53
|
+
className: STEP,
|
|
54
|
+
children: labels.next
|
|
55
|
+
}) : /*#__PURE__*/ _jsx("span", {})
|
|
56
|
+
]
|
|
57
|
+
})
|
|
58
|
+
]
|
|
59
|
+
}, item.id);
|
|
60
|
+
})
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=media-gallery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/media-gallery/media-gallery.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef, ReactNode } from 'react';\n\nimport { cn } from '../../utils/cn.js';\n\nexport interface MediaGalleryItem {\n /** Unique within the gallery; the slide's element id is derived from it. */\n readonly id: string;\n readonly src: string;\n /** What the image shows. Empty only for an image that adds nothing to the text beside it. */\n readonly alt: string;\n}\n\nexport interface MediaGalleryLabels {\n /** The gallery's name — usually the product's. */\n readonly region: string;\n readonly previous: string;\n readonly next: string;\n /** A slide's position, e.g. `2 of 5`. */\n readonly position: (index: number, total: number) => string;\n}\n\nexport interface MediaGalleryProps extends Omit<\n ComponentPropsWithoutRef<'div'>,\n 'children'\n> {\n items: readonly MediaGalleryItem[];\n labels: MediaGalleryLabels;\n /** Renders one image; a plain `<img>` when omitted (a Next host passes `next/image`). */\n renderMedia?: (item: MediaGalleryItem) => ReactNode;\n}\n\nconst slideId = (item: MediaGalleryItem) => `media-${item.id}`;\n\nconst STEP =\n 'flex min-h-(--target-min) items-center rounded-md px-2xs text-step-sm text-content focus-ring hover:underline';\n\n/**\n * Product media as a horizontal scroller that works with scripting off and\n * with a keyboard. The scroller is a named region and a Tab stop, so arrow\n * keys scroll it (WCAG 2.1.1, axe `scrollable-region-focusable`); each slide is\n * a figure with its position, and its previous and next links jump to the\n * neighbouring slide's id, which the browser scrolls into view. Server-safe.\n */\nexport function MediaGallery({\n items,\n labels,\n renderMedia = item => (\n <img\n src={item.src}\n alt={item.alt}\n className=\"h-auto w-full object-contain\"\n />\n ),\n className,\n ...props\n}: MediaGalleryProps) {\n return (\n <div\n role=\"region\"\n aria-label={labels.region}\n // A scroller must take focus to be scrolled from the keyboard.\n // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex\n tabIndex={0}\n className={cn(\n 'flex snap-x snap-mandatory gap-s overflow-x-auto scroll-smooth rounded-lg focus-ring motion-reduce:scroll-auto',\n className,\n )}\n {...props}\n >\n {items.map((item, index) => {\n const previous = items[index - 1];\n const next = items[index + 1];\n return (\n <figure\n key={item.id}\n id={slideId(item)}\n className=\"m-0 flex w-full shrink-0 snap-center flex-col gap-2xs\"\n >\n {renderMedia(item)}\n <figcaption className=\"flex items-center justify-between gap-s text-step-xs text-content-muted\">\n {previous ? (\n <a href={`#${slideId(previous)}`} className={STEP}>\n {labels.previous}\n </a>\n ) : (\n <span />\n )}\n <span>{labels.position(index + 1, items.length)}</span>\n {next ? (\n <a href={`#${slideId(next)}`} className={STEP}>\n {labels.next}\n </a>\n ) : (\n <span />\n )}\n </figcaption>\n </figure>\n );\n })}\n </div>\n );\n}\n"],"names":["cn","slideId","item","id","STEP","MediaGallery","items","labels","renderMedia","img","src","alt","className","props","div","role","aria-label","region","tabIndex","map","index","previous","next","figure","figcaption","a","href","span","position","length"],"mappings":";;;AAEA,SAASA,EAAE,QAAQ,oBAAoB;AA6BvC,MAAMC,UAAU,CAACC,OAA2B,CAAC,MAAM,EAAEA,KAAKC,EAAE,EAAE;AAE9D,MAAMC,OACJ;AAEF;;;;;;CAMC,GACD,OAAO,SAASC,aAAa,EAYT;QAZS,EAC3BC,KAAK,EACLC,MAAM,EACNC,cAAcN,CAAAA,qBACZ,KAACO;YACCC,KAAKR,KAAKQ,GAAG;YACbC,KAAKT,KAAKS,GAAG;YACbC,WAAU;UAEb,EACDA,SAAS,EAES,OADfC;;;;;;IAEH,qBACE,KAACC;QACCC,MAAK;QACLC,cAAYT,OAAOU,MAAM;QACzB,+DAA+D;QAC/D,+DAA+D;QAC/DC,UAAU;QACVN,WAAWZ,GACT,kHACAY;OAEEC;kBAEHP,MAAMa,GAAG,CAAC,CAACjB,MAAMkB;YAChB,MAAMC,WAAWf,KAAK,CAACc,QAAQ,EAAE;YACjC,MAAME,OAAOhB,KAAK,CAACc,QAAQ,EAAE;YAC7B,qBACE,MAACG;gBAECpB,IAAIF,QAAQC;gBACZU,WAAU;;oBAETJ,YAAYN;kCACb,MAACsB;wBAAWZ,WAAU;;4BACnBS,yBACC,KAACI;gCAAEC,MAAM,CAAC,CAAC,EAAEzB,QAAQoB,WAAW;gCAAET,WAAWR;0CAC1CG,OAAOc,QAAQ;+CAGlB,KAACM;0CAEH,KAACA;0CAAMpB,OAAOqB,QAAQ,CAACR,QAAQ,GAAGd,MAAMuB,MAAM;;4BAC7CP,qBACC,KAACG;gCAAEC,MAAM,CAAC,CAAC,EAAEzB,QAAQqB,OAAO;gCAAEV,WAAWR;0CACtCG,OAAOe,IAAI;+CAGd,KAACK;;;;eAnBAzB,KAAKC,EAAE;QAwBlB;;AAGN"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/menu/menu.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,wBAAwB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIjE,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,SAAS,+BAStD;yBATe,IAAI;2CAWsB,wBAAwB,CAAC,QAAQ,CAAC;0CAiBzE;QACD,QAAQ,EAAE,SAAS,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;8CAwCwD,aAAa;
|
|
1
|
+
{"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/menu/menu.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,wBAAwB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIjE,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,SAAS,+BAStD;yBATe,IAAI;2CAWsB,wBAAwB,CAAC,QAAQ,CAAC;0CAiBzE;QACD,QAAQ,EAAE,SAAS,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;8CAwCwD,aAAa;wCA2B/B,wBAAwB,CAAC,GAAG,CAAC;;AAlDpE,8EAA8E;AAC9E,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,aAAa,CAAC;AAErD,MAAM,WAAW,aAAc,SAAQ,wBAAwB,CAAC,QAAQ,CAAC;IACvE;;;;;;;;;;OAUG;IACH,IAAI,CAAC,EAAE,YAAY,CAAC;CACrB"}
|
|
@@ -20,12 +20,12 @@ function Trigger(_0) {
|
|
|
20
20
|
"className"
|
|
21
21
|
]);
|
|
22
22
|
return /*#__PURE__*/ _jsx(MenuButton, _extends({
|
|
23
|
-
className: cn('inline-flex items-center gap-2xs rounded-lg px-2xs py-3xs text-step-sm text-content', 'transition duration-200 ease-smooth hover:bg-surface', 'focus-ring', className)
|
|
23
|
+
className: cn('inline-flex min-h-(--target-min) min-w-(--target-min) items-center gap-2xs rounded-lg px-2xs py-3xs text-step-sm text-content', 'transition duration-200 ease-smooth hover:bg-surface', 'focus-ring', className)
|
|
24
24
|
}, props));
|
|
25
25
|
}
|
|
26
26
|
function Items({ children, className }) {
|
|
27
27
|
return /*#__PURE__*/ _jsx(MenuItems, {
|
|
28
|
-
className: cn('right-0 absolute z-
|
|
28
|
+
className: cn('right-0 min-w-48 absolute z-(--z-dropdown) mt-2xs rounded-lg border border-border bg-surface-elevated p-3xs shadow-overlay', // The panel itself is a focus-managed CONTAINER, not a target: the ring
|
|
29
29
|
// belongs on the item inside it. Stripping the outline here is
|
|
30
30
|
// correct, and is not a missing `focus-ring`.
|
|
31
31
|
'focus:outline-none', className),
|
|
@@ -43,7 +43,9 @@ function Item(_0) {
|
|
|
43
43
|
]);
|
|
44
44
|
return /*#__PURE__*/ _jsx(HuiMenuItem, _extends({
|
|
45
45
|
as: "button",
|
|
46
|
-
className: cn('flex w-full items-center gap-2xs rounded-md px-2xs py-3xs text-step-sm',
|
|
46
|
+
className: cn('flex min-h-(--target-min) w-full items-center gap-2xs rounded-md px-2xs py-3xs text-step-sm', // The item under the keyboard wears the focus ring, inset so the
|
|
47
|
+
// panel's edge never clips it; a background swap alone is ~1.05:1.
|
|
48
|
+
'focus-ring data-focus:bg-surface data-focus:-outline-offset-2', ITEM_TONE_CLASS[tone], 'disabled:cursor-not-allowed disabled:opacity-50', className)
|
|
47
49
|
}, props));
|
|
48
50
|
}
|
|
49
51
|
/**
|
|
@@ -60,7 +62,7 @@ function Item(_0) {
|
|
|
60
62
|
]);
|
|
61
63
|
return /*#__PURE__*/ _jsx(HuiMenuItem, _extends({
|
|
62
64
|
as: "a",
|
|
63
|
-
className: cn('flex w-full items-center gap-2xs rounded-md px-2xs py-3xs text-step-sm text-content no-underline', 'data-focus:bg-surface data-focus:text-content', className)
|
|
65
|
+
className: cn('flex min-h-(--target-min) w-full items-center gap-2xs rounded-md px-2xs py-3xs text-step-sm text-content no-underline', 'focus-ring data-focus:bg-surface data-focus:text-content data-focus:-outline-offset-2', className)
|
|
64
66
|
}, props));
|
|
65
67
|
}
|
|
66
68
|
Menu.Trigger = Trigger;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/ui/molecules/menu/menu.tsx"],"sourcesContent":["'use client';\n\nimport {\n Menu as HuiMenu,\n MenuButton,\n MenuItem as HuiMenuItem,\n MenuItems,\n} from '@headlessui/react';\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react';\n\nimport { cn } from '../../utils/cn.js';\n\nexport interface MenuProps {\n children: ReactNode;\n className?: string;\n}\n\n/**\n * A dropdown menu — the user context menu in the top bar, and the tab-overflow\n * list. A thin styled wrapper over Headless UI so focus management, keyboard\n * nav, and outside-click come for free; the tokens keep it on-brand.\n */\nexport function Menu({ children, className }: MenuProps) {\n return (\n <HuiMenu\n as=\"div\"\n className={cn('relative inline-block text-left', className)}\n >\n {children}\n </HuiMenu>\n );\n}\n\nfunction Trigger({ className, ...props }: ComponentPropsWithoutRef<'button'>) {\n return (\n <MenuButton\n className={cn(\n 'inline-flex items-center gap-2xs rounded-lg px-2xs py-3xs text-step-sm text-content',\n 'transition duration-200 ease-smooth hover:bg-surface',\n 'focus-ring',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction Items({\n children,\n className,\n}: {\n children: ReactNode;\n className?: string;\n}) {\n return (\n <MenuItems\n className={cn(\n 'right-0 absolute z-
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/menu/menu.tsx"],"sourcesContent":["'use client';\n\nimport {\n Menu as HuiMenu,\n MenuButton,\n MenuItem as HuiMenuItem,\n MenuItems,\n} from '@headlessui/react';\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react';\n\nimport { cn } from '../../utils/cn.js';\n\nexport interface MenuProps {\n children: ReactNode;\n className?: string;\n}\n\n/**\n * A dropdown menu — the user context menu in the top bar, and the tab-overflow\n * list. A thin styled wrapper over Headless UI so focus management, keyboard\n * nav, and outside-click come for free; the tokens keep it on-brand.\n */\nexport function Menu({ children, className }: MenuProps) {\n return (\n <HuiMenu\n as=\"div\"\n className={cn('relative inline-block text-left', className)}\n >\n {children}\n </HuiMenu>\n );\n}\n\nfunction Trigger({ className, ...props }: ComponentPropsWithoutRef<'button'>) {\n return (\n <MenuButton\n className={cn(\n 'inline-flex min-h-(--target-min) min-w-(--target-min) items-center gap-2xs rounded-lg px-2xs py-3xs text-step-sm text-content',\n 'transition duration-200 ease-smooth hover:bg-surface',\n 'focus-ring',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction Items({\n children,\n className,\n}: {\n children: ReactNode;\n className?: string;\n}) {\n return (\n <MenuItems\n className={cn(\n 'right-0 min-w-48 absolute z-(--z-dropdown) mt-2xs rounded-lg border border-border bg-surface-elevated p-3xs shadow-overlay',\n // The panel itself is a focus-managed CONTAINER, not a target: the ring\n // belongs on the item inside it. Stripping the outline here is\n // correct, and is not a missing `focus-ring`.\n 'focus:outline-none',\n className,\n )}\n >\n {children}\n </MenuItems>\n );\n}\n\n/** How an entry reads: an ordinary action, or one that destroys something. */\nexport type MenuItemTone = 'neutral' | 'destructive';\n\nexport interface MenuItemProps extends ComponentPropsWithoutRef<'button'> {\n /**\n * `destructive` colours the entry as a deletion.\n *\n * It exists because an overflow menu is where a destructive verb ends up once\n * the visible actions are full, and a `Button` in that position would carry\n * `variant=\"destructive\"` — a menu that cannot say the same thing makes the\n * appearance of a verb depend on how many siblings it happens to have.\n *\n * Colour is not the affordance, and must not be the only warning: a verb\n * whose descriptor carries `confirm` still asks before it fires.\n */\n tone?: MenuItemTone;\n}\n\nconst ITEM_TONE_CLASS: Record<MenuItemTone, string> = {\n neutral: 'text-content data-focus:text-content',\n destructive: 'text-danger data-focus:text-danger',\n};\n\nfunction Item({ className, tone = 'neutral', ...props }: MenuItemProps) {\n return (\n <HuiMenuItem\n as=\"button\"\n className={cn(\n 'flex min-h-(--target-min) w-full items-center gap-2xs rounded-md px-2xs py-3xs text-step-sm',\n // The item under the keyboard wears the focus ring, inset so the\n // panel's edge never clips it; a background swap alone is ~1.05:1.\n 'focus-ring data-focus:bg-surface data-focus:-outline-offset-2',\n ITEM_TONE_CLASS[tone],\n 'disabled:cursor-not-allowed disabled:opacity-50',\n className,\n )}\n {...props}\n />\n );\n}\n\n/**\n * A navigating menu entry.\n *\n * A plain anchor rather than a router link: the entries that need this are the\n * account menu's, and those point at a different app on a different origin,\n * where a client-side transition is not an option anyway. Callers wanting an\n * in-app transition should pass their router's link through `as`-style\n * composition at the call site instead.\n */\nfunction Link({ className, ...props }: ComponentPropsWithoutRef<'a'>) {\n return (\n <HuiMenuItem\n as=\"a\"\n className={cn(\n 'flex min-h-(--target-min) w-full items-center gap-2xs rounded-md px-2xs py-3xs text-step-sm text-content no-underline',\n 'focus-ring data-focus:bg-surface data-focus:text-content data-focus:-outline-offset-2',\n className,\n )}\n {...props}\n />\n );\n}\n\nMenu.Trigger = Trigger;\nMenu.Items = Items;\nMenu.Item = Item;\nMenu.Link = Link;\n"],"names":["Menu","HuiMenu","MenuButton","MenuItem","HuiMenuItem","MenuItems","cn","children","className","as","Trigger","props","Items","ITEM_TONE_CLASS","neutral","destructive","Item","tone","Link"],"mappings":"AAAA;;;;AAEA,SACEA,QAAQC,OAAO,EACfC,UAAU,EACVC,YAAYC,WAAW,EACvBC,SAAS,QACJ,oBAAoB;AAG3B,SAASC,EAAE,QAAQ,oBAAoB;AAOvC;;;;CAIC,GACD,OAAO,SAASN,KAAK,EAAEO,QAAQ,EAAEC,SAAS,EAAa;IACrD,qBACE,KAACP;QACCQ,IAAG;QACHD,WAAWF,GAAG,mCAAmCE;kBAEhDD;;AAGP;AAEA,SAASG,QAAQ,EAA2D;QAA3D,EAAEF,SAAS,EAAgD,OAA3CG;;;IAC/B,qBACE,KAACT;QACCM,WAAWF,GACT,iIACA,wDACA,cACAE;OAEEG;AAGV;AAEA,SAASC,MAAM,EACbL,QAAQ,EACRC,SAAS,EAIV;IACC,qBACE,KAACH;QACCG,WAAWF,GACT,8HACA,wEAAwE;QACxE,+DAA+D;QAC/D,8CAA8C;QAC9C,sBACAE;kBAGDD;;AAGP;AAoBA,MAAMM,kBAAgD;IACpDC,SAAS;IACTC,aAAa;AACf;AAEA,SAASC,KAAK,EAAwD;QAAxD,EAAER,SAAS,EAAES,OAAO,SAAS,EAA2B,OAAtBN;;;;IAC9C,qBACE,KAACP;QACCK,IAAG;QACHD,WAAWF,GACT,+FACA,iEAAiE;QACjE,mEAAmE;QACnE,iEACAO,eAAe,CAACI,KAAK,EACrB,mDACAT;OAEEG;AAGV;AAEA;;;;;;;;CAQC,GACD,SAASO,KAAK,EAAsD;QAAtD,EAAEV,SAAS,EAA2C,OAAtCG;;;IAC5B,qBACE,KAACP;QACCK,IAAG;QACHD,WAAWF,GACT,yHACA,yFACAE;OAEEG;AAGV;AAEAX,KAAKU,OAAO,GAAGA;AACfV,KAAKY,KAAK,GAAGA;AACbZ,KAAKgB,IAAI,GAAGA;AACZhB,KAAKkB,IAAI,GAAGA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from '../../utils/cn.js';
|
|
3
|
-
/** A bar's trigger: quiet until hovered or open. */ const TRIGGER = 'focus-ring flex h-
|
|
3
|
+
/** A bar's trigger: quiet until hovered or open. */ const TRIGGER = 'focus-ring flex h-8 cursor-pointer list-none items-center gap-3xs rounded-md border border-transparent px-2xs text-step-sm font-medium text-content select-none transition-[background-color,border-color] duration-(--duration-fast,150ms) hover:border-border hover:bg-[color-mix(in_srgb,var(--color-content)_6%,transparent)] [&::-webkit-details-marker]:hidden [[open]>&]:border-border [[open]>&]:bg-[color-mix(in_srgb,var(--color-content)_6%,transparent)]';
|
|
4
4
|
/**
|
|
5
5
|
* A dropdown in a navigation bar: a native `<details>` disclosure, so it opens
|
|
6
6
|
* and its links work with scripting off. What a dropdown is expected to do on
|
|
@@ -31,7 +31,7 @@ import { cn } from '../../utils/cn.js';
|
|
|
31
31
|
"aria-hidden": "true",
|
|
32
32
|
viewBox: "0 0 24 24",
|
|
33
33
|
"data-slot": "nav-menu-chevron",
|
|
34
|
-
className: "size-
|
|
34
|
+
className: "size-3.5 flex-none fill-none stroke-current stroke-2 text-content-muted transition-[rotate] duration-(--duration-fast,150ms) [stroke-linecap:round] [stroke-linejoin:round] [[open]>summary>&]:rotate-180",
|
|
35
35
|
children: /*#__PURE__*/ _jsx("path", {
|
|
36
36
|
d: "m7 10 5 5 5-5"
|
|
37
37
|
})
|
|
@@ -40,7 +40,7 @@ import { cn } from '../../utils/cn.js';
|
|
|
40
40
|
}),
|
|
41
41
|
/*#__PURE__*/ _jsx("div", {
|
|
42
42
|
"data-slot": "nav-menu-panel",
|
|
43
|
-
className: "right-0 ease-out starting:-translate-y-1 [&_ul]:m-0 [&_ul]:p-0 absolute top-[calc(100%+var(--spacing-3xs))] z-
|
|
43
|
+
className: "right-0 ease-out starting:-translate-y-1 [&_ul]:m-0 [&_ul]:p-0 min-w-40 absolute top-[calc(100%+var(--spacing-3xs))] z-(--z-dropdown) rounded-lg border border-border bg-surface-elevated p-3xs shadow-overlay transition-[opacity,translate] duration-(--duration-fast) motion-reduce:transition-none starting:opacity-0 [&_ul]:list-none",
|
|
44
44
|
children: children
|
|
45
45
|
})
|
|
46
46
|
]
|
|
@@ -51,7 +51,7 @@ import { cn } from '../../utils/cn.js';
|
|
|
51
51
|
"aria-hidden": "true",
|
|
52
52
|
viewBox: "0 0 24 24",
|
|
53
53
|
"data-slot": "nav-menu-check",
|
|
54
|
-
className: "size-
|
|
54
|
+
className: "size-4 flex-none fill-none stroke-current stroke-2 [stroke-linecap:round] [stroke-linejoin:round]",
|
|
55
55
|
children: checked && /*#__PURE__*/ _jsx("path", {
|
|
56
56
|
d: "m5 12.5 4.5 4.5L19 7.5"
|
|
57
57
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/ui/molecules/nav-menu/nav-menu.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\n\nimport { cn } from '../../utils/cn.js';\n\n/** A bar's trigger: quiet until hovered or open. */\nconst TRIGGER =\n 'focus-ring flex h-
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/nav-menu/nav-menu.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\n\nimport { cn } from '../../utils/cn.js';\n\n/** A bar's trigger: quiet until hovered or open. */\nconst TRIGGER =\n 'focus-ring flex h-8 cursor-pointer list-none items-center gap-3xs rounded-md border border-transparent px-2xs text-step-sm font-medium text-content select-none transition-[background-color,border-color] duration-(--duration-fast,150ms) hover:border-border hover:bg-[color-mix(in_srgb,var(--color-content)_6%,transparent)] [&::-webkit-details-marker]:hidden [[open]>&]:border-border [[open]>&]:bg-[color-mix(in_srgb,var(--color-content)_6%,transparent)]';\n\n/**\n * A dropdown in a navigation bar: a native `<details>` disclosure, so it opens\n * and its links work with scripting off. What a dropdown is expected to do on\n * top — one open at a time, closed by a click elsewhere or Escape — is the\n * page's to add.\n *\n * No hooks, so a client component can render it too. Its parts carry\n * `data-slot` (`nav-menu`, `nav-menu-trigger`, `nav-menu-chevron`,\n * `nav-menu-panel`), so a page can restyle them — a panel as wide as the bar\n * on a phone, say — without knowing the classes underneath.\n */\nexport function NavMenu({\n label,\n icon,\n summary,\n chevron = true,\n className,\n triggerClassName = TRIGGER,\n children,\n}: {\n /** The accessible name of the trigger. */\n label: string;\n icon: ReactNode;\n /** What the trigger shows beside the icon, if anything. */\n summary?: ReactNode;\n /** A chevron after the trigger's content: a menu of choices has one. */\n chevron?: boolean;\n className?: string;\n /** Replaces the trigger's look: a split button draws it as its own segment. */\n triggerClassName?: string;\n children: ReactNode;\n}) {\n return (\n <details data-slot=\"nav-menu\" className={cn('relative', className)}>\n {/* The name is the label followed by the summary, so what a sighted\n user reads on the trigger (\"EN\") is part of what is announced and\n what a voice user says (WCAG 2.5.3), rather than replaced by it. */}\n <summary\n title={label}\n data-slot=\"nav-menu-trigger\"\n className={triggerClassName}\n >\n <span className=\"sr-only\">{label}</span>\n {icon}\n {summary}\n {chevron && (\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 24 24\"\n data-slot=\"nav-menu-chevron\"\n className=\"size-3.5 flex-none fill-none stroke-current stroke-2 text-content-muted transition-[rotate] duration-(--duration-fast,150ms) [stroke-linecap:round] [stroke-linejoin:round] [[open]>summary>&]:rotate-180\"\n >\n <path d=\"m7 10 5 5 5-5\" />\n </svg>\n )}\n </summary>\n <div\n data-slot=\"nav-menu-panel\"\n className=\"right-0 ease-out starting:-translate-y-1 [&_ul]:m-0 [&_ul]:p-0 min-w-40 absolute top-[calc(100%+var(--spacing-3xs))] z-(--z-dropdown) rounded-lg border border-border bg-surface-elevated p-3xs shadow-overlay transition-[opacity,translate] duration-(--duration-fast) motion-reduce:transition-none starting:opacity-0 [&_ul]:list-none\"\n >\n {children}\n </div>\n </details>\n );\n}\n\n/** A check beside the chosen entry; an equal space beside the others. */\nexport function NavMenuCheck({ checked }: { checked: boolean }) {\n return (\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 24 24\"\n data-slot=\"nav-menu-check\"\n className=\"size-4 flex-none fill-none stroke-current stroke-2 [stroke-linecap:round] [stroke-linejoin:round]\"\n >\n {checked && <path d=\"m5 12.5 4.5 4.5L19 7.5\" />}\n </svg>\n );\n}\n"],"names":["cn","TRIGGER","NavMenu","label","icon","summary","chevron","className","triggerClassName","children","details","data-slot","title","span","svg","aria-hidden","viewBox","path","d","div","NavMenuCheck","checked"],"mappings":";AAEA,SAASA,EAAE,QAAQ,oBAAoB;AAEvC,kDAAkD,GAClD,MAAMC,UACJ;AAEF;;;;;;;;;;CAUC,GACD,OAAO,SAASC,QAAQ,EACtBC,KAAK,EACLC,IAAI,EACJC,OAAO,EACPC,UAAU,IAAI,EACdC,SAAS,EACTC,mBAAmBP,OAAO,EAC1BQ,QAAQ,EAaT;IACC,qBACE,MAACC;QAAQC,aAAU;QAAWJ,WAAWP,GAAG,YAAYO;;0BAItD,MAACF;gBACCO,OAAOT;gBACPQ,aAAU;gBACVJ,WAAWC;;kCAEX,KAACK;wBAAKN,WAAU;kCAAWJ;;oBAC1BC;oBACAC;oBACAC,yBACC,KAACQ;wBACCC,eAAY;wBACZC,SAAQ;wBACRL,aAAU;wBACVJ,WAAU;kCAEV,cAAA,KAACU;4BAAKC,GAAE;;;;;0BAId,KAACC;gBACCR,aAAU;gBACVJ,WAAU;0BAETE;;;;AAIT;AAEA,uEAAuE,GACvE,OAAO,SAASW,aAAa,EAAEC,OAAO,EAAwB;IAC5D,qBACE,KAACP;QACCC,eAAY;QACZC,SAAQ;QACRL,aAAU;QACVJ,WAAU;kBAETc,yBAAW,KAACJ;YAAKC,GAAE;;;AAG1B"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* (a choice in the page): one bordered track, the chosen segment filled.
|
|
4
4
|
*/
|
|
5
5
|
/** The joined segments; scrolls sideways rather than wrapping. */
|
|
6
|
-
export declare const LIST = "m-0 flex w-max max-w-full list-none overflow-x-auto rounded-md border border-border p-
|
|
7
|
-
export declare const OTHER = "block rounded-[calc(var(--radius-md,0.375rem)-0.125rem)] px-xs py-
|
|
8
|
-
export declare const CURRENT = "block rounded-[calc(var(--radius-md,0.375rem)-0.125rem)] px-xs py-
|
|
6
|
+
export declare const LIST = "m-0 flex w-max max-w-full list-none overflow-x-auto rounded-md border border-border p-0.75 [scrollbar-width:none]";
|
|
7
|
+
export declare const OTHER = "block rounded-[calc(var(--radius-md,0.375rem)-0.125rem)] px-xs py-1.25 text-step-sm whitespace-nowrap no-underline transition-colors duration-(--duration-fast,150ms) motion-reduce:transition-none focus-ring text-content hover:bg-[color-mix(in_srgb,var(--color-content)_6%,transparent)] hover:text-primary";
|
|
8
|
+
export declare const CURRENT = "block rounded-[calc(var(--radius-md,0.375rem)-0.125rem)] px-xs py-1.25 text-step-sm whitespace-nowrap no-underline transition-colors duration-(--duration-fast,150ms) motion-reduce:transition-none bg-primary font-medium text-primary-content";
|
|
9
9
|
//# sourceMappingURL=segment-classes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"segment-classes.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/segmented-nav/segment-classes.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,kEAAkE;AAClE,eAAO,MAAM,IAAI,
|
|
1
|
+
{"version":3,"file":"segment-classes.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/segmented-nav/segment-classes.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,kEAAkE;AAClE,eAAO,MAAM,IAAI,sHACoG,CAAC;AAKtH,eAAO,MAAM,KAAK,qTAA4H,CAAC;AAE/I,eAAO,MAAM,OAAO,oPAA2D,CAAC"}
|