@eifi1/ui-kit 0.11.0 → 0.12.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 +57 -32
- package/dist/chart.d.ts +3 -1
- package/dist/chart.js +2 -0
- package/dist/chart.js.map +1 -1
- package/dist/components/account-settings-labels.d.ts +105 -0
- package/dist/components/account-settings-labels.js +81 -0
- package/dist/components/account-settings-labels.js.map +1 -0
- package/dist/components/account-settings.d.ts +36 -52
- package/dist/components/account-settings.js +44 -8
- package/dist/components/account-settings.js.map +1 -1
- package/dist/components/amount-input.d.ts +24 -4
- package/dist/components/amount-input.js +5 -2
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/authed-image.d.ts +68 -0
- package/dist/components/authed-image.js +117 -0
- package/dist/components/authed-image.js.map +1 -0
- package/dist/components/breadcrumbs.d.ts +2 -1
- package/dist/components/breadcrumbs.js +5 -2
- package/dist/components/breadcrumbs.js.map +1 -1
- package/dist/components/button-group.d.ts +51 -2
- package/dist/components/button-group.js +5 -1
- package/dist/components/button-group.js.map +1 -1
- package/dist/components/calculator.d.ts +15 -4
- package/dist/components/calendar-heatmap.d.ts +41 -3
- package/dist/components/calendar-heatmap.js +48 -7
- package/dist/components/calendar-heatmap.js.map +1 -1
- package/dist/components/chip.d.ts +2 -1
- package/dist/components/chip.js +6 -3
- package/dist/components/chip.js.map +1 -1
- package/dist/components/choice-card.d.ts +35 -3
- package/dist/components/choice-card.js +83 -25
- package/dist/components/choice-card.js.map +1 -1
- package/dist/components/combobox-core.js +2 -0
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +2 -2
- package/dist/components/combobox.js +31 -9
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/copy-button.d.ts +51 -70
- package/dist/components/copy-button.js +1 -1
- package/dist/components/copy-button.js.map +1 -1
- package/dist/components/data-table-cells.d.ts +63 -0
- package/dist/components/data-table-cells.js +64 -0
- package/dist/components/data-table-cells.js.map +1 -0
- package/dist/components/data-table-filter-popover.d.ts +1 -1
- package/dist/components/data-table-filters.d.ts +1 -1
- package/dist/components/data-table-filters.js +31 -1
- package/dist/components/data-table-filters.js.map +1 -1
- package/dist/components/data-table-labels.d.ts +13 -0
- package/dist/components/data-table-labels.js +8 -1
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table.d.ts +1 -1
- package/dist/components/data-table.js +102 -12
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +3 -1
- package/dist/components/date-picker.js +15 -2
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/description-list.d.ts +23 -3
- package/dist/components/description-list.js +23 -3
- package/dist/components/description-list.js.map +1 -1
- package/dist/components/dialog-frame.d.ts +1 -0
- package/dist/components/entity-combobox.d.ts +1 -1
- package/dist/components/entity-combobox.js +18 -3
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/error-boundary.d.ts +92 -0
- package/dist/components/error-boundary.js +111 -0
- package/dist/components/error-boundary.js.map +1 -0
- package/dist/components/field.d.ts +30 -4
- package/dist/components/field.js +40 -17
- package/dist/components/field.js.map +1 -1
- package/dist/components/file-button.d.ts +50 -208
- package/dist/components/file-dropzone.d.ts +49 -2
- package/dist/components/floating-panel.d.ts +13 -4
- package/dist/components/floating-panel.js +7 -3
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/form-actions.d.ts +53 -0
- package/dist/components/form-actions.js +98 -0
- package/dist/components/form-actions.js.map +1 -0
- package/dist/components/image-grid.d.ts +67 -0
- package/dist/components/image-grid.js +112 -0
- package/dist/components/image-grid.js.map +1 -0
- package/dist/components/lightbox.d.ts +104 -0
- package/dist/components/lightbox.js +229 -0
- package/dist/components/lightbox.js.map +1 -0
- package/dist/components/line-items.d.ts +114 -0
- package/dist/components/line-items.js +238 -0
- package/dist/components/line-items.js.map +1 -0
- package/dist/components/list.d.ts +23 -2
- package/dist/components/list.js +32 -13
- package/dist/components/list.js.map +1 -1
- package/dist/components/loading-state.d.ts +24 -0
- package/dist/components/loading-state.js +32 -0
- package/dist/components/loading-state.js.map +1 -0
- package/dist/components/menu-item.d.ts +2 -1
- package/dist/components/menu-item.js +5 -1
- package/dist/components/menu-item.js.map +1 -1
- package/dist/components/mini-calendar.d.ts +13 -1
- package/dist/components/mini-calendar.js +46 -2
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.d.ts +20 -1
- package/dist/components/modal.js +26 -2
- package/dist/components/modal.js.map +1 -1
- package/dist/components/month-picker.d.ts +21 -1
- package/dist/components/month-picker.js +139 -26
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.d.ts +1 -1
- package/dist/components/multi-entity-combobox.js +19 -3
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/nav-pills.d.ts +2 -1
- package/dist/components/nav-pills.js +5 -1
- package/dist/components/nav-pills.js.map +1 -1
- package/dist/components/number-field.d.ts +24 -5
- package/dist/components/number-field.js +5 -7
- package/dist/components/number-field.js.map +1 -1
- package/dist/components/number-input.d.ts +25 -5
- package/dist/components/number-input.js +7 -1
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +15 -4
- package/dist/components/passkeys-setting.d.ts +67 -0
- package/dist/components/passkeys-setting.js +214 -0
- package/dist/components/passkeys-setting.js.map +1 -0
- package/dist/components/picker-sheet.js +2 -1
- package/dist/components/picker-sheet.js.map +1 -1
- package/dist/components/pie-chart-labels.d.ts +20 -0
- package/dist/components/pie-chart-labels.js +12 -0
- package/dist/components/pie-chart-labels.js.map +1 -0
- package/dist/components/pie-chart.d.ts +101 -0
- package/dist/components/pie-chart.js +283 -0
- package/dist/components/pie-chart.js.map +1 -0
- package/dist/components/progress-bar.d.ts +44 -3
- package/dist/components/progress-bar.js +58 -22
- package/dist/components/progress-bar.js.map +1 -1
- package/dist/components/qr-code.d.ts +34 -0
- package/dist/components/qr-code.js +57 -0
- package/dist/components/qr-code.js.map +1 -0
- package/dist/components/search-field.d.ts +15 -3
- package/dist/components/search-field.js +5 -1
- package/dist/components/search-field.js.map +1 -1
- package/dist/components/settings-fields.d.ts +49 -1
- package/dist/components/signed-amount.d.ts +121 -0
- package/dist/components/signed-amount.js +152 -0
- package/dist/components/signed-amount.js.map +1 -0
- package/dist/components/skeleton.d.ts +10 -3
- package/dist/components/skeleton.js +12 -2
- package/dist/components/skeleton.js.map +1 -1
- package/dist/components/stat-tile.d.ts +2 -1
- package/dist/components/stat-tile.js +5 -2
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/table.d.ts +39 -5
- package/dist/components/table.js +45 -11
- package/dist/components/table.js.map +1 -1
- package/dist/components/text-link.d.ts +176 -0
- package/dist/components/text-link.js +109 -0
- package/dist/components/text-link.js.map +1 -0
- package/dist/components/time-input.d.ts +49 -1
- package/dist/components/toggle-group.d.ts +13 -0
- package/dist/components/toggle-group.js +35 -6
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/toggle-legend.d.ts +36 -3
- package/dist/components/toggle-legend.js +49 -0
- package/dist/components/toggle-legend.js.map +1 -1
- package/dist/components/ui.d.ts +51 -779
- package/dist/components/ui.js +215 -23
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-table-state.d.ts +3 -52
- package/dist/components/use-table-state.js +128 -30
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/{data-table-filters-Dh9uF_S-.d.ts → data-table-Drun8Fk9.d.ts} +305 -61
- package/dist/data-table.d.ts +2 -1
- package/dist/data-table.js +5 -1
- package/dist/data-table.js.map +1 -1
- package/dist/feedback/feedback-dialog.d.ts +26 -3
- package/dist/feedback/feedback-dialog.js +16 -2
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +133 -0
- package/dist/feedback/feedback-thread.js +237 -0
- package/dist/feedback/feedback-thread.js.map +1 -0
- package/dist/feedback.d.ts +2 -1
- package/dist/feedback.js +1 -0
- package/dist/feedback.js.map +1 -1
- package/dist/hooks/use-authed-src.d.ts +71 -0
- package/dist/hooks/use-authed-src.js +63 -0
- package/dist/hooks/use-authed-src.js.map +1 -0
- package/dist/hooks/use-file-drop.d.ts +49 -2
- package/dist/hooks/use-hotkey.d.ts +59 -0
- package/dist/hooks/use-hotkey.js +105 -0
- package/dist/hooks/use-hotkey.js.map +1 -0
- package/dist/hooks/use-search-param-state.d.ts +83 -0
- package/dist/hooks/use-search-param-state.js +94 -0
- package/dist/hooks/use-search-param-state.js.map +1 -0
- package/dist/i18n/defaults.d.ts +15 -4
- package/dist/i18n/defaults.js +31 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +46 -312
- package/dist/i18n/kit-labels.js +20 -5
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/de-CH-informal.d.ts +15 -4
- package/dist/i18n/locales/de-CH.d.ts +15 -4
- package/dist/i18n/locales/de-informal.d.ts +15 -4
- package/dist/i18n/locales/de.d.ts +15 -4
- package/dist/i18n/locales/de.js +156 -3
- package/dist/i18n/locales/de.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +15 -4
- package/dist/i18n/locales/es.js +153 -3
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +15 -4
- package/dist/i18n/locales/fr.js +153 -3
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +15 -4
- package/dist/i18n/locales/hu.js +153 -3
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +15 -4
- package/dist/i18n/locales/it.js +153 -3
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +15 -4
- package/dist/i18n/locales/zh.js +153 -3
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/index.d.ts +37 -17
- package/dist/index.js +40 -1
- package/dist/index.js.map +1 -1
- package/dist/kit-labels-v3biUF1L.d.ts +1645 -0
- package/dist/lib/format.d.ts +144 -0
- package/dist/lib/format.js +149 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/lib/qr-encode.d.ts +44 -0
- package/dist/lib/qr-encode.js +338 -0
- package/dist/lib/qr-encode.js.map +1 -0
- package/dist/rhf/fields.d.ts +241 -0
- package/dist/rhf/fields.js +601 -0
- package/dist/rhf/fields.js.map +1 -0
- package/dist/rhf/form.d.ts +49 -1
- package/dist/rhf/line-items.d.ts +31 -0
- package/dist/rhf/line-items.js +59 -0
- package/dist/rhf/line-items.js.map +1 -0
- package/dist/rhf.d.ts +61 -1
- package/dist/rhf.js +2 -0
- package/dist/rhf.js.map +1 -1
- package/dist/shell/app-shell.d.ts +99 -2
- package/dist/shell/app-shell.js +58 -3
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/auth-layout.d.ts +74 -0
- package/dist/shell/auth-layout.js +97 -0
- package/dist/shell/auth-layout.js.map +1 -0
- package/dist/shell/top-bar-brand.d.ts +81 -0
- package/dist/shell/top-bar-brand.js +47 -0
- package/dist/shell/top-bar-brand.js.map +1 -0
- package/dist/shell/topbar-action-menu.d.ts +12 -1
- package/dist/shell/topbar-action-menu.js +16 -1
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell.d.ts +56 -1
- package/dist/shell.js +2 -0
- package/dist/shell.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +48 -1
- package/dist/wizard/wizard-context.d.ts +24 -1
- package/dist/wizard/wizard-context.js +19 -2
- package/dist/wizard/wizard-context.js.map +1 -1
- package/dist/wizard/wizard-step.d.ts +20 -5
- package/dist/wizard/wizard-step.js +16 -3
- package/dist/wizard/wizard-step.js.map +1 -1
- package/dist/wizard.d.ts +50 -3
- package/package.json +1 -1
- package/src/chart.ts +4 -0
- package/src/components/account-settings-labels.ts +190 -0
- package/src/components/account-settings.tsx +110 -63
- package/src/components/amount-input.tsx +13 -1
- package/src/components/authed-image.tsx +190 -0
- package/src/components/breadcrumbs.tsx +8 -2
- package/src/components/button-group.tsx +7 -2
- package/src/components/calendar-heatmap.tsx +105 -10
- package/src/components/chip.tsx +8 -4
- package/src/components/choice-card.tsx +136 -25
- package/src/components/combobox-core.tsx +5 -0
- package/src/components/combobox.tsx +31 -9
- package/src/components/copy-button.tsx +3 -2
- package/src/components/data-table-cells.tsx +119 -0
- package/src/components/data-table-filters.ts +77 -0
- package/src/components/data-table-labels.ts +23 -0
- package/src/components/data-table.tsx +224 -13
- package/src/components/date-picker.tsx +19 -3
- package/src/components/description-list.tsx +52 -3
- package/src/components/entity-combobox.tsx +18 -3
- package/src/components/error-boundary.tsx +216 -0
- package/src/components/field.tsx +56 -5
- package/src/components/floating-panel.tsx +20 -7
- package/src/components/form-actions.tsx +191 -0
- package/src/components/image-grid.tsx +181 -0
- package/src/components/lightbox.tsx +374 -0
- package/src/components/line-items.tsx +385 -0
- package/src/components/list.tsx +65 -16
- package/src/components/loading-state.tsx +47 -0
- package/src/components/menu-item.tsx +7 -2
- package/src/components/mini-calendar.tsx +72 -3
- package/src/components/modal.tsx +61 -3
- package/src/components/month-picker.tsx +181 -26
- package/src/components/multi-entity-combobox.tsx +19 -3
- package/src/components/nav-pills.tsx +8 -1
- package/src/components/number-field.tsx +15 -10
- package/src/components/number-input.tsx +16 -1
- package/src/components/passkeys-setting.tsx +331 -0
- package/src/components/picker-sheet.tsx +7 -1
- package/src/components/pie-chart-labels.ts +32 -0
- package/src/components/pie-chart.tsx +479 -0
- package/src/components/progress-bar.tsx +129 -19
- package/src/components/qr-code.tsx +83 -0
- package/src/components/search-field.tsx +20 -4
- package/src/components/signed-amount.tsx +286 -0
- package/src/components/skeleton.tsx +21 -3
- package/src/components/stat-tile.tsx +8 -2
- package/src/components/table.tsx +102 -13
- package/src/components/text-link.tsx +248 -0
- package/src/components/toggle-group.tsx +58 -10
- package/src/components/toggle-legend.tsx +87 -2
- package/src/components/ui.tsx +426 -36
- package/src/components/use-table-state.ts +265 -33
- package/src/data-table.ts +10 -0
- package/src/feedback/feedback-dialog.tsx +42 -3
- package/src/feedback/feedback-thread.tsx +434 -0
- package/src/feedback.ts +1 -0
- package/src/hooks/use-authed-src.ts +161 -0
- package/src/hooks/use-hotkey.ts +172 -0
- package/src/hooks/use-search-param-state.ts +187 -0
- package/src/i18n/defaults.ts +30 -0
- package/src/i18n/kit-labels.tsx +102 -8
- package/src/i18n/locales/de.ts +153 -0
- package/src/i18n/locales/es.ts +150 -0
- package/src/i18n/locales/fr.ts +153 -0
- package/src/i18n/locales/hu.ts +151 -0
- package/src/i18n/locales/it.ts +151 -0
- package/src/i18n/locales/zh.ts +150 -0
- package/src/index.ts +59 -0
- package/src/lib/format.ts +325 -0
- package/src/lib/qr-encode.ts +420 -0
- package/src/rhf/fields.tsx +1054 -0
- package/src/rhf/line-items.tsx +130 -0
- package/src/rhf.ts +6 -0
- package/src/shell/app-shell.tsx +147 -2
- package/src/shell/auth-layout.tsx +190 -0
- package/src/shell/top-bar-brand.tsx +76 -0
- package/src/shell/topbar-action-menu.tsx +21 -0
- package/src/shell.ts +2 -0
- package/src/wizard/wizard-context.tsx +41 -1
- package/src/wizard/wizard-step.tsx +43 -6
- package/tokens.css +12 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/authed-image.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ImgHTMLAttributes, ReactNode } from \"react\";\nimport { ImageOff } from \"lucide-react\";\n\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { SKELETON_CLASS } from \"./skeleton\";\nimport { useAuthedSrc } from \"../hooks/use-authed-src\";\nimport type { AuthedFetcher, UseAuthedSrcResult } from \"../hooks/use-authed-src\";\n\n/** The two states an image can be in besides \"there\". */\nexport interface AuthedImageLabels {\n /** Read out while the bytes are on their way (the skeleton itself is silent). */\n loading: string;\n /** Shown, small, in place of an image that could not be fetched or decoded. */\n loadError: string;\n /** The failed tile's accessible name, given the image's alt. */\n failedImage: (alt: string) => string;\n}\n\nexport const DEFAULT_AUTHED_IMAGE_LABELS: AuthedImageLabels = {\n loading: \"Loading image…\",\n loadError: \"Image couldn’t be loaded\",\n failedImage: (alt) => `${alt}: image couldn’t be loaded`,\n};\n\nexport interface AuthedImageProps\n extends Omit<ImgHTMLAttributes<HTMLImageElement>, \"src\" | \"alt\" | \"children\"> {\n /** The image's URL — an API path when a {@link fetcher} is given. */\n src: string | null | undefined;\n /** Required: an image without a text alternative is a gap a reader falls into.\n * `\"\"` for a purely decorative one. */\n alt: string;\n /** The app's authenticated GET (see {@link useAuthedSrc}). Left out, `src` is loaded\n * by the browser as usual and this is an `<img>` with loading and error states. */\n fetcher?: AuthedFetcher | null;\n /** Classes for the box around the image (the placeholders fill it). */\n wrapperClassName?: string;\n /**\n * Wrap the image in a link to itself, opening in a new tab — kastlan's\n * `AuthedImage` did, because an object URL is the only address of a protected image\n * a browser tab can open. The link's href is the `blob:` URL, never the API path.\n */\n link?: boolean;\n /** Replaces the default skeleton while loading. */\n loadingFallback?: ReactNode;\n /** Replaces the default \"couldn't load\" tile. */\n errorFallback?: ReactNode;\n labels?: Partial<AuthedImageLabels>;\n}\n\n/**\n * An image with a loading state, an error state and — given a `fetcher` — the app's\n * authentication. Replaces kastlan's `shared/components/authed-image.tsx`, which\n * rendered NOTHING until the blob arrived and nothing at all if it never did: a card\n * with a photo slot either had the photo or was silently shorter.\n *\n * Two failures are one state here: the fetch failing (401, 404, offline) and the\n * browser failing to decode what arrived (`onError` on the `<img>`, e.g. a PDF served\n * where an image was expected).\n */\nexport function AuthedImage({\n src,\n alt,\n fetcher,\n wrapperClassName,\n className,\n link,\n loadingFallback,\n errorFallback,\n labels: labelsProp,\n onLoad,\n onError,\n ...rest\n}: AuthedImageProps) {\n const labels = useKitLabels(\"authedImage\", DEFAULT_AUTHED_IMAGE_LABELS, labelsProp);\n const fetched = useAuthedSrc(src, { fetcher });\n return (\n <ImageStates\n fetched={fetched}\n alt={alt}\n labels={labels}\n className={className}\n wrapperClassName={wrapperClassName}\n link={link}\n loadingFallback={loadingFallback}\n errorFallback={errorFallback}\n imgProps={rest}\n onLoad={onLoad}\n onError={onError}\n />\n );\n}\n\n/**\n * The rendering half of {@link AuthedImage}, for a caller that already holds a\n * {@link useAuthedSrc} result (the lightbox needs the object URL for its download link\n * as well). Internal to the package.\n */\nexport function ImageStates({\n fetched,\n alt,\n labels,\n className,\n wrapperClassName,\n link,\n loadingFallback,\n errorFallback,\n imgProps,\n onLoad,\n onError,\n}: {\n fetched: UseAuthedSrcResult;\n alt: string;\n labels: AuthedImageLabels;\n className?: string;\n wrapperClassName?: string;\n link?: boolean;\n loadingFallback?: ReactNode;\n errorFallback?: ReactNode;\n imgProps?: Omit<ImgHTMLAttributes<HTMLImageElement>, \"src\" | \"alt\" | \"children\" | \"onLoad\" | \"onError\">;\n onLoad?: ImgHTMLAttributes<HTMLImageElement>[\"onLoad\"];\n onError?: ImgHTMLAttributes<HTMLImageElement>[\"onError\"];\n}) {\n // Decode state, per src: keyed so a new picture starts over rather than inheriting\n // the previous one's \"loaded\" or \"broken\".\n const [decode, setDecode] = useState<{ src: string | null; state: \"loaded\" | \"error\" } | null>(null);\n const decoded = decode && decode.src === fetched.src ? decode.state : \"pending\";\n const failed = fetched.status === \"error\" || decoded === \"error\";\n const waiting = !failed && (fetched.status === \"loading\" || (fetched.src !== null && decoded === \"pending\"));\n\n const img = fetched.src !== null && !failed && (\n // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- load/error are resource events, not interactions\n <img\n {...imgProps}\n src={fetched.src}\n alt={alt}\n onLoad={(e) => {\n setDecode({ src: fetched.src, state: \"loaded\" });\n onLoad?.(e);\n }}\n onError={(e) => {\n setDecode({ src: fetched.src, state: \"error\" });\n onError?.(e);\n }}\n className={cn(\n // Kept in the tree while it decodes (the browser needs it there to load at\n // all), but invisible, so the skeleton is what shows.\n decoded === \"pending\" && \"opacity-0\",\n className,\n )}\n />\n );\n\n return (\n <span\n data-image-state={failed ? \"error\" : waiting ? \"loading\" : fetched.status === \"idle\" ? \"idle\" : \"ready\"}\n className={cn(\"relative block overflow-hidden\", wrapperClassName)}\n aria-busy={waiting || undefined}\n >\n {link && img && fetched.src ? (\n <a href={fetched.src} target=\"_blank\" rel=\"noreferrer noopener\" className=\"block h-full w-full\">\n {img}\n </a>\n ) : (\n img\n )}\n {waiting &&\n (loadingFallback ?? (\n <span className={cn(SKELETON_CLASS, \"absolute inset-0 block rounded-none\")}>\n <span className=\"sr-only\">{labels.loading}</span>\n </span>\n ))}\n {failed &&\n (errorFallback ?? (\n <span\n // `role=\"img\"` with the alt as its name: the picture that failed still\n // occupies its place in the reading order, and \"couldn't be loaded\" is\n // the one fact a reader needs about it.\n role=\"img\"\n aria-label={alt ? labels.failedImage(alt) : labels.loadError}\n className=\"flex h-full min-h-16 w-full flex-col items-center justify-center gap-1 bg-[var(--bg-hover)] p-2 text-center text-xs text-[var(--text-muted)]\"\n >\n <ImageOff aria-hidden className=\"size-5 shrink-0\" />\n <span aria-hidden className=\"line-clamp-2\">{labels.loadError}</span>\n </span>\n ))}\n </span>\n );\n}\n"],"mappings":";AA8EI,cAiGM,YAjGN;AA9EJ,SAAS,gBAAgB;AAEzB,SAAS,gBAAgB;AAEzB,SAAS,UAAU;AACnB,SAAS,oBAAoB;AAC7B,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAatB,MAAM,8BAAiD;AAAA,EAC5D,SAAS;AAAA,EACT,WAAW;AAAA,EACX,aAAa,CAAC,QAAQ,GAAG,GAAG;AAC9B;AAqCO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAqB;AACnB,QAAM,SAAS,aAAa,eAAe,6BAA6B,UAAU;AAClF,QAAM,UAAU,aAAa,KAAK,EAAE,QAAQ,CAAC;AAC7C,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV;AAAA,MACA;AAAA;AAAA,EACF;AAEJ;AAOO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAYG;AAGD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAmE,IAAI;AACnG,QAAM,UAAU,UAAU,OAAO,QAAQ,QAAQ,MAAM,OAAO,QAAQ;AACtE,QAAM,SAAS,QAAQ,WAAW,WAAW,YAAY;AACzD,QAAM,UAAU,CAAC,WAAW,QAAQ,WAAW,aAAc,QAAQ,QAAQ,QAAQ,YAAY;AAEjG,QAAM,MAAM,QAAQ,QAAQ,QAAQ,CAAC;AAAA,EAEnC;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,KAAK,QAAQ;AAAA,MACb;AAAA,MACA,QAAQ,CAAC,MAAM;AACb,kBAAU,EAAE,KAAK,QAAQ,KAAK,OAAO,SAAS,CAAC;AAC/C,iBAAS,CAAC;AAAA,MACZ;AAAA,MACA,SAAS,CAAC,MAAM;AACd,kBAAU,EAAE,KAAK,QAAQ,KAAK,OAAO,QAAQ,CAAC;AAC9C,kBAAU,CAAC;AAAA,MACb;AAAA,MACA,WAAW;AAAA;AAAA;AAAA,QAGT,YAAY,aAAa;AAAA,QACzB;AAAA,MACF;AAAA;AAAA,EACF;AAGF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,oBAAkB,SAAS,UAAU,UAAU,YAAY,QAAQ,WAAW,SAAS,SAAS;AAAA,MAChG,WAAW,GAAG,kCAAkC,gBAAgB;AAAA,MAChE,aAAW,WAAW;AAAA,MAErB;AAAA,gBAAQ,OAAO,QAAQ,MACtB,oBAAC,OAAE,MAAM,QAAQ,KAAK,QAAO,UAAS,KAAI,uBAAsB,WAAU,uBACvE,eACH,IAEA;AAAA,QAED,YACE,mBACC,oBAAC,UAAK,WAAW,GAAG,gBAAgB,qCAAqC,GACvE,8BAAC,UAAK,WAAU,WAAW,iBAAO,SAAQ,GAC5C;AAAA,QAEH,WACE,iBACC;AAAA,UAAC;AAAA;AAAA,YAIC,MAAK;AAAA,YACL,cAAY,MAAM,OAAO,YAAY,GAAG,IAAI,OAAO;AAAA,YACnD,WAAU;AAAA,YAEV;AAAA,kCAAC,YAAS,eAAW,MAAC,WAAU,mBAAkB;AAAA,cAClD,oBAAC,UAAK,eAAW,MAAC,WAAU,gBAAgB,iBAAO,WAAU;AAAA;AAAA;AAAA,QAC/D;AAAA;AAAA;AAAA,EAEN;AAEJ;","names":[]}
|
|
@@ -25,7 +25,8 @@ interface BreadcrumbLinkProps {
|
|
|
25
25
|
}
|
|
26
26
|
interface BreadcrumbsProps extends Omit<ComponentPropsWithoutRef<"nav">, "children"> {
|
|
27
27
|
items: BreadcrumbItem[];
|
|
28
|
-
/** Your router's link. Default `<
|
|
28
|
+
/** Your router's link. Default: the `<UiKitProvider linkComponent>` (for an in-app
|
|
29
|
+
* `href`), then `<a>`. The API `Chip`, `StatTile` and `ListItem` share. */
|
|
29
30
|
renderLink?: (props: BreadcrumbLinkProps) => ReactElement;
|
|
30
31
|
/**
|
|
31
32
|
* The mark between crumbs. Default a chevron, mirrored in RTL — a chevron pointing
|
|
@@ -3,7 +3,8 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { Fragment, useEffect, useRef, useState } from "react";
|
|
4
4
|
import { ChevronRight, MoreHorizontal } from "lucide-react";
|
|
5
5
|
import { cn } from "../lib/cn.js";
|
|
6
|
-
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
6
|
+
import { useKitLabels, useKitLink } from "../i18n/kit-labels.js";
|
|
7
|
+
import { pickLinkRenderer, RenderedKitLink } from "./text-link.js";
|
|
7
8
|
const DEFAULT_BREADCRUMBS_LABELS = {
|
|
8
9
|
label: "Breadcrumb",
|
|
9
10
|
showAll: "Show full path"
|
|
@@ -20,6 +21,7 @@ function Breadcrumbs({
|
|
|
20
21
|
...rest
|
|
21
22
|
}) {
|
|
22
23
|
const labels = useKitLabels("breadcrumbs", DEFAULT_BREADCRUMBS_LABELS, labelsProp);
|
|
24
|
+
const kitLink = useKitLink();
|
|
23
25
|
const [expanded, setExpanded] = useState(false);
|
|
24
26
|
const revealedRef = useRef(null);
|
|
25
27
|
const focusRevealed = useRef(false);
|
|
@@ -44,7 +46,8 @@ function Breadcrumbs({
|
|
|
44
46
|
content = /* @__PURE__ */ jsx("span", { "aria-current": "page", className: "block max-w-[16rem] truncate font-medium text-[var(--text-primary)]", children: item.label });
|
|
45
47
|
} else if (item.href !== void 0) {
|
|
46
48
|
const props = { href: item.href, className: linkClass, children: item.label };
|
|
47
|
-
|
|
49
|
+
const kit = pickLinkRenderer(void 0, kitLink, item.href);
|
|
50
|
+
content = renderLink ? renderLink(props) : kit ? /* @__PURE__ */ jsx(RenderedKitLink, { render: kit, props }) : /* @__PURE__ */ jsx("a", { href: props.href, className: props.className, children: props.children });
|
|
48
51
|
} else {
|
|
49
52
|
content = /* @__PURE__ */ jsx("span", { className: "block max-w-[14rem] truncate", children: item.label });
|
|
50
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/breadcrumbs.tsx"],"sourcesContent":["import { Fragment, useEffect, useRef, useState } from \"react\";\nimport type { ComponentPropsWithoutRef, ReactElement, ReactNode } from \"react\";\nimport { ChevronRight, MoreHorizontal } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\n\nexport interface BreadcrumbsLabels {\n /** The `nav` landmark's name — what a screen reader lists it as. */\n label: string;\n /** The \"…\" button that reveals the levels collapsed away on a phone. */\n showAll: string;\n}\n\nexport const DEFAULT_BREADCRUMBS_LABELS: BreadcrumbsLabels = {\n label: \"Breadcrumb\",\n showAll: \"Show full path\",\n};\n\nexport interface BreadcrumbItem {\n label: ReactNode;\n /** Where the crumb leads. A crumb without one is text (a level with no page of its\n * own). The LAST crumb is the current page and is text whether or not it has one. */\n href?: string;\n /** A stable React key, when `label` is not a string. */\n key?: string;\n}\n\n/** What {@link BreadcrumbsProps.renderLink} is handed. Spread it onto your router's\n * link — `({ href, ...p }) => <Link to={href} {...p} />`. */\nexport interface BreadcrumbLinkProps {\n href: string;\n className: string;\n children: ReactNode;\n}\n\nexport interface BreadcrumbsProps extends Omit<ComponentPropsWithoutRef<\"nav\">, \"children\"> {\n items: BreadcrumbItem[];\n /** Your router's link. Default `<a>`. The API `Chip`, `StatTile` and `ListItem` share. */\n renderLink?: (props: BreadcrumbLinkProps) => ReactElement;\n /**\n * The mark between crumbs. Default a chevron, mirrored in RTL — a chevron pointing\n * right in a right-to-left trail points back toward where you came from. Supply your\n * own and it is drawn as given (a \"/\" needs no mirroring).\n */\n separator?: ReactNode;\n /**\n * Collapse the middle on phones (below the `md` breakpoint) into a \"…\" button, keeping\n * the first crumb and the last {@link keepEnd}. Default `true`. kastlan's trail\n * scrolled sideways on a phone instead, which hides the current page — the one crumb\n * that must never be off-screen.\n */\n collapse?: boolean;\n /** How many crumbs at the end stay visible when collapsed — the current page and its\n * parent by default (2), because \"up one level\" is what a phone user reaches for. */\n keepEnd?: number;\n /** Overrides the {@link UiKitProvider}'s `breadcrumbs` labels. */\n labels?: Partial<BreadcrumbsLabels>;\n}\n\n/**\n * The trail from the app's root to this page.\n *\n * `nav` + `ol` + `aria-current=\"page\"` on the last crumb: the WAI breadcrumb pattern,\n * which kastlan's (shared/components/layout/breadcrumbs.tsx) nearly was — a `nav` of\n * `span`s, so a screen reader heard no list, no count, and nothing marking where you\n * are. The collapse is CSS (`hidden md:flex`), so it costs no media-query listener and\n * the server's first paint is already right.\n */\nexport function Breadcrumbs({\n items,\n renderLink,\n separator,\n collapse = true,\n keepEnd = 2,\n labels: labelsProp,\n className,\n \"aria-label\": ariaLabel,\n ...rest\n}: BreadcrumbsProps) {\n const labels = useKitLabels(\"breadcrumbs\", DEFAULT_BREADCRUMBS_LABELS, labelsProp);\n const [expanded, setExpanded] = useState(false);\n // The \"…\" button vanishes when pressed; focus goes to the first crumb it revealed,\n // not back to the top of the document.\n const revealedRef = useRef<HTMLLIElement>(null);\n const focusRevealed = useRef(false);\n useEffect(() => {\n if (!expanded || !focusRevealed.current) return;\n focusRevealed.current = false;\n const li = revealedRef.current;\n (li?.querySelector<HTMLElement>(\"a[href]\") ?? li)?.focus();\n }, [expanded]);\n if (items.length === 0) return null;\n\n const end = Math.max(1, keepEnd);\n // Crumbs [1, firstKept) are the collapsible middle. Collapsing a single crumb into a\n // \"…\" saves nothing, so it takes two.\n const firstKept = Math.max(1, items.length - end);\n const collapsible = collapse && !expanded && firstKept - 1 >= 2;\n const isHidden = (i: number) => collapsible && i >= 1 && i < firstKept;\n\n const sep = (\n <span aria-hidden className=\"flex shrink-0 items-center text-[var(--text-placeholder)]\">\n {separator ?? <ChevronRight className=\"size-3.5 rtl:-scale-x-100\" />}\n </span>\n );\n const linkClass =\n \"block max-w-[14rem] truncate rounded-sm hover:text-[var(--text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand)]\";\n\n return (\n <nav {...rest} aria-label={ariaLabel ?? labels.label} className={cn(\"min-w-0 text-sm\", className)}>\n <ol className=\"m-0 flex min-w-0 list-none flex-wrap items-center gap-x-1.5 gap-y-1 p-0 text-[var(--text-muted)]\">\n {items.map((item, i) => {\n const last = i === items.length - 1;\n const key = item.key ?? (typeof item.label === \"string\" ? `${i}-${item.label}` : String(i));\n let content: ReactNode;\n if (last) {\n content = (\n <span aria-current=\"page\" className=\"block max-w-[16rem] truncate font-medium text-[var(--text-primary)]\">\n {item.label}\n </span>\n );\n } else if (item.href !== undefined) {\n const props: BreadcrumbLinkProps = { href: item.href, className: linkClass, children: item.label };\n content = renderLink ? (\n renderLink(props)\n ) : (\n <a href={props.href} className={props.className}>\n {props.children}\n </a>\n );\n } else {\n content = <span className=\"block max-w-[14rem] truncate\">{item.label}</span>;\n }\n return (\n <Fragment key={key}>\n {collapsible && i === 1 && (\n <li className=\"flex items-center gap-1.5 md:hidden\">\n {sep}\n <button\n type=\"button\"\n onClick={() => {\n focusRevealed.current = true;\n setExpanded(true);\n }}\n aria-label={labels.showAll}\n aria-expanded={false}\n className=\"flex items-center rounded-sm px-0.5 hover:text-[var(--text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand)]\"\n >\n <MoreHorizontal className=\"size-4\" aria-hidden />\n </button>\n </li>\n )}\n <li\n ref={i === 1 ? revealedRef : undefined}\n // Focusable only as the landing place for a revealed crumb with no link.\n tabIndex={i === 1 && expanded && item.href === undefined ? -1 : undefined}\n className={cn(\"flex min-w-0 items-center gap-1.5 outline-none\", isHidden(i) && \"hidden md:flex\")}\n >\n {i > 0 && sep}\n {content}\n </li>\n </Fragment>\n );\n })}\n </ol>\n </nav>\n );\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../src/components/breadcrumbs.tsx"],"sourcesContent":["import { Fragment, useEffect, useRef, useState } from \"react\";\nimport type { ComponentPropsWithoutRef, ReactElement, ReactNode } from \"react\";\nimport { ChevronRight, MoreHorizontal } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels, useKitLink } from \"../i18n/kit-labels\";\nimport { pickLinkRenderer, RenderedKitLink } from \"./text-link\";\n\nexport interface BreadcrumbsLabels {\n /** The `nav` landmark's name — what a screen reader lists it as. */\n label: string;\n /** The \"…\" button that reveals the levels collapsed away on a phone. */\n showAll: string;\n}\n\nexport const DEFAULT_BREADCRUMBS_LABELS: BreadcrumbsLabels = {\n label: \"Breadcrumb\",\n showAll: \"Show full path\",\n};\n\nexport interface BreadcrumbItem {\n label: ReactNode;\n /** Where the crumb leads. A crumb without one is text (a level with no page of its\n * own). The LAST crumb is the current page and is text whether or not it has one. */\n href?: string;\n /** A stable React key, when `label` is not a string. */\n key?: string;\n}\n\n/** What {@link BreadcrumbsProps.renderLink} is handed. Spread it onto your router's\n * link — `({ href, ...p }) => <Link to={href} {...p} />`. */\nexport interface BreadcrumbLinkProps {\n href: string;\n className: string;\n children: ReactNode;\n}\n\nexport interface BreadcrumbsProps extends Omit<ComponentPropsWithoutRef<\"nav\">, \"children\"> {\n items: BreadcrumbItem[];\n /** Your router's link. Default: the `<UiKitProvider linkComponent>` (for an in-app\n * `href`), then `<a>`. The API `Chip`, `StatTile` and `ListItem` share. */\n renderLink?: (props: BreadcrumbLinkProps) => ReactElement;\n /**\n * The mark between crumbs. Default a chevron, mirrored in RTL — a chevron pointing\n * right in a right-to-left trail points back toward where you came from. Supply your\n * own and it is drawn as given (a \"/\" needs no mirroring).\n */\n separator?: ReactNode;\n /**\n * Collapse the middle on phones (below the `md` breakpoint) into a \"…\" button, keeping\n * the first crumb and the last {@link keepEnd}. Default `true`. kastlan's trail\n * scrolled sideways on a phone instead, which hides the current page — the one crumb\n * that must never be off-screen.\n */\n collapse?: boolean;\n /** How many crumbs at the end stay visible when collapsed — the current page and its\n * parent by default (2), because \"up one level\" is what a phone user reaches for. */\n keepEnd?: number;\n /** Overrides the {@link UiKitProvider}'s `breadcrumbs` labels. */\n labels?: Partial<BreadcrumbsLabels>;\n}\n\n/**\n * The trail from the app's root to this page.\n *\n * `nav` + `ol` + `aria-current=\"page\"` on the last crumb: the WAI breadcrumb pattern,\n * which kastlan's (shared/components/layout/breadcrumbs.tsx) nearly was — a `nav` of\n * `span`s, so a screen reader heard no list, no count, and nothing marking where you\n * are. The collapse is CSS (`hidden md:flex`), so it costs no media-query listener and\n * the server's first paint is already right.\n */\nexport function Breadcrumbs({\n items,\n renderLink,\n separator,\n collapse = true,\n keepEnd = 2,\n labels: labelsProp,\n className,\n \"aria-label\": ariaLabel,\n ...rest\n}: BreadcrumbsProps) {\n const labels = useKitLabels(\"breadcrumbs\", DEFAULT_BREADCRUMBS_LABELS, labelsProp);\n const kitLink = useKitLink();\n const [expanded, setExpanded] = useState(false);\n // The \"…\" button vanishes when pressed; focus goes to the first crumb it revealed,\n // not back to the top of the document.\n const revealedRef = useRef<HTMLLIElement>(null);\n const focusRevealed = useRef(false);\n useEffect(() => {\n if (!expanded || !focusRevealed.current) return;\n focusRevealed.current = false;\n const li = revealedRef.current;\n (li?.querySelector<HTMLElement>(\"a[href]\") ?? li)?.focus();\n }, [expanded]);\n if (items.length === 0) return null;\n\n const end = Math.max(1, keepEnd);\n // Crumbs [1, firstKept) are the collapsible middle. Collapsing a single crumb into a\n // \"…\" saves nothing, so it takes two.\n const firstKept = Math.max(1, items.length - end);\n const collapsible = collapse && !expanded && firstKept - 1 >= 2;\n const isHidden = (i: number) => collapsible && i >= 1 && i < firstKept;\n\n const sep = (\n <span aria-hidden className=\"flex shrink-0 items-center text-[var(--text-placeholder)]\">\n {separator ?? <ChevronRight className=\"size-3.5 rtl:-scale-x-100\" />}\n </span>\n );\n const linkClass =\n \"block max-w-[14rem] truncate rounded-sm hover:text-[var(--text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand)]\";\n\n return (\n <nav {...rest} aria-label={ariaLabel ?? labels.label} className={cn(\"min-w-0 text-sm\", className)}>\n <ol className=\"m-0 flex min-w-0 list-none flex-wrap items-center gap-x-1.5 gap-y-1 p-0 text-[var(--text-muted)]\">\n {items.map((item, i) => {\n const last = i === items.length - 1;\n const key = item.key ?? (typeof item.label === \"string\" ? `${i}-${item.label}` : String(i));\n let content: ReactNode;\n if (last) {\n content = (\n <span aria-current=\"page\" className=\"block max-w-[16rem] truncate font-medium text-[var(--text-primary)]\">\n {item.label}\n </span>\n );\n } else if (item.href !== undefined) {\n const props: BreadcrumbLinkProps = { href: item.href, className: linkClass, children: item.label };\n const kit = pickLinkRenderer<BreadcrumbLinkProps>(undefined, kitLink, item.href);\n content = renderLink ? (\n renderLink(props)\n ) : kit ? (\n <RenderedKitLink render={kit} props={props} />\n ) : (\n <a href={props.href} className={props.className}>\n {props.children}\n </a>\n );\n } else {\n content = <span className=\"block max-w-[14rem] truncate\">{item.label}</span>;\n }\n return (\n <Fragment key={key}>\n {collapsible && i === 1 && (\n <li className=\"flex items-center gap-1.5 md:hidden\">\n {sep}\n <button\n type=\"button\"\n onClick={() => {\n focusRevealed.current = true;\n setExpanded(true);\n }}\n aria-label={labels.showAll}\n aria-expanded={false}\n className=\"flex items-center rounded-sm px-0.5 hover:text-[var(--text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand)]\"\n >\n <MoreHorizontal className=\"size-4\" aria-hidden />\n </button>\n </li>\n )}\n <li\n ref={i === 1 ? revealedRef : undefined}\n // Focusable only as the landing place for a revealed crumb with no link.\n tabIndex={i === 1 && expanded && item.href === undefined ? -1 : undefined}\n className={cn(\"flex min-w-0 items-center gap-1.5 outline-none\", isHidden(i) && \"hidden md:flex\")}\n >\n {i > 0 && sep}\n {content}\n </li>\n </Fragment>\n );\n })}\n </ol>\n </nav>\n );\n}\n"],"mappings":";AAyGoB,cAqCJ,YArCI;AAzGpB,SAAS,UAAU,WAAW,QAAQ,gBAAgB;AAEtD,SAAS,cAAc,sBAAsB;AAC7C,SAAS,UAAU;AACnB,SAAS,cAAc,kBAAkB;AACzC,SAAS,kBAAkB,uBAAuB;AAS3C,MAAM,6BAAgD;AAAA,EAC3D,OAAO;AAAA,EACP,SAAS;AACX;AAqDO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,UAAU;AAAA,EACV,QAAQ;AAAA,EACR;AAAA,EACA,cAAc;AAAA,EACd,GAAG;AACL,GAAqB;AACnB,QAAM,SAAS,aAAa,eAAe,4BAA4B,UAAU;AACjF,QAAM,UAAU,WAAW;AAC3B,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAG9C,QAAM,cAAc,OAAsB,IAAI;AAC9C,QAAM,gBAAgB,OAAO,KAAK;AAClC,YAAU,MAAM;AACd,QAAI,CAAC,YAAY,CAAC,cAAc,QAAS;AACzC,kBAAc,UAAU;AACxB,UAAM,KAAK,YAAY;AACvB,KAAC,IAAI,cAA2B,SAAS,KAAK,KAAK,MAAM;AAAA,EAC3D,GAAG,CAAC,QAAQ,CAAC;AACb,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAM,MAAM,KAAK,IAAI,GAAG,OAAO;AAG/B,QAAM,YAAY,KAAK,IAAI,GAAG,MAAM,SAAS,GAAG;AAChD,QAAM,cAAc,YAAY,CAAC,YAAY,YAAY,KAAK;AAC9D,QAAM,WAAW,CAAC,MAAc,eAAe,KAAK,KAAK,IAAI;AAE7D,QAAM,MACJ,oBAAC,UAAK,eAAW,MAAC,WAAU,6DACzB,uBAAa,oBAAC,gBAAa,WAAU,6BAA4B,GACpE;AAEF,QAAM,YACJ;AAEF,SACE,oBAAC,SAAK,GAAG,MAAM,cAAY,aAAa,OAAO,OAAO,WAAW,GAAG,mBAAmB,SAAS,GAC9F,8BAAC,QAAG,WAAU,oGACX,gBAAM,IAAI,CAAC,MAAM,MAAM;AACtB,UAAM,OAAO,MAAM,MAAM,SAAS;AAClC,UAAM,MAAM,KAAK,QAAQ,OAAO,KAAK,UAAU,WAAW,GAAG,CAAC,IAAI,KAAK,KAAK,KAAK,OAAO,CAAC;AACzF,QAAI;AACJ,QAAI,MAAM;AACR,gBACE,oBAAC,UAAK,gBAAa,QAAO,WAAU,uEACjC,eAAK,OACR;AAAA,IAEJ,WAAW,KAAK,SAAS,QAAW;AAClC,YAAM,QAA6B,EAAE,MAAM,KAAK,MAAM,WAAW,WAAW,UAAU,KAAK,MAAM;AACjG,YAAM,MAAM,iBAAsC,QAAW,SAAS,KAAK,IAAI;AAC/E,gBAAU,aACR,WAAW,KAAK,IACd,MACF,oBAAC,mBAAgB,QAAQ,KAAK,OAAc,IAE5C,oBAAC,OAAE,MAAM,MAAM,MAAM,WAAW,MAAM,WACnC,gBAAM,UACT;AAAA,IAEJ,OAAO;AACL,gBAAU,oBAAC,UAAK,WAAU,gCAAgC,eAAK,OAAM;AAAA,IACvE;AACA,WACE,qBAAC,YACE;AAAA,qBAAe,MAAM,KACpB,qBAAC,QAAG,WAAU,uCACX;AAAA;AAAA,QACD;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM;AACb,4BAAc,UAAU;AACxB,0BAAY,IAAI;AAAA,YAClB;AAAA,YACA,cAAY,OAAO;AAAA,YACnB,iBAAe;AAAA,YACf,WAAU;AAAA,YAEV,8BAAC,kBAAe,WAAU,UAAS,eAAW,MAAC;AAAA;AAAA,QACjD;AAAA,SACF;AAAA,MAEF;AAAA,QAAC;AAAA;AAAA,UACC,KAAK,MAAM,IAAI,cAAc;AAAA,UAE7B,UAAU,MAAM,KAAK,YAAY,KAAK,SAAS,SAAY,KAAK;AAAA,UAChE,WAAW,GAAG,kDAAkD,SAAS,CAAC,KAAK,gBAAgB;AAAA,UAE9F;AAAA,gBAAI,KAAK;AAAA,YACT;AAAA;AAAA;AAAA,MACH;AAAA,SA1Ba,GA2Bf;AAAA,EAEJ,CAAC,GACH,GACF;AAEJ;","names":[]}
|
|
@@ -1,9 +1,57 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ComponentPropsWithoutRef, ReactNode, Ref, MouseEvent, ReactElement } from 'react';
|
|
3
|
-
import { ButtonVariant, ButtonSize } from '
|
|
3
|
+
import { f as ButtonVariant, d as ButtonSize } from '../kit-labels-v3biUF1L.js';
|
|
4
|
+
import './data-table-labels.js';
|
|
5
|
+
import './mini-calendar.js';
|
|
6
|
+
import './calendar-heatmap.js';
|
|
7
|
+
import './popover.js';
|
|
8
|
+
import './chip.js';
|
|
9
|
+
import 'lucide-react';
|
|
10
|
+
import './field-sync.js';
|
|
4
11
|
import './tooltip.js';
|
|
5
12
|
import '../hooks/use-anchored-rect.js';
|
|
6
13
|
import '../lib/clipping.js';
|
|
14
|
+
import '../wizard/types.js';
|
|
15
|
+
import '../tour/tour.js';
|
|
16
|
+
import '../search/command-palette.js';
|
|
17
|
+
import '../search/global-search.js';
|
|
18
|
+
import '../search/search-index.js';
|
|
19
|
+
import './month-picker.js';
|
|
20
|
+
import './page-contents.js';
|
|
21
|
+
import './series-chart-labels.js';
|
|
22
|
+
import './pie-chart-labels.js';
|
|
23
|
+
import './sparkline.js';
|
|
24
|
+
import './stat-tile.js';
|
|
25
|
+
import './signature-pad.js';
|
|
26
|
+
import './password-strength.js';
|
|
27
|
+
import './danger-confirm.js';
|
|
28
|
+
import './swatch-picker.js';
|
|
29
|
+
import './tile-radio.js';
|
|
30
|
+
import './icon-picker.js';
|
|
31
|
+
import './dialog-frame.js';
|
|
32
|
+
import './modal.js';
|
|
33
|
+
import '../hooks/use-search-param-state.js';
|
|
34
|
+
import './measured-grid.js';
|
|
35
|
+
import '../feedback/feedback-attachment.js';
|
|
36
|
+
import '../feedback/feedback-dialog.js';
|
|
37
|
+
import '../feedback/feedback-thread.js';
|
|
38
|
+
import '../feedback/feedback-inbox.js';
|
|
39
|
+
import './account-settings-labels.js';
|
|
40
|
+
import './confirm-dialog.js';
|
|
41
|
+
import './floating-panel.js';
|
|
42
|
+
import './bulk-action-bar.js';
|
|
43
|
+
import './list.js';
|
|
44
|
+
import './breadcrumbs.js';
|
|
45
|
+
import './toast.js';
|
|
46
|
+
import './description-list.js';
|
|
47
|
+
import './line-items.js';
|
|
48
|
+
import './progress-bar.js';
|
|
49
|
+
import './signed-amount.js';
|
|
50
|
+
import './error-boundary.js';
|
|
51
|
+
import './authed-image.js';
|
|
52
|
+
import '../hooks/use-authed-src.js';
|
|
53
|
+
import './image-grid.js';
|
|
54
|
+
import './lightbox.js';
|
|
7
55
|
|
|
8
56
|
interface ButtonGroupProps extends Omit<ComponentPropsWithoutRef<"div">, "role"> {
|
|
9
57
|
/** `horizontal` (default): side by side, in reading order. `vertical`: stacked —
|
|
@@ -62,7 +110,8 @@ interface ButtonGroupLinkProps extends Omit<ComponentPropsWithoutRef<"a">, "href
|
|
|
62
110
|
/** The member for the page you are on: `aria-current="page"` and the "on" look of a
|
|
63
111
|
* pressed member, so a link row reads as a segmented switch between pages. */
|
|
64
112
|
current?: boolean;
|
|
65
|
-
/** Render through your router's `Link` — see {@link ButtonGroupLinkRenderProps}.
|
|
113
|
+
/** Render through your router's `Link` — see {@link ButtonGroupLinkRenderProps}.
|
|
114
|
+
* Default: the `<UiKitProvider linkComponent>`, then `<a>`. */
|
|
66
115
|
renderLink?: (props: ButtonGroupLinkRenderProps) => ReactElement;
|
|
67
116
|
className?: string;
|
|
68
117
|
children: ReactNode;
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { cn } from "../lib/cn.js";
|
|
4
4
|
import { buttonClasses } from "./ui.js";
|
|
5
|
+
import { useKitLink } from "../i18n/kit-labels.js";
|
|
6
|
+
import { pickLinkRenderer } from "./text-link.js";
|
|
5
7
|
const JOINED_COMMON = cn(
|
|
6
8
|
"inline-flex rounded-md border border-[var(--border)]",
|
|
7
9
|
// The group draws the frame; each member gives up its own border, radius and shadow.
|
|
@@ -76,6 +78,7 @@ function ButtonGroupLink({
|
|
|
76
78
|
children,
|
|
77
79
|
...rest
|
|
78
80
|
}) {
|
|
81
|
+
const kitLink = useKitLink();
|
|
79
82
|
const props = {
|
|
80
83
|
...rest,
|
|
81
84
|
href,
|
|
@@ -83,7 +86,8 @@ function ButtonGroupLink({
|
|
|
83
86
|
className: buttonClasses(variant, { size, className: cn(current && LINK_CURRENT, className) }),
|
|
84
87
|
children
|
|
85
88
|
};
|
|
86
|
-
|
|
89
|
+
const render = pickLinkRenderer(renderLink, kitLink, href);
|
|
90
|
+
if (render) return /* @__PURE__ */ jsx(RenderedGroupLink, { render, ...props });
|
|
87
91
|
const { children: content, ...anchor } = props;
|
|
88
92
|
return /* @__PURE__ */ jsx("a", { ...anchor, children: content });
|
|
89
93
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/button-group.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef, MouseEvent, ReactElement, ReactNode, Ref } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { buttonClasses, type ButtonSize, type ButtonVariant } from \"./ui\";\n\n// Every class below is spelled out in full rather than assembled from parts: the\n// consumer's Tailwind build finds classes by scanning this package's source as text,\n// and a class that exists only after a template literal runs is never generated.\n//\n// Each rule is written twice, once for a direct child and once for a `<button>` one\n// level down. The second is for a Tooltip-wrapped IconButton — the map zoom's + and −\n// both carry a tooltip — where the direct child is the tooltip's wrapper span and the\n// button that draws the border and the corners sits inside it. The same again for an\n// `<a>` one level down: a router link member (see {@link ButtonGroupLink}) under a\n// tooltip. A bare link is a direct child and needs nothing extra.\nconst JOINED_COMMON = cn(\n \"inline-flex rounded-md border border-[var(--border)]\",\n // The group draws the frame; each member gives up its own border, radius and shadow.\n \"[&>*]:rounded-none [&>*]:border-0 [&>*]:shadow-none\",\n \"[&>*>button]:rounded-none [&>*>button]:border-0 [&>*>button]:shadow-none\",\n \"[&>*>a]:rounded-none [&>*>a]:border-0 [&>*>a]:shadow-none\",\n \"[&>*]:border-[var(--border)]\",\n // A focused member rises above its neighbours so its ring is not painted over by\n // the next one's background.\n \"[&>*]:relative [&>*:focus-within]:z-10\",\n);\n\nconst JOINED: Record<\"horizontal\" | \"vertical\", string> = {\n // Logical edges and corners throughout (`border-s`, `rounded-s-*`), so in RTL the\n // first member sits on the right with the right-hand corners rounded — the physical\n // pair would round the outside of the last button and square off the first.\n horizontal: cn(\n JOINED_COMMON,\n \"flex-row items-stretch\",\n \"[&>*:not(:first-child)]:border-s\",\n \"[&>:first-child]:rounded-s-md [&>:last-child]:rounded-e-md\",\n \"[&>:first-child>button]:rounded-s-md [&>:last-child>button]:rounded-e-md\",\n \"[&>:first-child>a]:rounded-s-md [&>:last-child>a]:rounded-e-md\",\n ),\n // The block axis is top-to-bottom in both reading directions, so top/bottom are\n // already the logical edges here.\n vertical: cn(\n JOINED_COMMON,\n \"flex-col items-stretch\",\n \"[&>*:not(:first-child)]:border-t\",\n \"[&>:first-child]:rounded-t-md [&>:last-child]:rounded-b-md\",\n \"[&>:first-child>button]:rounded-t-md [&>:last-child>button]:rounded-b-md\",\n \"[&>:first-child>a]:rounded-t-md [&>:last-child>a]:rounded-b-md\",\n ),\n};\n\n// `gapped`: the members keep their own shape — round overlay discs, each its own\n// border and radius — and sit apart. Nothing is stripped, so there is nothing to\n// restore per edge, and the gap is logical by nature.\nconst GAPPED: Record<\"horizontal\" | \"vertical\", string> = {\n horizontal: \"inline-flex flex-row items-center gap-1\",\n vertical: \"inline-flex flex-col items-center gap-1\",\n};\n\n// `elevated`, joined: the frame itself floats — a filled surface under the members (a\n// ghost member would otherwise show the content through it) and the shadow of a\n// control lifted over the page.\nconst ELEVATED_JOINED = \"bg-[var(--bg-surface)] shadow-lg\";\n// `elevated`, gapped: there is no frame, so each disc carries the shadow. Aimed at the\n// control, not the tooltip's square wrapper span, whose shadow would be a box round a\n// round disc.\nconst ELEVATED_GAPPED =\n \"[&>button]:shadow-md [&>a]:shadow-md [&>*>button]:shadow-md [&>*>a]:shadow-md\";\n\nexport interface ButtonGroupProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"role\"> {\n /** `horizontal` (default): side by side, in reading order. `vertical`: stacked —\n * the map's zoom + over −. */\n orientation?: \"horizontal\" | \"vertical\";\n /**\n * `joined` (default): one frame, hairline dividers, outer corners rounded. `gapped`:\n * separate controls with a small gap, each keeping its own shape — the overlay\n * discs of keksdose's receipt preview zoom (scan-file-preview.tsx:362, the − and +\n * `IconButton variant=\"overlay\"` in a hand-made `flex gap-1`), where a joined frame\n * would be a box drawn over the photo.\n */\n variant?: \"joined\" | \"gapped\";\n /**\n * Float it: a surface with a shadow, for a toolbar that sits OVER content (a map's\n * zoom, a photo's controls) rather than in the page's flow — keksdose adds\n * `shadow-sm` to each disc on the receipt preview by hand. On a joined group the\n * frame is filled and shadowed; on a gapped one, each member.\n */\n elevated?: boolean;\n}\n\n/**\n * Adjacent `Button`s or `IconButton`s drawn as one joined control: a single frame,\n * hairline dividers between the members, rounded only at the outer corners.\n *\n * keksdose asked for it twice — \"collapse all / expand all\" above its category\n * tree, and the + and − of its map zoom — both hand-built with negative margins and\n * physical `rounded-l` / `rounded-r`, which came out inside-out under `dir=\"rtl\"`.\n *\n * `role=\"group\"` with a name: a reader then introduces the pair as \"Zoom, group\"\n * before reading \"Zoom in\", which is what tells a listener that the next button\n * belongs with this one. Pass `aria-label` (or `aria-labelledby`); a group with no\n * name is one more unlabelled wrapper. It is NOT a toolbar — it adds no arrow-key\n * roving, and every member keeps its own tab stop, as plain adjacent buttons do.\n * For one-of-many selection use `ToggleGroup`, which has the radio semantics.\n */\nexport function ButtonGroup({\n orientation = \"horizontal\",\n variant = \"joined\",\n elevated = false,\n className,\n ...rest\n}: ButtonGroupProps) {\n const joined = variant === \"joined\";\n return (\n <div\n {...rest}\n role=\"group\"\n data-orientation={orientation}\n data-variant={variant}\n className={cn(\n joined ? JOINED[orientation] : GAPPED[orientation],\n elevated && (joined ? ELEVATED_JOINED : ELEVATED_GAPPED),\n className,\n )}\n />\n );\n}\n\n/** What {@link ButtonGroupLinkProps.renderLink} is handed. Spread it onto your router's\n * link, mapping `href` — `renderLink={({ href, ...p }) => <Link to={href} {...p} />}`. */\nexport interface ButtonGroupLinkRenderProps {\n href: string;\n /** The button look — keep it, or the member is a bare link in the frame. */\n className: string;\n children: ReactNode;\n ref?: Ref<HTMLAnchorElement>;\n \"aria-current\"?: \"page\";\n onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;\n [key: `aria-${string}`]: string | boolean | number | undefined;\n [key: `data-${string}`]: unknown;\n}\n\nexport interface ButtonGroupLinkProps extends Omit<ComponentPropsWithoutRef<\"a\">, \"href\" | \"className\" | \"children\"> {\n href: string;\n /** The {@link Button} variant it looks like. Default `secondary`, the usual member. */\n variant?: ButtonVariant;\n size?: ButtonSize;\n /** The member for the page you are on: `aria-current=\"page\"` and the \"on\" look of a\n * pressed member, so a link row reads as a segmented switch between pages. */\n current?: boolean;\n /** Render through your router's `Link` — see {@link ButtonGroupLinkRenderProps}. */\n renderLink?: (props: ButtonGroupLinkRenderProps) => ReactElement;\n className?: string;\n children: ReactNode;\n ref?: Ref<HTMLAnchorElement>;\n}\n\nconst LINK_CURRENT = \"bg-[var(--brand-bg)] text-[var(--brand)] hover:bg-[var(--brand-bg-hover)]\";\n\n/**\n * A {@link ButtonGroup} member that navigates: a link drawn as a {@link Button}, so it\n * joins the frame like one. A group mixing actions and pages — \"Export\" beside \"Open\n * report\" — had to either make the page a button with an `onClick` navigate (no\n * middle-click, no \"open in new tab\", no link semantics) or style an `<a>` whose\n * corners the group did not know about. Plain `<a>` by default; `renderLink` for the\n * router's `Link`, the kit's usual shape (`Chip`, `MenuItem`, `StatTile`).\n */\nexport function ButtonGroupLink({\n href,\n variant = \"secondary\",\n size,\n current = false,\n renderLink,\n className,\n children,\n ...rest\n}: ButtonGroupLinkProps) {\n const props: ButtonGroupLinkRenderProps = {\n ...rest,\n href,\n \"aria-current\": current ? \"page\" : undefined,\n className: buttonClasses(variant, { size, className: cn(current && LINK_CURRENT, className) }),\n children,\n };\n if (renderLink) return <RenderedGroupLink render={renderLink} {...props} />;\n const { children: content, ...anchor } = props;\n return <a {...anchor}>{content}</a>;\n}\n\n/** Calls `renderLink` as a component, so the router link's hooks are its own. */\nfunction RenderedGroupLink({\n render,\n ...props\n}: ButtonGroupLinkRenderProps & { render: (props: ButtonGroupLinkRenderProps) => ReactElement }) {\n return render(props);\n}\n"],"mappings":";AAiHI;AAhHJ,SAAS,UAAU;AACnB,SAAS,qBAA0D;AAYnE,MAAM,gBAAgB;AAAA,EACpB;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AACF;AAEA,MAAM,SAAoD;AAAA;AAAA;AAAA;AAAA,EAIxD,YAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA;AAAA;AAAA,EAGA,UAAU;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAKA,MAAM,SAAoD;AAAA,EACxD,YAAY;AAAA,EACZ,UAAU;AACZ;AAKA,MAAM,kBAAkB;AAIxB,MAAM,kBACJ;AAsCK,SAAS,YAAY;AAAA,EAC1B,cAAc;AAAA,EACd,UAAU;AAAA,EACV,WAAW;AAAA,EACX;AAAA,EACA,GAAG;AACL,GAAqB;AACnB,QAAM,SAAS,YAAY;AAC3B,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,MAAK;AAAA,MACL,oBAAkB;AAAA,MAClB,gBAAc;AAAA,MACd,WAAW;AAAA,QACT,SAAS,OAAO,WAAW,IAAI,OAAO,WAAW;AAAA,QACjD,aAAa,SAAS,kBAAkB;AAAA,QACxC;AAAA,MACF;AAAA;AAAA,EACF;AAEJ;AA+BA,MAAM,eAAe;AAUd,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAyB;AACvB,QAAM,QAAoC;AAAA,IACxC,GAAG;AAAA,IACH;AAAA,IACA,gBAAgB,UAAU,SAAS;AAAA,IACnC,WAAW,cAAc,SAAS,EAAE,MAAM,WAAW,GAAG,WAAW,cAAc,SAAS,EAAE,CAAC;AAAA,IAC7F;AAAA,EACF;AACA,MAAI,WAAY,QAAO,oBAAC,qBAAkB,QAAQ,YAAa,GAAG,OAAO;AACzE,QAAM,EAAE,UAAU,SAAS,GAAG,OAAO,IAAI;AACzC,SAAO,oBAAC,OAAG,GAAG,QAAS,mBAAQ;AACjC;AAGA,SAAS,kBAAkB;AAAA,EACzB;AAAA,EACA,GAAG;AACL,GAAiG;AAC/F,SAAO,OAAO,KAAK;AACrB;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/button-group.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef, MouseEvent, ReactElement, ReactNode, Ref } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { buttonClasses, type ButtonSize, type ButtonVariant } from \"./ui\";\nimport { useKitLink } from \"../i18n/kit-labels\";\nimport { pickLinkRenderer } from \"./text-link\";\n\n// Every class below is spelled out in full rather than assembled from parts: the\n// consumer's Tailwind build finds classes by scanning this package's source as text,\n// and a class that exists only after a template literal runs is never generated.\n//\n// Each rule is written twice, once for a direct child and once for a `<button>` one\n// level down. The second is for a Tooltip-wrapped IconButton — the map zoom's + and −\n// both carry a tooltip — where the direct child is the tooltip's wrapper span and the\n// button that draws the border and the corners sits inside it. The same again for an\n// `<a>` one level down: a router link member (see {@link ButtonGroupLink}) under a\n// tooltip. A bare link is a direct child and needs nothing extra.\nconst JOINED_COMMON = cn(\n \"inline-flex rounded-md border border-[var(--border)]\",\n // The group draws the frame; each member gives up its own border, radius and shadow.\n \"[&>*]:rounded-none [&>*]:border-0 [&>*]:shadow-none\",\n \"[&>*>button]:rounded-none [&>*>button]:border-0 [&>*>button]:shadow-none\",\n \"[&>*>a]:rounded-none [&>*>a]:border-0 [&>*>a]:shadow-none\",\n \"[&>*]:border-[var(--border)]\",\n // A focused member rises above its neighbours so its ring is not painted over by\n // the next one's background.\n \"[&>*]:relative [&>*:focus-within]:z-10\",\n);\n\nconst JOINED: Record<\"horizontal\" | \"vertical\", string> = {\n // Logical edges and corners throughout (`border-s`, `rounded-s-*`), so in RTL the\n // first member sits on the right with the right-hand corners rounded — the physical\n // pair would round the outside of the last button and square off the first.\n horizontal: cn(\n JOINED_COMMON,\n \"flex-row items-stretch\",\n \"[&>*:not(:first-child)]:border-s\",\n \"[&>:first-child]:rounded-s-md [&>:last-child]:rounded-e-md\",\n \"[&>:first-child>button]:rounded-s-md [&>:last-child>button]:rounded-e-md\",\n \"[&>:first-child>a]:rounded-s-md [&>:last-child>a]:rounded-e-md\",\n ),\n // The block axis is top-to-bottom in both reading directions, so top/bottom are\n // already the logical edges here.\n vertical: cn(\n JOINED_COMMON,\n \"flex-col items-stretch\",\n \"[&>*:not(:first-child)]:border-t\",\n \"[&>:first-child]:rounded-t-md [&>:last-child]:rounded-b-md\",\n \"[&>:first-child>button]:rounded-t-md [&>:last-child>button]:rounded-b-md\",\n \"[&>:first-child>a]:rounded-t-md [&>:last-child>a]:rounded-b-md\",\n ),\n};\n\n// `gapped`: the members keep their own shape — round overlay discs, each its own\n// border and radius — and sit apart. Nothing is stripped, so there is nothing to\n// restore per edge, and the gap is logical by nature.\nconst GAPPED: Record<\"horizontal\" | \"vertical\", string> = {\n horizontal: \"inline-flex flex-row items-center gap-1\",\n vertical: \"inline-flex flex-col items-center gap-1\",\n};\n\n// `elevated`, joined: the frame itself floats — a filled surface under the members (a\n// ghost member would otherwise show the content through it) and the shadow of a\n// control lifted over the page.\nconst ELEVATED_JOINED = \"bg-[var(--bg-surface)] shadow-lg\";\n// `elevated`, gapped: there is no frame, so each disc carries the shadow. Aimed at the\n// control, not the tooltip's square wrapper span, whose shadow would be a box round a\n// round disc.\nconst ELEVATED_GAPPED =\n \"[&>button]:shadow-md [&>a]:shadow-md [&>*>button]:shadow-md [&>*>a]:shadow-md\";\n\nexport interface ButtonGroupProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"role\"> {\n /** `horizontal` (default): side by side, in reading order. `vertical`: stacked —\n * the map's zoom + over −. */\n orientation?: \"horizontal\" | \"vertical\";\n /**\n * `joined` (default): one frame, hairline dividers, outer corners rounded. `gapped`:\n * separate controls with a small gap, each keeping its own shape — the overlay\n * discs of keksdose's receipt preview zoom (scan-file-preview.tsx:362, the − and +\n * `IconButton variant=\"overlay\"` in a hand-made `flex gap-1`), where a joined frame\n * would be a box drawn over the photo.\n */\n variant?: \"joined\" | \"gapped\";\n /**\n * Float it: a surface with a shadow, for a toolbar that sits OVER content (a map's\n * zoom, a photo's controls) rather than in the page's flow — keksdose adds\n * `shadow-sm` to each disc on the receipt preview by hand. On a joined group the\n * frame is filled and shadowed; on a gapped one, each member.\n */\n elevated?: boolean;\n}\n\n/**\n * Adjacent `Button`s or `IconButton`s drawn as one joined control: a single frame,\n * hairline dividers between the members, rounded only at the outer corners.\n *\n * keksdose asked for it twice — \"collapse all / expand all\" above its category\n * tree, and the + and − of its map zoom — both hand-built with negative margins and\n * physical `rounded-l` / `rounded-r`, which came out inside-out under `dir=\"rtl\"`.\n *\n * `role=\"group\"` with a name: a reader then introduces the pair as \"Zoom, group\"\n * before reading \"Zoom in\", which is what tells a listener that the next button\n * belongs with this one. Pass `aria-label` (or `aria-labelledby`); a group with no\n * name is one more unlabelled wrapper. It is NOT a toolbar — it adds no arrow-key\n * roving, and every member keeps its own tab stop, as plain adjacent buttons do.\n * For one-of-many selection use `ToggleGroup`, which has the radio semantics.\n */\nexport function ButtonGroup({\n orientation = \"horizontal\",\n variant = \"joined\",\n elevated = false,\n className,\n ...rest\n}: ButtonGroupProps) {\n const joined = variant === \"joined\";\n return (\n <div\n {...rest}\n role=\"group\"\n data-orientation={orientation}\n data-variant={variant}\n className={cn(\n joined ? JOINED[orientation] : GAPPED[orientation],\n elevated && (joined ? ELEVATED_JOINED : ELEVATED_GAPPED),\n className,\n )}\n />\n );\n}\n\n/** What {@link ButtonGroupLinkProps.renderLink} is handed. Spread it onto your router's\n * link, mapping `href` — `renderLink={({ href, ...p }) => <Link to={href} {...p} />}`. */\nexport interface ButtonGroupLinkRenderProps {\n href: string;\n /** The button look — keep it, or the member is a bare link in the frame. */\n className: string;\n children: ReactNode;\n ref?: Ref<HTMLAnchorElement>;\n \"aria-current\"?: \"page\";\n onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;\n [key: `aria-${string}`]: string | boolean | number | undefined;\n [key: `data-${string}`]: unknown;\n}\n\nexport interface ButtonGroupLinkProps extends Omit<ComponentPropsWithoutRef<\"a\">, \"href\" | \"className\" | \"children\"> {\n href: string;\n /** The {@link Button} variant it looks like. Default `secondary`, the usual member. */\n variant?: ButtonVariant;\n size?: ButtonSize;\n /** The member for the page you are on: `aria-current=\"page\"` and the \"on\" look of a\n * pressed member, so a link row reads as a segmented switch between pages. */\n current?: boolean;\n /** Render through your router's `Link` — see {@link ButtonGroupLinkRenderProps}.\n * Default: the `<UiKitProvider linkComponent>`, then `<a>`. */\n renderLink?: (props: ButtonGroupLinkRenderProps) => ReactElement;\n className?: string;\n children: ReactNode;\n ref?: Ref<HTMLAnchorElement>;\n}\n\nconst LINK_CURRENT = \"bg-[var(--brand-bg)] text-[var(--brand)] hover:bg-[var(--brand-bg-hover)]\";\n\n/**\n * A {@link ButtonGroup} member that navigates: a link drawn as a {@link Button}, so it\n * joins the frame like one. A group mixing actions and pages — \"Export\" beside \"Open\n * report\" — had to either make the page a button with an `onClick` navigate (no\n * middle-click, no \"open in new tab\", no link semantics) or style an `<a>` whose\n * corners the group did not know about. Plain `<a>` by default; `renderLink` for the\n * router's `Link`, the kit's usual shape (`Chip`, `MenuItem`, `StatTile`).\n */\nexport function ButtonGroupLink({\n href,\n variant = \"secondary\",\n size,\n current = false,\n renderLink,\n className,\n children,\n ...rest\n}: ButtonGroupLinkProps) {\n const kitLink = useKitLink();\n const props: ButtonGroupLinkRenderProps = {\n ...rest,\n href,\n \"aria-current\": current ? \"page\" : undefined,\n className: buttonClasses(variant, { size, className: cn(current && LINK_CURRENT, className) }),\n children,\n };\n const render = pickLinkRenderer(renderLink, kitLink, href);\n if (render) return <RenderedGroupLink render={render} {...props} />;\n const { children: content, ...anchor } = props;\n return <a {...anchor}>{content}</a>;\n}\n\n/** Calls `renderLink` as a component, so the router link's hooks are its own. */\nfunction RenderedGroupLink({\n render,\n ...props\n}: ButtonGroupLinkRenderProps & { render: (props: ButtonGroupLinkRenderProps) => ReactElement }) {\n return render(props);\n}\n"],"mappings":";AAmHI;AAlHJ,SAAS,UAAU;AACnB,SAAS,qBAA0D;AACnE,SAAS,kBAAkB;AAC3B,SAAS,wBAAwB;AAYjC,MAAM,gBAAgB;AAAA,EACpB;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AACF;AAEA,MAAM,SAAoD;AAAA;AAAA;AAAA;AAAA,EAIxD,YAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA;AAAA;AAAA,EAGA,UAAU;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAKA,MAAM,SAAoD;AAAA,EACxD,YAAY;AAAA,EACZ,UAAU;AACZ;AAKA,MAAM,kBAAkB;AAIxB,MAAM,kBACJ;AAsCK,SAAS,YAAY;AAAA,EAC1B,cAAc;AAAA,EACd,UAAU;AAAA,EACV,WAAW;AAAA,EACX;AAAA,EACA,GAAG;AACL,GAAqB;AACnB,QAAM,SAAS,YAAY;AAC3B,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,MAAK;AAAA,MACL,oBAAkB;AAAA,MAClB,gBAAc;AAAA,MACd,WAAW;AAAA,QACT,SAAS,OAAO,WAAW,IAAI,OAAO,WAAW;AAAA,QACjD,aAAa,SAAS,kBAAkB;AAAA,QACxC;AAAA,MACF;AAAA;AAAA,EACF;AAEJ;AAgCA,MAAM,eAAe;AAUd,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAyB;AACvB,QAAM,UAAU,WAAW;AAC3B,QAAM,QAAoC;AAAA,IACxC,GAAG;AAAA,IACH;AAAA,IACA,gBAAgB,UAAU,SAAS;AAAA,IACnC,WAAW,cAAc,SAAS,EAAE,MAAM,WAAW,GAAG,WAAW,cAAc,SAAS,EAAE,CAAC;AAAA,IAC7F;AAAA,EACF;AACA,QAAM,SAAS,iBAAiB,YAAY,SAAS,IAAI;AACzD,MAAI,OAAQ,QAAO,oBAAC,qBAAkB,QAAiB,GAAG,OAAO;AACjE,QAAM,EAAE,UAAU,SAAS,GAAG,OAAO,IAAI;AACzC,SAAO,oBAAC,OAAG,GAAG,QAAS,mBAAQ;AACjC;AAGA,SAAS,kBAAkB;AAAA,EACzB;AAAA,EACA,GAAG;AACL,GAAiG;AAC/F,SAAO,OAAO,KAAK;AACrB;","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { CalculatorLabels } from '../
|
|
2
|
+
import { C as CalculatorLabels } from '../kit-labels-v3biUF1L.js';
|
|
3
3
|
import './data-table-labels.js';
|
|
4
4
|
import './mini-calendar.js';
|
|
5
5
|
import './calendar-heatmap.js';
|
|
@@ -7,7 +7,6 @@ import './popover.js';
|
|
|
7
7
|
import './chip.js';
|
|
8
8
|
import 'lucide-react';
|
|
9
9
|
import './field-sync.js';
|
|
10
|
-
import './ui.js';
|
|
11
10
|
import './tooltip.js';
|
|
12
11
|
import '../hooks/use-anchored-rect.js';
|
|
13
12
|
import '../lib/clipping.js';
|
|
@@ -19,6 +18,7 @@ import '../search/search-index.js';
|
|
|
19
18
|
import './month-picker.js';
|
|
20
19
|
import './page-contents.js';
|
|
21
20
|
import './series-chart-labels.js';
|
|
21
|
+
import './pie-chart-labels.js';
|
|
22
22
|
import './sparkline.js';
|
|
23
23
|
import './stat-tile.js';
|
|
24
24
|
import './signature-pad.js';
|
|
@@ -29,17 +29,28 @@ import './tile-radio.js';
|
|
|
29
29
|
import './icon-picker.js';
|
|
30
30
|
import './dialog-frame.js';
|
|
31
31
|
import './modal.js';
|
|
32
|
-
import '
|
|
32
|
+
import '../hooks/use-search-param-state.js';
|
|
33
33
|
import './measured-grid.js';
|
|
34
34
|
import '../feedback/feedback-attachment.js';
|
|
35
35
|
import '../feedback/feedback-dialog.js';
|
|
36
|
+
import '../feedback/feedback-thread.js';
|
|
37
|
+
import '../feedback/feedback-inbox.js';
|
|
38
|
+
import './account-settings-labels.js';
|
|
36
39
|
import './confirm-dialog.js';
|
|
37
40
|
import './floating-panel.js';
|
|
38
|
-
import './copy-button.js';
|
|
39
41
|
import './bulk-action-bar.js';
|
|
40
42
|
import './list.js';
|
|
41
43
|
import './breadcrumbs.js';
|
|
42
44
|
import './toast.js';
|
|
45
|
+
import './description-list.js';
|
|
46
|
+
import './line-items.js';
|
|
47
|
+
import './progress-bar.js';
|
|
48
|
+
import './signed-amount.js';
|
|
49
|
+
import './error-boundary.js';
|
|
50
|
+
import './authed-image.js';
|
|
51
|
+
import '../hooks/use-authed-src.js';
|
|
52
|
+
import './image-grid.js';
|
|
53
|
+
import './lightbox.js';
|
|
43
54
|
|
|
44
55
|
/** Screen-reader names for the calculator panel's own controls — any part of the
|
|
45
56
|
* kit's `calculator` namespace. Once three optional keys of its own; widened, not
|
|
@@ -87,6 +87,39 @@ interface CalendarHeatmapProps extends Omit<ComponentPropsWithoutRef<"div">, "on
|
|
|
87
87
|
/** The scale's colour, as any CSS colour. Default `var(--brand)`; each step is this
|
|
88
88
|
* mixed into `--bg-surface`, so the ramp follows the theme. */
|
|
89
89
|
color?: string;
|
|
90
|
+
/**
|
|
91
|
+
* A CONTINUOUS scale from `colorFrom` (the smallest value) to `colorTo` (the busiest
|
|
92
|
+
* day), instead of `levels` steps of `color` — keksdose's palette-store ramp,
|
|
93
|
+
* `seqLow` → `seqHigh`. Both are needed; `color` is then unused. Two `#rrggbb`
|
|
94
|
+
* colours are interpolated in JS, so the fill is a concrete hex and the day number's
|
|
95
|
+
* ink (in `"month"`) is MEASURED against it; anything else (a `var()`) is mixed with
|
|
96
|
+
* `color-mix()`. `levels` still sets `data-level` and the legend's swatch count.
|
|
97
|
+
*/
|
|
98
|
+
colorFrom?: string;
|
|
99
|
+
colorTo?: string;
|
|
100
|
+
/**
|
|
101
|
+
* A fill of the caller's own for a day with a value: handed the value and the
|
|
102
|
+
* scale's `max`, returns any CSS colour. Wins over `color` and `colorFrom`/`colorTo`.
|
|
103
|
+
* Also paints the legend, at `max × level / levels`.
|
|
104
|
+
*/
|
|
105
|
+
fill?: (value: number, max: number) => string;
|
|
106
|
+
/** The fill of a day with no value (or a value of 0). Default: a wash of the text
|
|
107
|
+
* colour over the surface, which reads as a cell on both themes. */
|
|
108
|
+
emptyColor?: string;
|
|
109
|
+
/**
|
|
110
|
+
* Where the window ENDS — the day `maxDays` counts back from, and the last one drawn:
|
|
111
|
+
*
|
|
112
|
+
* - `"to"` (default): on `to`.
|
|
113
|
+
* - `"today"`: on today.
|
|
114
|
+
* - `"latest"`: on the last day that can carry a figure — today, or a later day that
|
|
115
|
+
* actually has a value. A window running into the future (a report's "all" preset
|
|
116
|
+
* ends a year ahead) then spends its cells on the days a person is looking for,
|
|
117
|
+
* not on a year of empty ones.
|
|
118
|
+
* - a `"YYYY-MM-DD"`: on that day.
|
|
119
|
+
*
|
|
120
|
+
* Always kept inside `from`…`to`.
|
|
121
|
+
*/
|
|
122
|
+
anchor?: "to" | "today" | "latest" | (string & {});
|
|
90
123
|
/**
|
|
91
124
|
* The most days to draw. A longer window keeps its LATEST `maxDays` and says how many
|
|
92
125
|
* it left out (`labels.truncated`) rather than silently ending. Default: no cap.
|
|
@@ -101,6 +134,9 @@ interface CalendarHeatmapProps extends Omit<ComponentPropsWithoutRef<"div">, "on
|
|
|
101
134
|
labels?: Partial<CalendarHeatmapLabels>;
|
|
102
135
|
className?: string;
|
|
103
136
|
}
|
|
137
|
+
/** Where the window ends for an {@link CalendarHeatmapProps.anchor}, kept in `[from, to]`.
|
|
138
|
+
* `latestData` is the latest ISO date carrying a value, or `""`. */
|
|
139
|
+
declare function heatmapWindowEnd(from: string, to: string, anchor: string, latestData: string, today?: string): string;
|
|
104
140
|
/** The step a value falls on: 0 for nothing, else 1…levels by its share of `max`. */
|
|
105
141
|
declare function heatmapLevel(value: number, max: number, levels: number): number;
|
|
106
142
|
/**
|
|
@@ -113,12 +149,14 @@ declare function heatmapLevel(value: number, max: number, levels: number): numbe
|
|
|
113
149
|
* the other way round, mirrored in RTL — PageUp/PageDown move a month and Home/End go
|
|
114
150
|
* to the window's first/last day. Each cell is named with its whole date AND its value,
|
|
115
151
|
* because the colour is not something a screen reader can say, and carries
|
|
116
|
-
* `data-day="YYYY-MM-DD"` as its identity for tests and for the host.
|
|
152
|
+
* `data-day="YYYY-MM-DD"` as its identity for tests and for the host. Note that in
|
|
153
|
+
* `"weeks"` the cells are in DOM order by WEEKDAY ROW (all Mondays, then all
|
|
154
|
+
* Tuesdays…), not by date — query by `data-day`, not by position.
|
|
117
155
|
*
|
|
118
156
|
* The tooltip is portalled (a year of squares always sits in a sideways scroller) and
|
|
119
157
|
* `data-private` unless `sensitive={false}`. Cell size in `"weeks"` is the CSS variable
|
|
120
158
|
* `--heatmap-cell` (default `0.75rem`): `style={{ "--heatmap-cell": "1rem" }}`.
|
|
121
159
|
*/
|
|
122
|
-
declare function CalendarHeatmap({ data, from, to, layout, selected, onSelect, formatValue: formatValueProp, tooltip, sensitive, max: maxProp, levels, color, maxDays, legend, locale: localeProp, weekStartsOn, labels: labelsProp, className, "aria-label": ariaLabel, ...rest }: CalendarHeatmapProps): react.JSX.Element;
|
|
160
|
+
declare function CalendarHeatmap({ data, from, to, layout, selected, onSelect, formatValue: formatValueProp, tooltip, sensitive, max: maxProp, levels, color, colorFrom, colorTo, fill: fillProp, emptyColor, anchor, maxDays, legend, locale: localeProp, weekStartsOn, labels: labelsProp, className, "aria-label": ariaLabel, ...rest }: CalendarHeatmapProps): react.JSX.Element;
|
|
123
161
|
|
|
124
|
-
export { CalendarHeatmap, type CalendarHeatmapDatum, type CalendarHeatmapDay, type CalendarHeatmapLabels, type CalendarHeatmapProps, DEFAULT_CALENDAR_HEATMAP_LABELS, heatmapLevel };
|
|
162
|
+
export { CalendarHeatmap, type CalendarHeatmapDatum, type CalendarHeatmapDay, type CalendarHeatmapLabels, type CalendarHeatmapProps, DEFAULT_CALENDAR_HEATMAP_LABELS, heatmapLevel, heatmapWindowEnd };
|
|
@@ -5,6 +5,8 @@ import { cn } from "../lib/cn.js";
|
|
|
5
5
|
import { dirOf, horizontalStep } from "../lib/direction.js";
|
|
6
6
|
import { addMonthsClamped, localeWeekStart, parseIsoDate, toLocalIso } from "../lib/dates.js";
|
|
7
7
|
import { useKitLabels, useKitLocale, useKitWeekStart } from "../i18n/kit-labels.js";
|
|
8
|
+
import { lerpHex, textOn } from "../theme/chart-palette.js";
|
|
9
|
+
import { parseHex } from "../theme/color.js";
|
|
8
10
|
import { Tooltip } from "./tooltip.js";
|
|
9
11
|
const DEFAULT_CALENDAR_HEATMAP_LABELS = {
|
|
10
12
|
grid: "Daily values",
|
|
@@ -21,6 +23,16 @@ const DAY_NAME_FORMAT = {
|
|
|
21
23
|
};
|
|
22
24
|
const EMPTY_FILL = "color-mix(in oklab, var(--text-primary) 8%, var(--bg-surface))";
|
|
23
25
|
const weekdayIndex = (d, weekStart) => (d.getDay() - weekStart + 7) % 7;
|
|
26
|
+
function heatmapWindowEnd(from, to, anchor, latestData, today = toLocalIso(/* @__PURE__ */ new Date())) {
|
|
27
|
+
let end;
|
|
28
|
+
if (anchor === "today") end = today;
|
|
29
|
+
else if (anchor === "latest") end = latestData > today ? latestData : today;
|
|
30
|
+
else if (anchor === "to" || !parseIsoDate(anchor)) end = to;
|
|
31
|
+
else end = anchor;
|
|
32
|
+
if (end > to) end = to;
|
|
33
|
+
if (end < from) end = from;
|
|
34
|
+
return end;
|
|
35
|
+
}
|
|
24
36
|
function heatmapLevel(value, max, levels) {
|
|
25
37
|
if (!(value > 0) || !(max > 0) || levels < 1) return 0;
|
|
26
38
|
return Math.min(levels, Math.max(1, Math.ceil(value / max * levels)));
|
|
@@ -46,6 +58,11 @@ function CalendarHeatmap({
|
|
|
46
58
|
max: maxProp,
|
|
47
59
|
levels = 4,
|
|
48
60
|
color = "var(--brand)",
|
|
61
|
+
colorFrom,
|
|
62
|
+
colorTo,
|
|
63
|
+
fill: fillProp,
|
|
64
|
+
emptyColor,
|
|
65
|
+
anchor = "to",
|
|
49
66
|
maxDays,
|
|
50
67
|
legend = true,
|
|
51
68
|
locale: localeProp,
|
|
@@ -66,9 +83,15 @@ function CalendarHeatmap({
|
|
|
66
83
|
for (const p of data) m.set(p.date, (m.get(p.date) ?? 0) + p.value);
|
|
67
84
|
return m;
|
|
68
85
|
}, [data]);
|
|
86
|
+
const latestData = useMemo(() => {
|
|
87
|
+
let latest = "";
|
|
88
|
+
for (const [iso, v] of values) if (v !== 0 && iso > latest) latest = iso;
|
|
89
|
+
return latest;
|
|
90
|
+
}, [values]);
|
|
91
|
+
const windowEnd = heatmapWindowEnd(from, to, anchor, latestData);
|
|
69
92
|
const { days, dropped } = useMemo(() => {
|
|
70
93
|
const start = parseIsoDate(from);
|
|
71
|
-
const end = parseIsoDate(
|
|
94
|
+
const end = parseIsoDate(windowEnd);
|
|
72
95
|
const list = [];
|
|
73
96
|
if (!start || !end || end < start) return { days: list, dropped: 0 };
|
|
74
97
|
const cursor = new Date(end);
|
|
@@ -80,7 +103,7 @@ function CalendarHeatmap({
|
|
|
80
103
|
const first = list[0];
|
|
81
104
|
const trimmed = first ? Math.round((first.getTime() - start.getTime()) / 864e5) : 0;
|
|
82
105
|
return { days: list, dropped: Math.max(0, trimmed) };
|
|
83
|
-
}, [from,
|
|
106
|
+
}, [from, windowEnd, maxDays]);
|
|
84
107
|
const firstIso = days.length ? toLocalIso(days[0]) : "";
|
|
85
108
|
const lastIso = days.length ? toLocalIso(days[days.length - 1]) : "";
|
|
86
109
|
const max = useMemo(() => {
|
|
@@ -182,18 +205,35 @@ function CalendarHeatmap({
|
|
|
182
205
|
setActiveIso(iso);
|
|
183
206
|
};
|
|
184
207
|
const legendLevels = Array.from({ length: levels + 1 }, (_, l) => l);
|
|
208
|
+
const empty = emptyColor ?? EMPTY_FILL;
|
|
209
|
+
const continuous = colorFrom !== void 0 && colorTo !== void 0;
|
|
210
|
+
const hexRamp = continuous && parseHex(colorFrom) != null && parseHex(colorTo) != null;
|
|
211
|
+
const paint = (value, level) => {
|
|
212
|
+
if (level === 0) return { fill: empty, ink: "var(--text-primary)" };
|
|
213
|
+
if (fillProp || continuous) {
|
|
214
|
+
const t = max > 0 ? Math.min(1, Math.max(0, value / max)) : 1;
|
|
215
|
+
const fill = fillProp ? fillProp(value, max) : hexRamp ? lerpHex(colorFrom, colorTo, t) : `color-mix(in oklab, ${colorTo} ${Math.round(t * 100)}%, ${colorFrom})`;
|
|
216
|
+
const measured = textOn(fill);
|
|
217
|
+
return { fill, ink: measured === "currentColor" ? "var(--text-primary)" : measured };
|
|
218
|
+
}
|
|
219
|
+
const strong = levelPercent(level, levels) >= 45;
|
|
220
|
+
return {
|
|
221
|
+
fill: levelFill(level, levels, color),
|
|
222
|
+
ink: strong ? "var(--bg-surface)" : "var(--text-primary)"
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
const legendFill = (l) => paint((max > 0 ? max : 1) * l / levels, l).fill;
|
|
185
226
|
const cell = (d, wrapClass, cellClass, withNumber) => {
|
|
186
227
|
const iso = toLocalIso(d);
|
|
187
228
|
const raw = values.get(iso);
|
|
188
229
|
const value = raw ?? 0;
|
|
189
230
|
const level = heatmapLevel(value, max, levels);
|
|
190
|
-
const fill =
|
|
231
|
+
const { fill, ink } = paint(value, level);
|
|
191
232
|
const formattedDate = formatDay(d);
|
|
192
233
|
const formattedValue = formatValue(value);
|
|
193
234
|
const name = labels.day(formattedDate, formattedValue);
|
|
194
235
|
const label = tooltip ? tooltip({ iso, date: d, value, hasData: raw !== void 0, level, formattedDate, formattedValue }) : name;
|
|
195
236
|
const isSelected = selected === iso;
|
|
196
|
-
const strong = level > 0 && levelPercent(level, levels) >= 45;
|
|
197
237
|
const shared = {
|
|
198
238
|
role: "gridcell",
|
|
199
239
|
"data-day": iso,
|
|
@@ -205,7 +245,7 @@ function CalendarHeatmap({
|
|
|
205
245
|
onFocus: () => {
|
|
206
246
|
if (iso !== activeIso) setActiveIso(iso);
|
|
207
247
|
},
|
|
208
|
-
style: { background: fill, color:
|
|
248
|
+
style: { background: fill, color: ink },
|
|
209
249
|
className: cn(
|
|
210
250
|
cellClass,
|
|
211
251
|
isSelected && "ring-2 ring-[var(--text-primary)]",
|
|
@@ -279,7 +319,7 @@ function CalendarHeatmap({
|
|
|
279
319
|
{
|
|
280
320
|
"data-legend-level": l,
|
|
281
321
|
className: "size-2.5 rounded-sm",
|
|
282
|
-
style: { background:
|
|
322
|
+
style: { background: legendFill(l) }
|
|
283
323
|
},
|
|
284
324
|
l
|
|
285
325
|
)),
|
|
@@ -290,6 +330,7 @@ function CalendarHeatmap({
|
|
|
290
330
|
export {
|
|
291
331
|
CalendarHeatmap,
|
|
292
332
|
DEFAULT_CALENDAR_HEATMAP_LABELS,
|
|
293
|
-
heatmapLevel
|
|
333
|
+
heatmapLevel,
|
|
334
|
+
heatmapWindowEnd
|
|
294
335
|
};
|
|
295
336
|
//# sourceMappingURL=calendar-heatmap.js.map
|