@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,154 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from "@testing-library/react"
|
|
2
|
+
import { describe, expect, it, vi } from "vitest"
|
|
3
|
+
import { SectionTabs } from "./section-tabs"
|
|
4
|
+
|
|
5
|
+
const ITEMS = [
|
|
6
|
+
{ value: "a", label: "Alpha" },
|
|
7
|
+
{ value: "b", label: "Beta" },
|
|
8
|
+
{ value: "c", label: "Gamma" },
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
describe("SectionTabs", () => {
|
|
12
|
+
it("renders a tablist with one tab per item and the active state", () => {
|
|
13
|
+
render(<SectionTabs value="b" items={ITEMS} />)
|
|
14
|
+
expect(screen.getByRole("tablist")).toBeInTheDocument()
|
|
15
|
+
expect(screen.getByRole("tab", { name: "Alpha" })).toHaveAttribute(
|
|
16
|
+
"aria-selected",
|
|
17
|
+
"false",
|
|
18
|
+
)
|
|
19
|
+
const beta = screen.getByRole("tab", { name: "Beta" })
|
|
20
|
+
expect(beta).toHaveAttribute("aria-selected", "true")
|
|
21
|
+
expect(beta).toHaveAttribute("tabindex", "0")
|
|
22
|
+
expect(screen.getByRole("tab", { name: "Alpha" })).toHaveAttribute(
|
|
23
|
+
"tabindex",
|
|
24
|
+
"-1",
|
|
25
|
+
)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it("reports the picked value through onChange", () => {
|
|
29
|
+
const onChange = vi.fn()
|
|
30
|
+
render(<SectionTabs value="a" items={ITEMS} onChange={onChange} />)
|
|
31
|
+
fireEvent.click(screen.getByRole("tab", { name: "Gamma" }))
|
|
32
|
+
expect(onChange).toHaveBeenCalledWith("c")
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it("moves activation and focus with the arrow keys", () => {
|
|
36
|
+
const onChange = vi.fn()
|
|
37
|
+
const { rerender } = render(
|
|
38
|
+
<SectionTabs value="a" items={ITEMS} onChange={onChange} />,
|
|
39
|
+
)
|
|
40
|
+
const alpha = () => screen.getByRole("tab", { name: "Alpha" })
|
|
41
|
+
const beta = () => screen.getByRole("tab", { name: "Beta" })
|
|
42
|
+
const gamma = () => screen.getByRole("tab", { name: "Gamma" })
|
|
43
|
+
|
|
44
|
+
fireEvent.keyDown(alpha(), { key: "ArrowRight" })
|
|
45
|
+
expect(onChange).toHaveBeenLastCalledWith("b")
|
|
46
|
+
expect(beta()).toHaveFocus()
|
|
47
|
+
rerender(<SectionTabs value="b" items={ITEMS} onChange={onChange} />)
|
|
48
|
+
|
|
49
|
+
fireEvent.keyDown(beta(), { key: "ArrowLeft" })
|
|
50
|
+
expect(onChange).toHaveBeenLastCalledWith("a")
|
|
51
|
+
expect(alpha()).toHaveFocus()
|
|
52
|
+
rerender(<SectionTabs value="a" items={ITEMS} onChange={onChange} />)
|
|
53
|
+
|
|
54
|
+
fireEvent.keyDown(alpha(), { key: "ArrowLeft" })
|
|
55
|
+
expect(onChange).toHaveBeenLastCalledWith("c")
|
|
56
|
+
rerender(<SectionTabs value="c" items={ITEMS} onChange={onChange} />)
|
|
57
|
+
|
|
58
|
+
fireEvent.keyDown(gamma(), { key: "Home" })
|
|
59
|
+
expect(onChange).toHaveBeenLastCalledWith("a")
|
|
60
|
+
rerender(<SectionTabs value="a" items={ITEMS} onChange={onChange} />)
|
|
61
|
+
|
|
62
|
+
fireEvent.keyDown(alpha(), { key: "End" })
|
|
63
|
+
expect(onChange).toHaveBeenLastCalledWith("c")
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it("mounts only the active panel and links it to the active tab", () => {
|
|
67
|
+
const { rerender } = render(
|
|
68
|
+
<SectionTabs
|
|
69
|
+
value="a"
|
|
70
|
+
items={[
|
|
71
|
+
{ ...ITEMS[0]!, panel: <p>Panel Alpha</p> },
|
|
72
|
+
{ ...ITEMS[1]!, panel: <p>Panel Beta</p> },
|
|
73
|
+
]}
|
|
74
|
+
/>,
|
|
75
|
+
)
|
|
76
|
+
const panel = screen.getByRole("tabpanel")
|
|
77
|
+
expect(panel).toHaveTextContent("Panel Alpha")
|
|
78
|
+
expect(panel).toHaveAttribute(
|
|
79
|
+
"aria-labelledby",
|
|
80
|
+
screen.getByRole("tab", { name: "Alpha" }).id,
|
|
81
|
+
)
|
|
82
|
+
expect(screen.queryByText("Panel Beta")).not.toBeInTheDocument()
|
|
83
|
+
|
|
84
|
+
rerender(
|
|
85
|
+
<SectionTabs
|
|
86
|
+
value="b"
|
|
87
|
+
items={[
|
|
88
|
+
{ ...ITEMS[0]!, panel: <p>Panel Alpha</p> },
|
|
89
|
+
{ ...ITEMS[1]!, panel: <p>Panel Beta</p> },
|
|
90
|
+
]}
|
|
91
|
+
/>,
|
|
92
|
+
)
|
|
93
|
+
expect(screen.getByRole("tabpanel")).toHaveTextContent("Panel Beta")
|
|
94
|
+
expect(screen.queryByText("Panel Alpha")).not.toBeInTheDocument()
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it("replaces the default panel spacing per item", () => {
|
|
98
|
+
render(
|
|
99
|
+
<SectionTabs
|
|
100
|
+
value="a"
|
|
101
|
+
items={[
|
|
102
|
+
{
|
|
103
|
+
...ITEMS[0]!,
|
|
104
|
+
panelClassName: "pt-3",
|
|
105
|
+
panel: <p>Panel Alpha</p>,
|
|
106
|
+
},
|
|
107
|
+
]}
|
|
108
|
+
/>,
|
|
109
|
+
)
|
|
110
|
+
expect(screen.getByRole("tabpanel").className).toContain("pt-3")
|
|
111
|
+
expect(screen.getByRole("tabpanel").className).not.toContain("mt-4")
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it("lets a render slot replace the trigger with ARIA props to spread", () => {
|
|
115
|
+
render(
|
|
116
|
+
<SectionTabs
|
|
117
|
+
value="a"
|
|
118
|
+
items={[
|
|
119
|
+
{
|
|
120
|
+
...ITEMS[0]!,
|
|
121
|
+
testId: "link-tab",
|
|
122
|
+
render: (active, className, trigger) => (
|
|
123
|
+
<a
|
|
124
|
+
href="/alpha"
|
|
125
|
+
{...trigger}
|
|
126
|
+
className={className}
|
|
127
|
+
data-active={active}
|
|
128
|
+
>
|
|
129
|
+
Alpha link
|
|
130
|
+
</a>
|
|
131
|
+
),
|
|
132
|
+
},
|
|
133
|
+
{ ...ITEMS[1]!, label: "Beta" },
|
|
134
|
+
]}
|
|
135
|
+
/>,
|
|
136
|
+
)
|
|
137
|
+
const link = screen.getByTestId("link-tab")
|
|
138
|
+
expect(link).toHaveAttribute("role", "tab")
|
|
139
|
+
expect(link).toHaveAttribute("aria-selected", "true")
|
|
140
|
+
expect(link.className).toContain("uppercase")
|
|
141
|
+
expect(screen.getByRole("tab", { name: "Beta" })).toBeInTheDocument()
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it("renders the right-hand controls in the tab row", () => {
|
|
145
|
+
render(
|
|
146
|
+
<SectionTabs
|
|
147
|
+
value="a"
|
|
148
|
+
items={ITEMS}
|
|
149
|
+
controls={<span data-testid="row-control">Sort</span>}
|
|
150
|
+
/>,
|
|
151
|
+
)
|
|
152
|
+
expect(screen.getByTestId("row-control")).toBeInTheDocument()
|
|
153
|
+
})
|
|
154
|
+
})
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { cn } from "@hoardodile/ui/lib/utils"
|
|
2
|
+
import {
|
|
3
|
+
Fragment,
|
|
4
|
+
type KeyboardEvent,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
useId,
|
|
7
|
+
useRef,
|
|
8
|
+
} from "react"
|
|
9
|
+
|
|
10
|
+
export type SectionTabTriggerProps = {
|
|
11
|
+
readonly id: string
|
|
12
|
+
readonly role: "tab"
|
|
13
|
+
readonly tabIndex: number
|
|
14
|
+
readonly "aria-selected": boolean
|
|
15
|
+
readonly "aria-controls": string | undefined
|
|
16
|
+
readonly "data-testid": string | undefined
|
|
17
|
+
readonly onKeyDown: (event: KeyboardEvent<HTMLElement>) => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type SectionTabItem<T extends string> = {
|
|
21
|
+
readonly value: T
|
|
22
|
+
readonly label: ReactNode
|
|
23
|
+
readonly testId?: string
|
|
24
|
+
/** Replaces the trigger element (e.g. a router Link) — receives the
|
|
25
|
+
active state, the trigger classes and the ARIA/keyboard props to
|
|
26
|
+
spread onto the element. */
|
|
27
|
+
readonly render?: (
|
|
28
|
+
active: boolean,
|
|
29
|
+
className: string,
|
|
30
|
+
trigger: SectionTabTriggerProps,
|
|
31
|
+
) => ReactNode
|
|
32
|
+
/** Content panel; mounted only while the tab is active. */
|
|
33
|
+
readonly panel?: ReactNode
|
|
34
|
+
/** Panel wrapper classes, replacing the default `mt-4`. */
|
|
35
|
+
readonly panelClassName?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type SectionTabsProps<T extends string> = {
|
|
39
|
+
readonly value: T
|
|
40
|
+
readonly items: readonly SectionTabItem<T>[]
|
|
41
|
+
readonly onChange?: (value: T) => void
|
|
42
|
+
readonly className?: string
|
|
43
|
+
readonly panelClassName?: string
|
|
44
|
+
/** Overrides for the tab bar itself (e.g. an indented side-panel bar). */
|
|
45
|
+
readonly listClassName?: string
|
|
46
|
+
/** Right-hand controls sharing the tab row (e.g. a sort toggle). */
|
|
47
|
+
readonly controls?: ReactNode
|
|
48
|
+
readonly ariaLabel?: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const TRIGGER_CLASSES =
|
|
52
|
+
"relative inline-flex shrink-0 items-center justify-center gap-1.5 whitespace-nowrap rounded-none border-0 bg-transparent px-0.5 pt-1 pb-3 text-xs font-semibold uppercase tracking-[0.1em] text-muted-foreground transition-colors hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 after:absolute after:inset-x-0 after:-bottom-[2px] after:h-0.5 after:bg-foreground after:opacity-0 after:transition-opacity"
|
|
53
|
+
|
|
54
|
+
function triggerClassName(active: boolean): string {
|
|
55
|
+
return cn(TRIGGER_CLASSES, active && "text-foreground after:opacity-100")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Section-mode tab bar: uppercase 12px labels, a 2px underline on the
|
|
60
|
+
* active tab over a 2px strong bottom edge, and a hidden horizontal
|
|
61
|
+
* scrollbar on overflow. Standalone replacement for the shadcn tabs'
|
|
62
|
+
* `section` variant — the group-data variant chains it leaned on were
|
|
63
|
+
* too fragile to extend.
|
|
64
|
+
*/
|
|
65
|
+
export function SectionTabs<T extends string>(props: SectionTabsProps<T>) {
|
|
66
|
+
const {
|
|
67
|
+
value,
|
|
68
|
+
items,
|
|
69
|
+
onChange,
|
|
70
|
+
className,
|
|
71
|
+
panelClassName,
|
|
72
|
+
listClassName,
|
|
73
|
+
controls,
|
|
74
|
+
ariaLabel,
|
|
75
|
+
} = props
|
|
76
|
+
const baseId = useId()
|
|
77
|
+
const listRef = useRef<HTMLDivElement>(null)
|
|
78
|
+
|
|
79
|
+
const activeIndex = items.findIndex((item) => item.value === value)
|
|
80
|
+
const activeItem = activeIndex >= 0 ? items[activeIndex] : undefined
|
|
81
|
+
|
|
82
|
+
function activate(next: T): void {
|
|
83
|
+
if (next !== value) onChange?.(next)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function focusTriggerAt(index: number): void {
|
|
87
|
+
const trigger =
|
|
88
|
+
listRef.current?.querySelectorAll<HTMLElement>('[role="tab"]')[index]
|
|
89
|
+
trigger?.focus()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function handleKeyDown(event: KeyboardEvent<HTMLElement>): void {
|
|
93
|
+
const from = activeIndex < 0 ? 0 : activeIndex
|
|
94
|
+
const count = items.length
|
|
95
|
+
let nextIndex: number
|
|
96
|
+
switch (event.key) {
|
|
97
|
+
case "ArrowRight":
|
|
98
|
+
nextIndex = (from + 1) % count
|
|
99
|
+
break
|
|
100
|
+
case "ArrowLeft":
|
|
101
|
+
nextIndex = (from - 1 + count) % count
|
|
102
|
+
break
|
|
103
|
+
case "Home":
|
|
104
|
+
nextIndex = 0
|
|
105
|
+
break
|
|
106
|
+
case "End":
|
|
107
|
+
nextIndex = count - 1
|
|
108
|
+
break
|
|
109
|
+
default:
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
event.preventDefault()
|
|
113
|
+
const next = items[nextIndex]
|
|
114
|
+
if (next === undefined) return
|
|
115
|
+
activate(next.value)
|
|
116
|
+
focusTriggerAt(nextIndex)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<div className={cn("flex min-w-0 flex-col gap-2", className)}>
|
|
121
|
+
<div className="flex items-center gap-6">
|
|
122
|
+
<div
|
|
123
|
+
ref={listRef}
|
|
124
|
+
role="tablist"
|
|
125
|
+
aria-label={ariaLabel}
|
|
126
|
+
className={cn(
|
|
127
|
+
// The 2px strong edge is an inset shadow over the bottom
|
|
128
|
+
// padding zone: the active underline (positioned 2px
|
|
129
|
+
// below the trigger) lands inside the padding box —
|
|
130
|
+
// a border would be outside it and get clipped by the
|
|
131
|
+
// scroll list's overflow-y-hidden. The color reads the
|
|
132
|
+
// runtime palette variable directly (what border-border
|
|
133
|
+
// compiles to), not the @theme mirror — the mirror
|
|
134
|
+
// lives in a cascade layer and is not reliable here.
|
|
135
|
+
"flex min-w-0 flex-1 items-center gap-6 overflow-x-auto overflow-y-hidden no-scrollbar pb-0.5 shadow-[inset_0_-2px_0_0_var(--border)]",
|
|
136
|
+
listClassName,
|
|
137
|
+
)}
|
|
138
|
+
>
|
|
139
|
+
{items.map((item, index) => {
|
|
140
|
+
const active = item.value === value
|
|
141
|
+
const triggerProps: SectionTabTriggerProps = {
|
|
142
|
+
id: `${baseId}-tab-${index}`,
|
|
143
|
+
role: "tab",
|
|
144
|
+
tabIndex: active ? 0 : -1,
|
|
145
|
+
"aria-selected": active,
|
|
146
|
+
"aria-controls":
|
|
147
|
+
item.panel !== undefined
|
|
148
|
+
? `${baseId}-panel-${index}`
|
|
149
|
+
: undefined,
|
|
150
|
+
"data-testid": item.testId,
|
|
151
|
+
onKeyDown: handleKeyDown,
|
|
152
|
+
}
|
|
153
|
+
if (item.render !== undefined) {
|
|
154
|
+
return (
|
|
155
|
+
<Fragment key={item.value}>
|
|
156
|
+
{item.render(active, triggerClassName(active), triggerProps)}
|
|
157
|
+
</Fragment>
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
return (
|
|
161
|
+
<button
|
|
162
|
+
key={item.value}
|
|
163
|
+
type="button"
|
|
164
|
+
{...triggerProps}
|
|
165
|
+
onClick={() => activate(item.value)}
|
|
166
|
+
className={triggerClassName(active)}
|
|
167
|
+
>
|
|
168
|
+
{item.label}
|
|
169
|
+
</button>
|
|
170
|
+
)
|
|
171
|
+
})}
|
|
172
|
+
</div>
|
|
173
|
+
{controls}
|
|
174
|
+
</div>
|
|
175
|
+
{activeItem?.panel !== undefined ? (
|
|
176
|
+
<div
|
|
177
|
+
role="tabpanel"
|
|
178
|
+
id={`${baseId}-panel-${activeIndex}`}
|
|
179
|
+
aria-labelledby={`${baseId}-tab-${activeIndex}`}
|
|
180
|
+
className={cn(
|
|
181
|
+
"flex-1 text-sm outline-none",
|
|
182
|
+
activeItem.panelClassName ?? panelClassName ?? "mt-4",
|
|
183
|
+
)}
|
|
184
|
+
>
|
|
185
|
+
{activeItem.panel}
|
|
186
|
+
</div>
|
|
187
|
+
) : null}
|
|
188
|
+
</div>
|
|
189
|
+
)
|
|
190
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Separator as SeparatorPrimitive } from "@base-ui/react/separator"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@hoardodile/ui/lib/utils"
|
|
4
|
+
|
|
5
|
+
export type SeparatorSize = "hairline" | "seam"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Horizontal/vertical divider: hairlines are 1px and quiet; the 2px
|
|
9
|
+
* `seam` tier marks only structural seams (tab bars, panel sections,
|
|
10
|
+
* pinned action bars).
|
|
11
|
+
*/
|
|
12
|
+
function Separator({
|
|
13
|
+
className,
|
|
14
|
+
orientation = "horizontal",
|
|
15
|
+
size = "hairline",
|
|
16
|
+
...props
|
|
17
|
+
}: SeparatorPrimitive.Props & {
|
|
18
|
+
readonly size?: SeparatorSize
|
|
19
|
+
}) {
|
|
20
|
+
const thickness =
|
|
21
|
+
size === "seam"
|
|
22
|
+
? "data-horizontal:h-0.5 data-vertical:w-0.5"
|
|
23
|
+
: "data-horizontal:h-px data-vertical:w-px"
|
|
24
|
+
return (
|
|
25
|
+
<SeparatorPrimitive
|
|
26
|
+
data-slot="separator"
|
|
27
|
+
orientation={orientation}
|
|
28
|
+
className={cn(
|
|
29
|
+
"shrink-0 bg-border data-horizontal:w-full data-vertical:self-stretch",
|
|
30
|
+
thickness,
|
|
31
|
+
className
|
|
32
|
+
)}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { Separator }
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Dialog as SheetPrimitive } from "@base-ui/react/dialog"
|
|
3
|
+
import { useTranslation } from "react-i18next"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@hoardodile/ui/lib/utils"
|
|
6
|
+
import { Button } from "@hoardodile/ui/components/button"
|
|
7
|
+
import { useMobileBackToClose } from "@hoardodile/ui/hooks/useMobileBackToClose"
|
|
8
|
+
import { Cross } from "@hoardodile/ui/icons/marks"
|
|
9
|
+
|
|
10
|
+
function Sheet({
|
|
11
|
+
open,
|
|
12
|
+
defaultOpen,
|
|
13
|
+
onOpenChange,
|
|
14
|
+
...props
|
|
15
|
+
}: Omit<SheetPrimitive.Root.Props, "onOpenChange"> & {
|
|
16
|
+
/**
|
|
17
|
+
* `eventDetails` is absent for closes triggered by the mobile back
|
|
18
|
+
* gesture (a synthetic close with no underlying Base UI event).
|
|
19
|
+
*/
|
|
20
|
+
onOpenChange?: (
|
|
21
|
+
open: boolean,
|
|
22
|
+
eventDetails?: SheetPrimitive.Root.ChangeEventDetails,
|
|
23
|
+
) => void
|
|
24
|
+
}) {
|
|
25
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(
|
|
26
|
+
defaultOpen ?? false,
|
|
27
|
+
)
|
|
28
|
+
const isControlled = open !== undefined
|
|
29
|
+
const currentOpen = isControlled ? open : uncontrolledOpen
|
|
30
|
+
function handleOpenChange(
|
|
31
|
+
next: boolean,
|
|
32
|
+
eventDetails?: SheetPrimitive.Root.ChangeEventDetails,
|
|
33
|
+
) {
|
|
34
|
+
if (!isControlled) setUncontrolledOpen(next)
|
|
35
|
+
onOpenChange?.(next, eventDetails)
|
|
36
|
+
}
|
|
37
|
+
useMobileBackToClose(currentOpen, handleOpenChange)
|
|
38
|
+
return (
|
|
39
|
+
<SheetPrimitive.Root
|
|
40
|
+
data-slot="sheet"
|
|
41
|
+
open={currentOpen}
|
|
42
|
+
onOpenChange={handleOpenChange}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {
|
|
49
|
+
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function SheetClose({ ...props }: SheetPrimitive.Close.Props) {
|
|
53
|
+
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {
|
|
57
|
+
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
|
|
61
|
+
return (
|
|
62
|
+
<SheetPrimitive.Backdrop
|
|
63
|
+
data-slot="sheet-overlay"
|
|
64
|
+
className={cn(
|
|
65
|
+
"fixed inset-0 z-50 bg-black/10 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs",
|
|
66
|
+
className
|
|
67
|
+
)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function SheetContent({
|
|
74
|
+
className,
|
|
75
|
+
children,
|
|
76
|
+
side = "right",
|
|
77
|
+
showCloseButton = true,
|
|
78
|
+
...props
|
|
79
|
+
}: SheetPrimitive.Popup.Props & {
|
|
80
|
+
side?: "top" | "right" | "bottom" | "left"
|
|
81
|
+
showCloseButton?: boolean
|
|
82
|
+
}) {
|
|
83
|
+
const { t } = useTranslation("ui", { useSuspense: false })
|
|
84
|
+
return (
|
|
85
|
+
<SheetPortal>
|
|
86
|
+
<SheetOverlay />
|
|
87
|
+
<SheetPrimitive.Popup
|
|
88
|
+
data-slot="sheet-content"
|
|
89
|
+
data-side={side}
|
|
90
|
+
className={cn(
|
|
91
|
+
"fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-sm text-popover-foreground shadow-lg transition duration-200 ease-in-out data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm",
|
|
92
|
+
className
|
|
93
|
+
)}
|
|
94
|
+
{...props}
|
|
95
|
+
>
|
|
96
|
+
{children}
|
|
97
|
+
{showCloseButton && (
|
|
98
|
+
<SheetPrimitive.Close
|
|
99
|
+
data-slot="sheet-close"
|
|
100
|
+
render={
|
|
101
|
+
<Button
|
|
102
|
+
variant="ghost"
|
|
103
|
+
className="absolute top-4 right-4"
|
|
104
|
+
size="icon-sm"
|
|
105
|
+
/>
|
|
106
|
+
}
|
|
107
|
+
>
|
|
108
|
+
<Cross
|
|
109
|
+
/>
|
|
110
|
+
<span className="sr-only">{t("aria.close")}</span>
|
|
111
|
+
</SheetPrimitive.Close>
|
|
112
|
+
)}
|
|
113
|
+
</SheetPrimitive.Popup>
|
|
114
|
+
</SheetPortal>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
119
|
+
return (
|
|
120
|
+
<div
|
|
121
|
+
data-slot="sheet-header"
|
|
122
|
+
className={cn("flex flex-col gap-1.5 p-4", className)}
|
|
123
|
+
{...props}
|
|
124
|
+
/>
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
129
|
+
return (
|
|
130
|
+
<div
|
|
131
|
+
data-slot="sheet-footer"
|
|
132
|
+
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
|
133
|
+
{...props}
|
|
134
|
+
/>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
|
|
139
|
+
return (
|
|
140
|
+
<SheetPrimitive.Title
|
|
141
|
+
data-slot="sheet-title"
|
|
142
|
+
className={cn("font-heading font-medium text-foreground", className)}
|
|
143
|
+
{...props}
|
|
144
|
+
/>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function SheetDescription({
|
|
149
|
+
className,
|
|
150
|
+
...props
|
|
151
|
+
}: SheetPrimitive.Description.Props) {
|
|
152
|
+
return (
|
|
153
|
+
<SheetPrimitive.Description
|
|
154
|
+
data-slot="sheet-description"
|
|
155
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
156
|
+
{...props}
|
|
157
|
+
/>
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export {
|
|
162
|
+
Sheet,
|
|
163
|
+
SheetTrigger,
|
|
164
|
+
SheetClose,
|
|
165
|
+
SheetContent,
|
|
166
|
+
SheetHeader,
|
|
167
|
+
SheetFooter,
|
|
168
|
+
SheetTitle,
|
|
169
|
+
SheetDescription,
|
|
170
|
+
}
|