@hoardodile/ui 0.0.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/LICENSE +18 -0
- package/README.md +70 -0
- package/dist/components/alert.d.ts +13 -0
- package/dist/components/alert.js +97 -0
- package/dist/components/alert.js.map +1 -0
- package/dist/components/app-dialog.d.ts +62 -0
- package/dist/components/app-dialog.js +656 -0
- package/dist/components/app-dialog.js.map +1 -0
- package/dist/components/badge.d.ts +11 -0
- package/dist/components/badge.js +70 -0
- package/dist/components/badge.js.map +1 -0
- package/dist/components/breadcrumb.d.ts +12 -0
- package/dist/components/breadcrumb.js +617 -0
- package/dist/components/breadcrumb.js.map +1 -0
- package/dist/components/button.d.ts +17 -0
- package/dist/components/button.js +78 -0
- package/dist/components/button.js.map +1 -0
- package/dist/components/caption-bar.d.ts +44 -0
- package/dist/components/caption-bar.js +827 -0
- package/dist/components/caption-bar.js.map +1 -0
- package/dist/components/chart-card.d.ts +15 -0
- package/dist/components/chart-card.js +27 -0
- package/dist/components/chart-card.js.map +1 -0
- package/dist/components/checkbox.d.ts +6 -0
- package/dist/components/checkbox.js +76 -0
- package/dist/components/checkbox.js.map +1 -0
- package/dist/components/chip-button.d.ts +28 -0
- package/dist/components/chip-button.js +87 -0
- package/dist/components/chip-button.js.map +1 -0
- package/dist/components/close-confirm-dialog.d.ts +19 -0
- package/dist/components/close-confirm-dialog.js +743 -0
- package/dist/components/close-confirm-dialog.js.map +1 -0
- package/dist/components/color-picker.d.ts +32 -0
- package/dist/components/color-picker.js +667 -0
- package/dist/components/color-picker.js.map +1 -0
- package/dist/components/combobox.d.ts +32 -0
- package/dist/components/combobox.js +1211 -0
- package/dist/components/combobox.js.map +1 -0
- package/dist/components/confirm-by-typing-dialog.d.ts +48 -0
- package/dist/components/confirm-by-typing-dialog.js +759 -0
- package/dist/components/confirm-by-typing-dialog.js.map +1 -0
- package/dist/components/confirm-dialog.d.ts +34 -0
- package/dist/components/confirm-dialog.js +710 -0
- package/dist/components/confirm-dialog.js.map +1 -0
- package/dist/components/count-badge.d.ts +13 -0
- package/dist/components/count-badge.js +39 -0
- package/dist/components/count-badge.js.map +1 -0
- package/dist/components/dialog.d.ts +40 -0
- package/dist/components/dialog.js +571 -0
- package/dist/components/dialog.js.map +1 -0
- package/dist/components/dropdown-menu.d.ts +45 -0
- package/dist/components/dropdown-menu.js +1003 -0
- package/dist/components/dropdown-menu.js.map +1 -0
- package/dist/components/dropdown-select.d.ts +29 -0
- package/dist/components/dropdown-select.js +1036 -0
- package/dist/components/dropdown-select.js.map +1 -0
- package/dist/components/empty.d.ts +15 -0
- package/dist/components/empty.js +119 -0
- package/dist/components/empty.js.map +1 -0
- package/dist/components/filter-rail.d.ts +48 -0
- package/dist/components/filter-rail.js +231 -0
- package/dist/components/filter-rail.js.map +1 -0
- package/dist/components/fixed-action-bar.d.ts +24 -0
- package/dist/components/fixed-action-bar.js +48 -0
- package/dist/components/fixed-action-bar.js.map +1 -0
- package/dist/components/font-picker.d.ts +45 -0
- package/dist/components/font-picker.js +759 -0
- package/dist/components/font-picker.js.map +1 -0
- package/dist/components/form.d.ts +17 -0
- package/dist/components/form.js +150 -0
- package/dist/components/form.js.map +1 -0
- package/dist/components/group-label.d.ts +12 -0
- package/dist/components/group-label.js +10 -0
- package/dist/components/group-label.js.map +1 -0
- package/dist/components/icon-tile.d.ts +16 -0
- package/dist/components/icon-tile.js +67 -0
- package/dist/components/icon-tile.js.map +1 -0
- package/dist/components/icon-toggle.d.ts +24 -0
- package/dist/components/icon-toggle.js +81 -0
- package/dist/components/icon-toggle.js.map +1 -0
- package/dist/components/icon.d.ts +2 -0
- package/dist/components/icon.js +49 -0
- package/dist/components/icon.js.map +1 -0
- package/dist/components/image-crop-panel.d.ts +80 -0
- package/dist/components/image-crop-panel.js +1104 -0
- package/dist/components/image-crop-panel.js.map +1 -0
- package/dist/components/image-cropper.d.ts +63 -0
- package/dist/components/image-cropper.js +166 -0
- package/dist/components/image-cropper.js.map +1 -0
- package/dist/components/input-group.d.ts +22 -0
- package/dist/components/input-group.js +270 -0
- package/dist/components/input-group.js.map +1 -0
- package/dist/components/input.d.ts +16 -0
- package/dist/components/input.js +60 -0
- package/dist/components/input.js.map +1 -0
- package/dist/components/label.d.ts +5 -0
- package/dist/components/label.js +40 -0
- package/dist/components/label.js.map +1 -0
- package/dist/components/list-empty-row.d.ts +16 -0
- package/dist/components/list-empty-row.js +40 -0
- package/dist/components/list-empty-row.js.map +1 -0
- package/dist/components/management-empty.d.ts +14 -0
- package/dist/components/management-empty.js +40 -0
- package/dist/components/management-empty.js.map +1 -0
- package/dist/components/management-skeleton.d.ts +11 -0
- package/dist/components/management-skeleton.js +72 -0
- package/dist/components/management-skeleton.js.map +1 -0
- package/dist/components/meta-chip.d.ts +17 -0
- package/dist/components/meta-chip.js +49 -0
- package/dist/components/meta-chip.js.map +1 -0
- package/dist/components/mobile-drawer.d.ts +29 -0
- package/dist/components/mobile-drawer.js +331 -0
- package/dist/components/mobile-drawer.js.map +1 -0
- package/dist/components/page-header.d.ts +20 -0
- package/dist/components/page-header.js +44 -0
- package/dist/components/page-header.js.map +1 -0
- package/dist/components/page-scaffold.d.ts +34 -0
- package/dist/components/page-scaffold.js +157 -0
- package/dist/components/page-scaffold.js.map +1 -0
- package/dist/components/pagination-bar.d.ts +22 -0
- package/dist/components/pagination-bar.js +703 -0
- package/dist/components/pagination-bar.js.map +1 -0
- package/dist/components/pagination.d.ts +22 -0
- package/dist/components/pagination.js +680 -0
- package/dist/components/pagination.js.map +1 -0
- package/dist/components/panel-toolbar.d.ts +37 -0
- package/dist/components/panel-toolbar.js +781 -0
- package/dist/components/panel-toolbar.js.map +1 -0
- package/dist/components/pill-tabs.d.ts +33 -0
- package/dist/components/pill-tabs.js +66 -0
- package/dist/components/pill-tabs.js.map +1 -0
- package/dist/components/plugin-download-consent.d.ts +52 -0
- package/dist/components/plugin-download-consent.js +780 -0
- package/dist/components/plugin-download-consent.js.map +1 -0
- package/dist/components/popover.d.ts +26 -0
- package/dist/components/popover.js +343 -0
- package/dist/components/popover.js.map +1 -0
- package/dist/components/progress.d.ts +10 -0
- package/dist/components/progress.js +103 -0
- package/dist/components/progress.js.map +1 -0
- package/dist/components/query-state-view.d.ts +36 -0
- package/dist/components/query-state-view.js +17 -0
- package/dist/components/query-state-view.js.map +1 -0
- package/dist/components/radio-group.d.ts +8 -0
- package/dist/components/radio-group.js +60 -0
- package/dist/components/radio-group.js.map +1 -0
- package/dist/components/scroll-area.d.ts +7 -0
- package/dist/components/scroll-area.js +80 -0
- package/dist/components/scroll-area.js.map +1 -0
- package/dist/components/search-field.d.ts +49 -0
- package/dist/components/search-field.js +586 -0
- package/dist/components/search-field.js.map +1 -0
- package/dist/components/secondary-stat.d.ts +13 -0
- package/dist/components/secondary-stat.js +56 -0
- package/dist/components/secondary-stat.js.map +1 -0
- package/dist/components/section-header.d.ts +23 -0
- package/dist/components/section-header.js +76 -0
- package/dist/components/section-header.js.map +1 -0
- package/dist/components/section-label.d.ts +20 -0
- package/dist/components/section-label.js +46 -0
- package/dist/components/section-label.js.map +1 -0
- package/dist/components/section-tabs.d.ts +47 -0
- package/dist/components/section-tabs.js +145 -0
- package/dist/components/section-tabs.js.map +1 -0
- package/dist/components/separator.d.ts +14 -0
- package/dist/components/separator.js +49 -0
- package/dist/components/separator.js.map +1 -0
- package/dist/components/sheet.d.ts +22 -0
- package/dist/components/sheet.js +469 -0
- package/dist/components/sheet.js.map +1 -0
- package/dist/components/sidebar.d.ts +69 -0
- package/dist/components/sidebar.js +1645 -0
- package/dist/components/sidebar.js.map +1 -0
- package/dist/components/skeleton.d.ts +8 -0
- package/dist/components/skeleton.js +41 -0
- package/dist/components/skeleton.js.map +1 -0
- package/dist/components/slider.d.ts +6 -0
- package/dist/components/slider.js +75 -0
- package/dist/components/slider.js.map +1 -0
- package/dist/components/sortable-chip-list.d.ts +26 -0
- package/dist/components/sortable-chip-list.js +59 -0
- package/dist/components/sortable-chip-list.js.map +1 -0
- package/dist/components/spinner.d.ts +5 -0
- package/dist/components/spinner.js +65 -0
- package/dist/components/spinner.js.map +1 -0
- package/dist/components/surface.d.ts +13 -0
- package/dist/components/surface.js +51 -0
- package/dist/components/surface.js.map +1 -0
- package/dist/components/switch.d.ts +8 -0
- package/dist/components/switch.js +53 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/table.d.ts +12 -0
- package/dist/components/table.js +129 -0
- package/dist/components/table.js.map +1 -0
- package/dist/components/textarea.d.ts +15 -0
- package/dist/components/textarea.js +58 -0
- package/dist/components/textarea.js.map +1 -0
- package/dist/components/toast.d.ts +19 -0
- package/dist/components/toast.js +805 -0
- package/dist/components/toast.js.map +1 -0
- package/dist/components/toggle.d.ts +12 -0
- package/dist/components/toggle.js +63 -0
- package/dist/components/toggle.js.map +1 -0
- package/dist/components/tooltip.d.ts +9 -0
- package/dist/components/tooltip.js +82 -0
- package/dist/components/tooltip.js.map +1 -0
- package/dist/hooks/use-dialog-footer-actions.d.ts +18 -0
- package/dist/hooks/use-dialog-footer-actions.js +11 -0
- package/dist/hooks/use-dialog-footer-actions.js.map +1 -0
- package/dist/hooks/use-form-field.d.ts +33 -0
- package/dist/hooks/use-form-field.js +36 -0
- package/dist/hooks/use-form-field.js.map +1 -0
- package/dist/hooks/use-mobile.d.ts +34 -0
- package/dist/hooks/use-mobile.js +29 -0
- package/dist/hooks/use-mobile.js.map +1 -0
- package/dist/hooks/use-pinch-pan.d.ts +56 -0
- package/dist/hooks/use-pinch-pan.js +223 -0
- package/dist/hooks/use-pinch-pan.js.map +1 -0
- package/dist/hooks/useMobileBackToClose.d.ts +61 -0
- package/dist/hooks/useMobileBackToClose.js +234 -0
- package/dist/hooks/useMobileBackToClose.js.map +1 -0
- package/dist/icon-BOgL5bpL.d.ts +85 -0
- package/dist/icons/actions.d.ts +4 -0
- package/dist/icons/actions.js +520 -0
- package/dist/icons/actions.js.map +1 -0
- package/dist/icons/icon-style.d.ts +2 -0
- package/dist/icons/icon-style.js +51 -0
- package/dist/icons/icon-style.js.map +1 -0
- package/dist/icons/marks.d.ts +15 -0
- package/dist/icons/marks.js +48 -0
- package/dist/icons/marks.js.map +1 -0
- package/dist/icons/registry.d.ts +177 -0
- package/dist/icons/registry.js +1180 -0
- package/dist/icons/registry.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +653 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/merge-config.d.ts +15 -0
- package/dist/lib/merge-config.js +20 -0
- package/dist/lib/merge-config.js.map +1 -0
- package/dist/lib/pagination.d.ts +19 -0
- package/dist/lib/pagination.js +26 -0
- package/dist/lib/pagination.js.map +1 -0
- package/dist/lib/utils.d.ts +5 -0
- package/dist/lib/utils.js +26 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/styles/theme.css +480 -0
- package/dist/styles/theme.css.map +1 -0
- package/dist/styles/theme.d.ts +2 -0
- package/dist/viewport.d.ts +64 -0
- package/dist/viewport.js +13 -0
- package/dist/viewport.js.map +1 -0
- package/package.json +122 -0
- package/src/components/alert.tsx +76 -0
- package/src/components/app-dialog.tsx +187 -0
- package/src/components/badge.tsx +52 -0
- package/src/components/breadcrumb.tsx +129 -0
- package/src/components/button.tsx +70 -0
- package/src/components/caption-bar.test.tsx +151 -0
- package/src/components/caption-bar.tsx +392 -0
- package/src/components/chart-card.tsx +31 -0
- package/src/components/checkbox.tsx +28 -0
- package/src/components/chip-button.tsx +69 -0
- package/src/components/close-confirm-dialog.tsx +99 -0
- package/src/components/color-picker.tsx +186 -0
- package/src/components/combobox.tsx +335 -0
- package/src/components/confirm-by-typing-dialog.tsx +130 -0
- package/src/components/confirm-dialog.test.tsx +39 -0
- package/src/components/confirm-dialog.tsx +98 -0
- package/src/components/count-badge.tsx +23 -0
- package/src/components/dialog.tsx +315 -0
- package/src/components/dropdown-menu.tsx +311 -0
- package/src/components/dropdown-select.tsx +211 -0
- package/src/components/empty.tsx +104 -0
- package/src/components/filter-rail.tsx +139 -0
- package/src/components/fixed-action-bar.tsx +46 -0
- package/src/components/font-picker.tsx +257 -0
- package/src/components/form.tsx +127 -0
- package/src/components/group-label.tsx +13 -0
- package/src/components/icon-tile.tsx +35 -0
- package/src/components/icon-toggle.tsx +57 -0
- package/src/components/icon.tsx +66 -0
- package/src/components/image-crop-panel.test.tsx +167 -0
- package/src/components/image-crop-panel.tsx +465 -0
- package/src/components/image-cropper.test.tsx +144 -0
- package/src/components/image-cropper.tsx +265 -0
- package/src/components/input-group.tsx +156 -0
- package/src/components/input.tsx +45 -0
- package/src/components/label.tsx +20 -0
- package/src/components/list-empty-row.tsx +27 -0
- package/src/components/management-empty.tsx +25 -0
- package/src/components/management-skeleton.tsx +23 -0
- package/src/components/meta-chip.tsx +40 -0
- package/src/components/mobile-drawer.test.tsx +52 -0
- package/src/components/mobile-drawer.tsx +98 -0
- package/src/components/page-header.tsx +47 -0
- package/src/components/page-scaffold.test.tsx +24 -0
- package/src/components/page-scaffold.tsx +131 -0
- package/src/components/pagination-bar.test.tsx +87 -0
- package/src/components/pagination-bar.tsx +139 -0
- package/src/components/pagination.tsx +139 -0
- package/src/components/panel-toolbar.tsx +99 -0
- package/src/components/pill-tabs.test.tsx +84 -0
- package/src/components/pill-tabs.tsx +77 -0
- package/src/components/plugin-download-consent.test.tsx +90 -0
- package/src/components/plugin-download-consent.tsx +183 -0
- package/src/components/popover.tsx +133 -0
- package/src/components/progress.tsx +88 -0
- package/src/components/query-state-view.tsx +47 -0
- package/src/components/radio-group.tsx +36 -0
- package/src/components/scroll-area.tsx +52 -0
- package/src/components/search-field.test.tsx +86 -0
- package/src/components/search-field.tsx +119 -0
- package/src/components/secondary-stat.tsx +24 -0
- package/src/components/section-header.tsx +54 -0
- package/src/components/section-label.tsx +40 -0
- package/src/components/section-tabs.test.tsx +154 -0
- package/src/components/section-tabs.tsx +190 -0
- package/src/components/separator.tsx +38 -0
- package/src/components/sheet.tsx +170 -0
- package/src/components/sidebar.tsx +725 -0
- package/src/components/skeleton.tsx +18 -0
- package/src/components/slider.tsx +52 -0
- package/src/components/sortable-chip-list.tsx +56 -0
- package/src/components/spinner.tsx +35 -0
- package/src/components/surface.tsx +37 -0
- package/src/components/switch.tsx +30 -0
- package/src/components/table.tsx +116 -0
- package/src/components/textarea.tsx +41 -0
- package/src/components/toast.tsx +239 -0
- package/src/components/toggle.tsx +45 -0
- package/src/components/tooltip.tsx +64 -0
- package/src/hooks/use-dialog-footer-actions.ts +23 -0
- package/src/hooks/use-form-field.ts +62 -0
- package/src/hooks/use-mobile.ts +54 -0
- package/src/hooks/use-pinch-pan.test.tsx +42 -0
- package/src/hooks/use-pinch-pan.ts +340 -0
- package/src/hooks/useMobileBackToClose.test.tsx +140 -0
- package/src/hooks/useMobileBackToClose.ts +508 -0
- package/src/icons/actions.ts +10 -0
- package/src/icons/icon-style.test.tsx +69 -0
- package/src/icons/icon-style.ts +104 -0
- package/src/icons/marks.tsx +72 -0
- package/src/icons/registry.test.ts +65 -0
- package/src/icons/registry.ts +1163 -0
- package/src/index.ts +22 -0
- package/src/lib/merge-config.test.ts +109 -0
- package/src/lib/merge-config.ts +22 -0
- package/src/lib/pagination.test.ts +55 -0
- package/src/lib/pagination.ts +42 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles/scan-contract.test.ts +74 -0
- package/src/styles/theme.css +798 -0
- package/src/test/i18n.tsx +22 -0
- package/src/test/setup.ts +104 -0
- package/src/viewport.ts +69 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { AppDialog } from "@hoardodile/ui/components/app-dialog"
|
|
2
|
+
import { Button } from "@hoardodile/ui/components/button"
|
|
3
|
+
import { Checkbox } from "@hoardodile/ui/components/checkbox"
|
|
4
|
+
import { useEffect, useState } from "react"
|
|
5
|
+
import { useTranslation } from "react-i18next"
|
|
6
|
+
|
|
7
|
+
export type CloseConfirmDialogProps = {
|
|
8
|
+
readonly open: boolean
|
|
9
|
+
readonly onOpenChange: (open: boolean) => void
|
|
10
|
+
readonly onDecide: (action: "tray" | "quit", remember: boolean) => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The shared close confirmation: hide to tray (primary), quit, or cancel,
|
|
15
|
+
* plus a "remember my choice" checkbox. Copy comes from the shared `ui`
|
|
16
|
+
* catalog namespace, so the SPA, the shell's static pages and the
|
|
17
|
+
* workbench all render the same strings for the current language (the
|
|
18
|
+
* native dialog in the Electron main process reads the same catalog
|
|
19
|
+
* directly). The three-button footer follows DESIGN.md: quit at the left
|
|
20
|
+
* edge, cancel and the primary action right-aligned.
|
|
21
|
+
*/
|
|
22
|
+
export function CloseConfirmDialog(props: CloseConfirmDialogProps) {
|
|
23
|
+
const { open, onOpenChange, onDecide } = props
|
|
24
|
+
const { t } = useTranslation("ui", { useSuspense: false })
|
|
25
|
+
const [remember, setRemember] = useState(false)
|
|
26
|
+
|
|
27
|
+
// One-shot choice: the checkbox applies only to the close it was
|
|
28
|
+
// checked on. Resetting it whenever the dialog closes stops a stale
|
|
29
|
+
// check from re-persisting the old action (and undoing the Settings →
|
|
30
|
+
// App preference) the next time the dialog opens.
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!open) setRemember(false)
|
|
33
|
+
}, [open])
|
|
34
|
+
|
|
35
|
+
const footer = (
|
|
36
|
+
<>
|
|
37
|
+
{/* Three-button footer (DESIGN.md — Overlays): the secondary
|
|
38
|
+
function key (quit) sits at the left edge; cancel and the
|
|
39
|
+
primary action (hide to tray) stay right-aligned. */}
|
|
40
|
+
<Button
|
|
41
|
+
variant="secondary"
|
|
42
|
+
className="mr-auto"
|
|
43
|
+
onClick={() => {
|
|
44
|
+
onDecide("quit", remember)
|
|
45
|
+
}}
|
|
46
|
+
data-testid="close-confirm-quit"
|
|
47
|
+
>
|
|
48
|
+
{t("closeConfirm.quit")}
|
|
49
|
+
</Button>
|
|
50
|
+
<Button
|
|
51
|
+
variant="secondary"
|
|
52
|
+
onClick={() => {
|
|
53
|
+
onOpenChange(false)
|
|
54
|
+
}}
|
|
55
|
+
data-testid="close-confirm-cancel"
|
|
56
|
+
>
|
|
57
|
+
{t("closeConfirm.cancel")}
|
|
58
|
+
</Button>
|
|
59
|
+
<Button
|
|
60
|
+
variant="default"
|
|
61
|
+
onClick={() => {
|
|
62
|
+
onDecide("tray", remember)
|
|
63
|
+
}}
|
|
64
|
+
data-testid="close-confirm-tray"
|
|
65
|
+
>
|
|
66
|
+
{t("closeConfirm.tray")}
|
|
67
|
+
</Button>
|
|
68
|
+
</>
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<AppDialog
|
|
73
|
+
open={open}
|
|
74
|
+
onOpenChange={onOpenChange}
|
|
75
|
+
title={t("closeConfirm.title")}
|
|
76
|
+
description={t("closeConfirm.description")}
|
|
77
|
+
footer={footer}
|
|
78
|
+
>
|
|
79
|
+
{/* The checkbox is the only toggle (no label row); `py-2` keeps
|
|
80
|
+
it from sitting flush against the description and the
|
|
81
|
+
footer hairline. */}
|
|
82
|
+
<div
|
|
83
|
+
className="flex items-center gap-2 py-2 select-none"
|
|
84
|
+
data-testid="close-confirm-remember"
|
|
85
|
+
>
|
|
86
|
+
<Checkbox
|
|
87
|
+
checked={remember}
|
|
88
|
+
onCheckedChange={(checked) => {
|
|
89
|
+
setRemember(checked === true)
|
|
90
|
+
}}
|
|
91
|
+
aria-label={t("closeConfirm.remember")}
|
|
92
|
+
/>
|
|
93
|
+
<span className="text-xs text-muted-foreground">
|
|
94
|
+
{t("closeConfirm.remember")}
|
|
95
|
+
</span>
|
|
96
|
+
</div>
|
|
97
|
+
</AppDialog>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { Cross } from "@hoardodile/ui/icons/marks"
|
|
2
|
+
import { Bookmark, Palette } from "@hoardodile/ui/icons/registry"
|
|
3
|
+
import { cn } from "@hoardodile/ui/lib/utils"
|
|
4
|
+
import { type ReactNode } from "react"
|
|
5
|
+
import { useTranslation } from "react-i18next"
|
|
6
|
+
|
|
7
|
+
export type ColorPickerProps = {
|
|
8
|
+
readonly value: string
|
|
9
|
+
readonly onChange: (color: string) => void
|
|
10
|
+
/** Default preset colors — one dot each. */
|
|
11
|
+
readonly presets?: readonly string[]
|
|
12
|
+
/** User presets appended after the defaults; their dots carry a
|
|
13
|
+
hover-revealed remove. */
|
|
14
|
+
readonly userPresets?: readonly string[]
|
|
15
|
+
/** Pins the current custom value to the user presets. */
|
|
16
|
+
readonly onAddPreset?: () => void
|
|
17
|
+
readonly onRemovePreset?: (index: number) => void
|
|
18
|
+
/** Special-style ids rendered as chips by the injected renderer
|
|
19
|
+
(the consumer owns the surface, e.g. gradient chips). */
|
|
20
|
+
readonly specialStyles?: readonly string[]
|
|
21
|
+
readonly renderSpecialStyle?: (
|
|
22
|
+
style: string,
|
|
23
|
+
active: boolean,
|
|
24
|
+
onPick: () => void,
|
|
25
|
+
) => ReactNode
|
|
26
|
+
readonly placeholder?: string
|
|
27
|
+
readonly testId?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Color field: the current value leads as a removable pill (a dot + hex
|
|
32
|
+
* in a muted pill, or the special-style chip), the default and user
|
|
33
|
+
* presets merge into one row of dots — whitespace parts the registers —
|
|
34
|
+
* and the special styles ride below as their own chips. Custom colors
|
|
35
|
+
* enter through the dashed swatch at the row's end (a real color input
|
|
36
|
+
* underneath — the OS picker opens on click).
|
|
37
|
+
*/
|
|
38
|
+
export function ColorPicker(props: ColorPickerProps) {
|
|
39
|
+
const {
|
|
40
|
+
value,
|
|
41
|
+
onChange,
|
|
42
|
+
presets = [],
|
|
43
|
+
userPresets = [],
|
|
44
|
+
onAddPreset,
|
|
45
|
+
onRemovePreset,
|
|
46
|
+
specialStyles = [],
|
|
47
|
+
renderSpecialStyle,
|
|
48
|
+
placeholder,
|
|
49
|
+
testId,
|
|
50
|
+
} = props
|
|
51
|
+
const { t } = useTranslation("ui", { useSuspense: false })
|
|
52
|
+
|
|
53
|
+
const hasColor = value !== ""
|
|
54
|
+
const isSpecial = specialStyles.includes(value)
|
|
55
|
+
const isCustom =
|
|
56
|
+
hasColor &&
|
|
57
|
+
!isSpecial &&
|
|
58
|
+
![...presets, ...userPresets]
|
|
59
|
+
.map((c) => c.toLowerCase())
|
|
60
|
+
.includes(value.toLowerCase())
|
|
61
|
+
|
|
62
|
+
const inputValue = hasColor && !isSpecial ? value : "#9D9D9D"
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div className="flex flex-col gap-2.5" data-testid={testId}>
|
|
66
|
+
<span className="flex items-center gap-1.5">
|
|
67
|
+
{hasColor && (
|
|
68
|
+
<span
|
|
69
|
+
className={cn(
|
|
70
|
+
"inline-flex h-chip w-fit items-center gap-1 rounded-md pr-1.5 text-xs text-foreground",
|
|
71
|
+
!isSpecial && "bg-muted pl-2",
|
|
72
|
+
)}
|
|
73
|
+
>
|
|
74
|
+
{isSpecial && renderSpecialStyle !== undefined ? (
|
|
75
|
+
<span className="pointer-events-none">
|
|
76
|
+
{renderSpecialStyle(value, true, () => {})}
|
|
77
|
+
</span>
|
|
78
|
+
) : (
|
|
79
|
+
<>
|
|
80
|
+
<span
|
|
81
|
+
className="size-2 shrink-0 rounded-full"
|
|
82
|
+
style={{ backgroundColor: value }}
|
|
83
|
+
/>
|
|
84
|
+
<span className="-translate-y-px">{value}</span>
|
|
85
|
+
</>
|
|
86
|
+
)}
|
|
87
|
+
<button
|
|
88
|
+
type="button"
|
|
89
|
+
onClick={() => onChange("")}
|
|
90
|
+
aria-label={t("colorPicker.clear")}
|
|
91
|
+
className="flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 bg-transparent p-0 text-muted-foreground hover:text-foreground"
|
|
92
|
+
data-testid={testId !== undefined ? `${testId}-clear` : undefined}
|
|
93
|
+
>
|
|
94
|
+
<Cross className="size-3" />
|
|
95
|
+
</button>
|
|
96
|
+
</span>
|
|
97
|
+
)}
|
|
98
|
+
{isCustom && onAddPreset !== undefined && (
|
|
99
|
+
<button
|
|
100
|
+
type="button"
|
|
101
|
+
onClick={onAddPreset}
|
|
102
|
+
aria-label={t("colorPicker.addPreset")}
|
|
103
|
+
className="flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 bg-transparent p-0 text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
104
|
+
data-testid={
|
|
105
|
+
testId !== undefined ? `${testId}-add-preset` : undefined
|
|
106
|
+
}
|
|
107
|
+
>
|
|
108
|
+
<Bookmark className="size-3" />
|
|
109
|
+
</button>
|
|
110
|
+
)}
|
|
111
|
+
</span>
|
|
112
|
+
|
|
113
|
+
{/* One row of dots: the default presets, then the user's own —
|
|
114
|
+
parted by whitespace, not labels. A my-preset dot's remove
|
|
115
|
+
appears on hover (pinned on the last one). */}
|
|
116
|
+
<span className="flex flex-wrap items-center gap-1.5">
|
|
117
|
+
{presets.map((color) => (
|
|
118
|
+
<button
|
|
119
|
+
type="button"
|
|
120
|
+
key={color}
|
|
121
|
+
onClick={() => onChange(color)}
|
|
122
|
+
className="size-5 shrink-0 cursor-pointer rounded-full border-0 p-0"
|
|
123
|
+
style={{ backgroundColor: color }}
|
|
124
|
+
aria-label={color}
|
|
125
|
+
/>
|
|
126
|
+
))}
|
|
127
|
+
{userPresets.map((color, index) => (
|
|
128
|
+
<span key={`${color}-${index}`} className="group/color relative ml-2">
|
|
129
|
+
<button
|
|
130
|
+
type="button"
|
|
131
|
+
onClick={() => onChange(color)}
|
|
132
|
+
className="block size-5 cursor-pointer rounded-full border-0 p-0"
|
|
133
|
+
style={{ backgroundColor: color }}
|
|
134
|
+
aria-label={color}
|
|
135
|
+
/>
|
|
136
|
+
{onRemovePreset !== undefined && (
|
|
137
|
+
<button
|
|
138
|
+
type="button"
|
|
139
|
+
onClick={() => onRemovePreset(index)}
|
|
140
|
+
aria-label={t("colorPicker.removePresetAria", { color })}
|
|
141
|
+
className={cn(
|
|
142
|
+
"absolute -top-1 -right-1 flex size-3.5 cursor-pointer items-center justify-center rounded-full border border-border bg-background p-0 text-muted-foreground hover:text-foreground",
|
|
143
|
+
index === userPresets.length - 1
|
|
144
|
+
? "opacity-100"
|
|
145
|
+
: "opacity-0 transition-opacity group-hover/color:opacity-100",
|
|
146
|
+
)}
|
|
147
|
+
>
|
|
148
|
+
<Cross className="size-2" />
|
|
149
|
+
</button>
|
|
150
|
+
)}
|
|
151
|
+
</span>
|
|
152
|
+
))}
|
|
153
|
+
{/* The custom swatch — dashed until a custom color is set, then
|
|
154
|
+
filled; the real color input underneath opens the OS
|
|
155
|
+
picker. */}
|
|
156
|
+
<span
|
|
157
|
+
title={t("colorPicker.customSwatch")}
|
|
158
|
+
className={cn(
|
|
159
|
+
"relative ml-2 flex size-5 shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-full",
|
|
160
|
+
isCustom ? "" : "border border-dashed border-border-strong",
|
|
161
|
+
)}
|
|
162
|
+
style={isCustom ? { backgroundColor: value } : undefined}
|
|
163
|
+
>
|
|
164
|
+
{!isCustom && <Palette className="size-3 text-muted-foreground" />}
|
|
165
|
+
<input
|
|
166
|
+
type="color"
|
|
167
|
+
value={inputValue}
|
|
168
|
+
onChange={(e) => onChange(e.target.value)}
|
|
169
|
+
className="absolute inset-0 cursor-pointer opacity-0"
|
|
170
|
+
data-testid={testId !== undefined ? `${testId}-input` : undefined}
|
|
171
|
+
title={placeholder}
|
|
172
|
+
aria-label={t("colorPicker.customSwatch")}
|
|
173
|
+
/>
|
|
174
|
+
</span>
|
|
175
|
+
</span>
|
|
176
|
+
|
|
177
|
+
{specialStyles.length > 0 && renderSpecialStyle !== undefined ? (
|
|
178
|
+
<span className="flex flex-wrap gap-1.5">
|
|
179
|
+
{specialStyles.map((style) =>
|
|
180
|
+
renderSpecialStyle(style, value === style, () => onChange(style)),
|
|
181
|
+
)}
|
|
182
|
+
</span>
|
|
183
|
+
) : null}
|
|
184
|
+
</div>
|
|
185
|
+
)
|
|
186
|
+
}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import * as React from "react"
|
|
5
|
+
import { Combobox as ComboboxPrimitive } from "@base-ui/react"
|
|
6
|
+
|
|
7
|
+
import { cn } from "@hoardodile/ui/lib/utils"
|
|
8
|
+
import { useMobileBackToClose } from "@hoardodile/ui/hooks/useMobileBackToClose"
|
|
9
|
+
import { Button } from "@hoardodile/ui/components/button"
|
|
10
|
+
import {
|
|
11
|
+
InputGroup,
|
|
12
|
+
InputGroupAddon,
|
|
13
|
+
InputGroupButton,
|
|
14
|
+
InputGroupInput,
|
|
15
|
+
} from "@hoardodile/ui/components/input-group"
|
|
16
|
+
import { AltArrowDown } from "@hoardodile/ui/icons/registry"
|
|
17
|
+
import { Check, Cross } from "@hoardodile/ui/icons/marks"
|
|
18
|
+
|
|
19
|
+
function Combobox<Value, Multiple extends boolean | undefined = false>({
|
|
20
|
+
open,
|
|
21
|
+
defaultOpen,
|
|
22
|
+
onOpenChange,
|
|
23
|
+
...props
|
|
24
|
+
}: Omit<ComboboxPrimitive.Root.Props<Value, Multiple>, "onOpenChange"> & {
|
|
25
|
+
/**
|
|
26
|
+
* `eventDetails` is absent for closes triggered by the mobile back
|
|
27
|
+
* gesture (a synthetic close with no underlying Base UI event).
|
|
28
|
+
*/
|
|
29
|
+
onOpenChange?: (
|
|
30
|
+
open: boolean,
|
|
31
|
+
eventDetails?: ComboboxPrimitive.Root.ChangeEventDetails,
|
|
32
|
+
) => void
|
|
33
|
+
}) {
|
|
34
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(
|
|
35
|
+
defaultOpen ?? false,
|
|
36
|
+
)
|
|
37
|
+
const isControlled = open !== undefined
|
|
38
|
+
const currentOpen = isControlled ? open : uncontrolledOpen
|
|
39
|
+
function handleOpenChange(
|
|
40
|
+
next: boolean,
|
|
41
|
+
eventDetails?: ComboboxPrimitive.Root.ChangeEventDetails,
|
|
42
|
+
) {
|
|
43
|
+
if (!isControlled) setUncontrolledOpen(next)
|
|
44
|
+
onOpenChange?.(next, eventDetails)
|
|
45
|
+
}
|
|
46
|
+
useMobileBackToClose(currentOpen, handleOpenChange)
|
|
47
|
+
return (
|
|
48
|
+
<ComboboxPrimitive.Root<Value, Multiple>
|
|
49
|
+
open={currentOpen}
|
|
50
|
+
onOpenChange={handleOpenChange}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {
|
|
57
|
+
return <ComboboxPrimitive.Value data-slot="combobox-value" {...props} />
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function ComboboxTrigger({
|
|
61
|
+
className,
|
|
62
|
+
children,
|
|
63
|
+
...props
|
|
64
|
+
}: ComboboxPrimitive.Trigger.Props) {
|
|
65
|
+
return (
|
|
66
|
+
<ComboboxPrimitive.Trigger
|
|
67
|
+
data-slot="combobox-trigger"
|
|
68
|
+
className={cn("[&_svg:not([class*='size-'])]:size-4", className)}
|
|
69
|
+
{...props}
|
|
70
|
+
>
|
|
71
|
+
{children}
|
|
72
|
+
<AltArrowDown className="pointer-events-none size-4 text-muted-foreground" />
|
|
73
|
+
</ComboboxPrimitive.Trigger>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {
|
|
78
|
+
return (
|
|
79
|
+
<ComboboxPrimitive.Clear
|
|
80
|
+
data-slot="combobox-clear"
|
|
81
|
+
render={<InputGroupButton variant="ghost" size="icon-xs" />}
|
|
82
|
+
className={cn(className)}
|
|
83
|
+
{...props}
|
|
84
|
+
>
|
|
85
|
+
<Cross className="pointer-events-none" />
|
|
86
|
+
</ComboboxPrimitive.Clear>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function ComboboxInput({
|
|
91
|
+
className,
|
|
92
|
+
children,
|
|
93
|
+
disabled = false,
|
|
94
|
+
showTrigger = true,
|
|
95
|
+
showClear = false,
|
|
96
|
+
...props
|
|
97
|
+
}: ComboboxPrimitive.Input.Props & {
|
|
98
|
+
showTrigger?: boolean
|
|
99
|
+
showClear?: boolean
|
|
100
|
+
}) {
|
|
101
|
+
return (
|
|
102
|
+
<InputGroup className={cn("w-auto", className)}>
|
|
103
|
+
<ComboboxPrimitive.Input
|
|
104
|
+
render={<InputGroupInput disabled={disabled} />}
|
|
105
|
+
{...props}
|
|
106
|
+
/>
|
|
107
|
+
<InputGroupAddon align="inline-end">
|
|
108
|
+
{showTrigger && (
|
|
109
|
+
<InputGroupButton
|
|
110
|
+
size="icon-xs"
|
|
111
|
+
variant="ghost"
|
|
112
|
+
render={<ComboboxTrigger />}
|
|
113
|
+
data-slot="input-group-button"
|
|
114
|
+
className="group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent"
|
|
115
|
+
disabled={disabled}
|
|
116
|
+
/>
|
|
117
|
+
)}
|
|
118
|
+
{showClear && <ComboboxClear disabled={disabled} />}
|
|
119
|
+
</InputGroupAddon>
|
|
120
|
+
{children}
|
|
121
|
+
</InputGroup>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function ComboboxContent({
|
|
126
|
+
className,
|
|
127
|
+
side = "bottom",
|
|
128
|
+
sideOffset = 6,
|
|
129
|
+
align = "start",
|
|
130
|
+
alignOffset = 0,
|
|
131
|
+
anchor,
|
|
132
|
+
...props
|
|
133
|
+
}: ComboboxPrimitive.Popup.Props &
|
|
134
|
+
Pick<
|
|
135
|
+
ComboboxPrimitive.Positioner.Props,
|
|
136
|
+
"side" | "align" | "sideOffset" | "alignOffset" | "anchor"
|
|
137
|
+
>) {
|
|
138
|
+
return (
|
|
139
|
+
<ComboboxPrimitive.Portal>
|
|
140
|
+
<ComboboxPrimitive.Positioner
|
|
141
|
+
side={side}
|
|
142
|
+
sideOffset={sideOffset}
|
|
143
|
+
align={align}
|
|
144
|
+
alignOffset={alignOffset}
|
|
145
|
+
anchor={anchor}
|
|
146
|
+
className="isolate z-50"
|
|
147
|
+
>
|
|
148
|
+
<ComboboxPrimitive.Popup
|
|
149
|
+
data-slot="combobox-content"
|
|
150
|
+
data-chips={!!anchor}
|
|
151
|
+
className={cn("group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-md bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-(--anchor-width) data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
|
152
|
+
{...props}
|
|
153
|
+
/>
|
|
154
|
+
</ComboboxPrimitive.Positioner>
|
|
155
|
+
</ComboboxPrimitive.Portal>
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {
|
|
160
|
+
return (
|
|
161
|
+
<ComboboxPrimitive.List
|
|
162
|
+
data-slot="combobox-list"
|
|
163
|
+
className={cn(
|
|
164
|
+
"no-scrollbar max-h-[min(calc(--spacing(72)---spacing(9)),calc(var(--available-height)---spacing(9)))] scroll-py-1 overflow-y-auto overscroll-contain p-1 data-empty:p-0",
|
|
165
|
+
className
|
|
166
|
+
)}
|
|
167
|
+
{...props}
|
|
168
|
+
/>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function ComboboxItem({
|
|
173
|
+
className,
|
|
174
|
+
children,
|
|
175
|
+
...props
|
|
176
|
+
}: ComboboxPrimitive.Item.Props) {
|
|
177
|
+
return (
|
|
178
|
+
<ComboboxPrimitive.Item
|
|
179
|
+
data-slot="combobox-item"
|
|
180
|
+
className={cn(
|
|
181
|
+
"relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
182
|
+
className
|
|
183
|
+
)}
|
|
184
|
+
{...props}
|
|
185
|
+
>
|
|
186
|
+
{children}
|
|
187
|
+
<ComboboxPrimitive.ItemIndicator
|
|
188
|
+
render={
|
|
189
|
+
<span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
|
|
190
|
+
}
|
|
191
|
+
>
|
|
192
|
+
<Check className="pointer-events-none" />
|
|
193
|
+
</ComboboxPrimitive.ItemIndicator>
|
|
194
|
+
</ComboboxPrimitive.Item>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) {
|
|
199
|
+
return (
|
|
200
|
+
<ComboboxPrimitive.Group
|
|
201
|
+
data-slot="combobox-group"
|
|
202
|
+
className={cn(className)}
|
|
203
|
+
{...props}
|
|
204
|
+
/>
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function ComboboxLabel({
|
|
209
|
+
className,
|
|
210
|
+
...props
|
|
211
|
+
}: ComboboxPrimitive.GroupLabel.Props) {
|
|
212
|
+
return (
|
|
213
|
+
<ComboboxPrimitive.GroupLabel
|
|
214
|
+
data-slot="combobox-label"
|
|
215
|
+
className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)}
|
|
216
|
+
{...props}
|
|
217
|
+
/>
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) {
|
|
222
|
+
return (
|
|
223
|
+
<ComboboxPrimitive.Collection data-slot="combobox-collection" {...props} />
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {
|
|
228
|
+
return (
|
|
229
|
+
<ComboboxPrimitive.Empty
|
|
230
|
+
data-slot="combobox-empty"
|
|
231
|
+
className={cn(
|
|
232
|
+
"hidden w-full justify-center py-2 text-center text-sm text-muted-foreground group-data-empty/combobox-content:flex",
|
|
233
|
+
className
|
|
234
|
+
)}
|
|
235
|
+
{...props}
|
|
236
|
+
/>
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function ComboboxSeparator({
|
|
241
|
+
className,
|
|
242
|
+
...props
|
|
243
|
+
}: ComboboxPrimitive.Separator.Props) {
|
|
244
|
+
return (
|
|
245
|
+
<ComboboxPrimitive.Separator
|
|
246
|
+
data-slot="combobox-separator"
|
|
247
|
+
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
|
248
|
+
{...props}
|
|
249
|
+
/>
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function ComboboxChips({
|
|
254
|
+
className,
|
|
255
|
+
...props
|
|
256
|
+
}: React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> &
|
|
257
|
+
ComboboxPrimitive.Chips.Props) {
|
|
258
|
+
return (
|
|
259
|
+
<ComboboxPrimitive.Chips
|
|
260
|
+
data-slot="combobox-chips"
|
|
261
|
+
className={cn(
|
|
262
|
+
"flex min-h-9 flex-wrap items-center gap-1.5 rounded-md border border-input bg-transparent bg-clip-padding px-2.5 py-1.5 text-sm shadow-xs transition-[color,box-shadow] focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 has-aria-invalid:border-destructive has-aria-invalid:ring-3 has-aria-invalid:ring-destructive/20 has-data-[slot=combobox-chip]:px-1.5 dark:bg-input/30 dark:has-aria-invalid:border-destructive/50 dark:has-aria-invalid:ring-destructive/40",
|
|
263
|
+
className
|
|
264
|
+
)}
|
|
265
|
+
{...props}
|
|
266
|
+
/>
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function ComboboxChip({
|
|
271
|
+
className,
|
|
272
|
+
children,
|
|
273
|
+
showRemove = true,
|
|
274
|
+
...props
|
|
275
|
+
}: ComboboxPrimitive.Chip.Props & {
|
|
276
|
+
showRemove?: boolean
|
|
277
|
+
}) {
|
|
278
|
+
return (
|
|
279
|
+
<ComboboxPrimitive.Chip
|
|
280
|
+
data-slot="combobox-chip"
|
|
281
|
+
className={cn(
|
|
282
|
+
"flex h-[calc(--spacing(5.5))] w-fit items-center justify-center gap-1 rounded-sm bg-muted px-1.5 text-xs font-medium whitespace-nowrap text-foreground has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0",
|
|
283
|
+
className
|
|
284
|
+
)}
|
|
285
|
+
{...props}
|
|
286
|
+
>
|
|
287
|
+
{children}
|
|
288
|
+
{showRemove && (
|
|
289
|
+
<ComboboxPrimitive.ChipRemove
|
|
290
|
+
render={<Button variant="ghost" size="icon-xs" />}
|
|
291
|
+
className="-ml-1 opacity-50 hover:opacity-100"
|
|
292
|
+
data-slot="combobox-chip-remove"
|
|
293
|
+
>
|
|
294
|
+
<Cross className="pointer-events-none" />
|
|
295
|
+
</ComboboxPrimitive.ChipRemove>
|
|
296
|
+
)}
|
|
297
|
+
</ComboboxPrimitive.Chip>
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function ComboboxChipsInput({
|
|
302
|
+
className,
|
|
303
|
+
...props
|
|
304
|
+
}: ComboboxPrimitive.Input.Props) {
|
|
305
|
+
return (
|
|
306
|
+
<ComboboxPrimitive.Input
|
|
307
|
+
data-slot="combobox-chip-input"
|
|
308
|
+
className={cn("min-w-16 flex-1 outline-none", className)}
|
|
309
|
+
{...props}
|
|
310
|
+
/>
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function useComboboxAnchor() {
|
|
315
|
+
return React.useRef<HTMLDivElement | null>(null)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export {
|
|
319
|
+
Combobox,
|
|
320
|
+
ComboboxInput,
|
|
321
|
+
ComboboxContent,
|
|
322
|
+
ComboboxList,
|
|
323
|
+
ComboboxItem,
|
|
324
|
+
ComboboxGroup,
|
|
325
|
+
ComboboxLabel,
|
|
326
|
+
ComboboxCollection,
|
|
327
|
+
ComboboxEmpty,
|
|
328
|
+
ComboboxSeparator,
|
|
329
|
+
ComboboxChips,
|
|
330
|
+
ComboboxChip,
|
|
331
|
+
ComboboxChipsInput,
|
|
332
|
+
ComboboxTrigger,
|
|
333
|
+
ComboboxValue,
|
|
334
|
+
useComboboxAnchor,
|
|
335
|
+
}
|