@godxjp/ui 19.6.0 → 20.1.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 +8 -2
- package/dist/app/theme-axes.d.ts +2 -0
- package/dist/app/theme-axes.js +45 -0
- package/dist/components/charts/chart-cartesian.d.ts +2 -1
- package/dist/components/charts/chart-cartesian.js +52 -3
- package/dist/components/charts/chart-category-axis.d.ts +55 -0
- package/dist/components/charts/chart-category-axis.js +93 -0
- package/dist/components/charts/chart-frame.d.ts +10 -1
- package/dist/components/charts/chart-frame.js +19 -3
- package/dist/components/charts/compact-bar-trend.d.ts +1 -1
- package/dist/components/charts/compact-bar-trend.js +2 -0
- package/dist/components/charts/pie-chart.d.ts +1 -1
- package/dist/components/charts/pie-chart.js +12 -1
- package/dist/components/charts/recharts-peer.d.ts +49 -0
- package/dist/components/charts/recharts-peer.js +45 -0
- package/dist/components/data-display/card.d.ts +14 -3
- package/dist/components/data-display/card.js +22 -4
- package/dist/components/data-display/code-block.js +6 -2
- package/dist/components/data-display/collapsible.d.ts +23 -4
- package/dist/components/data-display/collapsible.js +119 -4
- package/dist/components/data-display/data-table.d.ts +39 -4
- package/dist/components/data-display/data-table.js +802 -270
- package/dist/components/data-display/descriptions.d.ts +27 -6
- package/dist/components/data-display/descriptions.js +58 -17
- package/dist/components/data-display/index.d.ts +5 -1
- package/dist/components/data-display/index.js +4 -0
- package/dist/components/data-display/legend.d.ts +15 -0
- package/dist/components/data-display/legend.js +11 -0
- package/dist/components/data-display/list-row.d.ts +10 -1
- package/dist/components/data-display/list-row.js +8 -2
- package/dist/components/data-display/popover.d.ts +60 -7
- package/dist/components/data-display/popover.js +268 -39
- package/dist/components/data-display/progress.d.ts +50 -2
- package/dist/components/data-display/progress.js +57 -9
- package/dist/components/data-display/range-timeline.d.ts +38 -0
- package/dist/components/data-display/range-timeline.js +161 -0
- package/dist/components/data-display/scroll-area.js +13 -2
- package/dist/components/data-display/service-launcher-card.js +10 -10
- package/dist/components/data-display/table.d.ts +11 -3
- package/dist/components/data-display/table.js +48 -18
- package/dist/components/data-display/tree-list.js +4 -9
- package/dist/components/data-entry/calendar.d.ts +2 -2
- package/dist/components/data-entry/calendar.js +79 -33
- package/dist/components/data-entry/cascader.d.ts +1 -1
- package/dist/components/data-entry/cascader.js +188 -24
- package/dist/components/data-entry/checkbox.d.ts +19 -6
- package/dist/components/data-entry/checkbox.js +55 -16
- package/dist/components/data-entry/choice-option.d.ts +1 -1
- package/dist/components/data-entry/color-picker.d.ts +1 -1
- package/dist/components/data-entry/color-picker.js +17 -5
- package/dist/components/data-entry/control-appearance.d.ts +64 -0
- package/dist/components/data-entry/control-appearance.js +39 -0
- package/dist/components/data-entry/control-surface.d.ts +61 -0
- package/dist/components/data-entry/control-surface.js +39 -0
- package/dist/components/data-entry/date-picker.d.ts +1 -1
- package/dist/components/data-entry/date-picker.js +344 -113
- package/dist/components/data-entry/date-range-picker.d.ts +2 -2
- package/dist/components/data-entry/date-range-picker.js +278 -140
- package/dist/components/data-entry/field.js +0 -1
- package/dist/components/data-entry/form-field.d.ts +1 -1
- package/dist/components/data-entry/form-field.js +39 -4
- package/dist/components/data-entry/form.d.ts +4 -0
- package/dist/components/data-entry/form.js +5 -3
- package/dist/components/data-entry/index.d.ts +7 -3
- package/dist/components/data-entry/index.js +10 -1
- package/dist/components/data-entry/input-otp.d.ts +1 -1
- package/dist/components/data-entry/input.d.ts +12 -34
- package/dist/components/data-entry/input.js +92 -24
- package/dist/components/data-entry/label.d.ts +3 -2
- package/dist/components/data-entry/label.js +23 -10
- package/dist/components/data-entry/month-picker.d.ts +2 -2
- package/dist/components/data-entry/month-picker.js +47 -10
- package/dist/components/data-entry/month-range-picker.d.ts +2 -2
- package/dist/components/data-entry/month-range-picker.js +51 -11
- package/dist/components/data-entry/number-input.d.ts +7 -0
- package/dist/components/data-entry/number-input.js +147 -98
- package/dist/components/data-entry/password-input.d.ts +1 -1
- package/dist/components/data-entry/radio.d.ts +1 -1
- package/dist/components/data-entry/radio.js +61 -13
- package/dist/components/data-entry/search-input.d.ts +1 -1
- package/dist/components/data-entry/search-input.js +16 -2
- package/dist/components/data-entry/search-select.d.ts +2 -2
- package/dist/components/data-entry/search-select.js +209 -78
- package/dist/components/data-entry/select.d.ts +22 -4
- package/dist/components/data-entry/select.js +215 -163
- package/dist/components/data-entry/slider.d.ts +9 -1
- package/dist/components/data-entry/slider.js +87 -9
- package/dist/components/data-entry/switch.d.ts +3 -0
- package/dist/components/data-entry/switch.js +29 -3
- package/dist/components/data-entry/textarea.d.ts +14 -56
- package/dist/components/data-entry/textarea.js +80 -33
- package/dist/components/data-entry/time-picker.d.ts +2 -2
- package/dist/components/data-entry/time-picker.js +333 -109
- package/dist/components/data-entry/time-range-picker.d.ts +5 -0
- package/dist/components/data-entry/time-range-picker.js +89 -0
- package/dist/components/data-entry/transfer.d.ts +1 -1
- package/dist/components/data-entry/transfer.js +99 -31
- package/dist/components/data-entry/tree-select-strategy.d.ts +1 -1
- package/dist/components/data-entry/tree-select.d.ts +1 -1
- package/dist/components/data-entry/tree-select.js +197 -92
- package/dist/components/data-entry/tree-utils.d.ts +1 -1
- package/dist/components/data-entry/tree-utils.js +7 -14
- package/dist/components/data-entry/upload-files.d.ts +2 -0
- package/dist/components/data-entry/upload-files.js +31 -0
- package/dist/components/data-entry/upload-request.d.ts +4 -0
- package/dist/components/data-entry/upload-request.js +53 -0
- package/dist/components/data-entry/upload-types.d.ts +28 -0
- package/dist/components/data-entry/upload-types.js +2 -0
- package/dist/components/data-entry/upload.d.ts +2 -2
- package/dist/components/data-entry/upload.js +482 -117
- package/dist/components/feedback/dialog.d.ts +105 -38
- package/dist/components/feedback/dialog.js +268 -193
- package/dist/components/feedback/overlay-close-focus.d.ts +31 -0
- package/dist/components/feedback/overlay-close-focus.js +31 -0
- package/dist/components/feedback/overlay-header-tone.d.ts +1 -1
- package/dist/components/feedback/sheet.d.ts +53 -11
- package/dist/components/feedback/sheet.js +150 -81
- package/dist/components/feedback/tooltip.d.ts +51 -7
- package/dist/components/feedback/tooltip.js +107 -25
- package/dist/components/general/button.d.ts +1 -1
- package/dist/components/general/button.js +7 -3
- package/dist/components/general/index.d.ts +1 -0
- package/dist/components/general/index.js +2 -0
- package/dist/components/general/logo.d.ts +17 -0
- package/dist/components/general/logo.js +22 -16
- package/dist/components/general/typography.d.ts +3 -0
- package/dist/components/general/typography.js +15 -1
- package/dist/components/general/visually-hidden.d.ts +3 -0
- package/dist/components/general/visually-hidden.js +9 -0
- package/dist/components/layout/app-launcher.d.ts +34 -0
- package/dist/components/layout/app-launcher.js +228 -0
- package/dist/components/layout/app-shell.d.ts +3 -1
- package/dist/components/layout/app-shell.js +69 -12
- package/dist/components/layout/aspect-ratio.js +0 -1
- package/dist/components/layout/auth-divider.js +0 -1
- package/dist/components/layout/breadcrumb.d.ts +14 -2
- package/dist/components/layout/breadcrumb.js +46 -4
- package/dist/components/layout/flex.d.ts +1 -1
- package/dist/components/layout/flex.js +41 -3
- package/dist/components/layout/index.d.ts +3 -0
- package/dist/components/layout/index.js +5 -1
- package/dist/components/layout/nav-surface.d.ts +26 -0
- package/dist/components/layout/nav-surface.js +17 -0
- package/dist/components/layout/org-switcher.d.ts +5 -1
- package/dist/components/layout/org-switcher.js +25 -4
- package/dist/components/layout/page-container.d.ts +1 -1
- package/dist/components/layout/page-container.js +5 -3
- package/dist/components/layout/responsive-grid.d.ts +16 -2
- package/dist/components/layout/responsive-grid.js +29 -2
- package/dist/components/layout/separator.js +0 -1
- package/dist/components/layout/sidebar.js +4 -1
- package/dist/components/layout/split-pane.d.ts +14 -1
- package/dist/components/layout/split-pane.js +26 -13
- package/dist/components/layout/topbar-item.d.ts +3 -0
- package/dist/components/layout/topbar-item.js +19 -5
- package/dist/components/layout/topbar.d.ts +1 -1
- package/dist/components/layout/topbar.js +27 -6
- package/dist/components/navigation/app-setting-picker.js +21 -2
- package/dist/components/navigation/app-setting-toggle.d.ts +16 -0
- package/dist/components/navigation/app-setting-toggle.js +96 -0
- package/dist/components/navigation/dropdown-menu.d.ts +227 -18
- package/dist/components/navigation/dropdown-menu.js +402 -118
- package/dist/components/navigation/index.d.ts +2 -0
- package/dist/components/navigation/index.js +2 -0
- package/dist/components/navigation/pagination-utils.d.ts +2 -1
- package/dist/components/navigation/pagination.d.ts +2 -2
- package/dist/components/navigation/pagination.js +155 -83
- package/dist/components/navigation/steps.d.ts +2 -2
- package/dist/components/navigation/steps.js +29 -4
- package/dist/components/navigation/tabs.d.ts +29 -16
- package/dist/components/navigation/tabs.js +374 -144
- package/dist/components/ui/accordion.d.ts +50 -5
- package/dist/components/ui/accordion.js +239 -33
- package/dist/components/ui/aspect-ratio.d.ts +23 -2
- package/dist/components/ui/aspect-ratio.js +15 -13
- package/dist/components/ui/avatar.d.ts +29 -4
- package/dist/components/ui/avatar.js +111 -25
- package/dist/components/ui/hover-card.d.ts +42 -4
- package/dist/components/ui/hover-card.js +183 -27
- package/dist/components/ui/input-otp.d.ts +26 -28
- package/dist/components/ui/input-otp.js +50 -7
- package/dist/components/ui/label.js +0 -1
- package/dist/components/ui/password-input.d.ts +36 -2
- package/dist/components/ui/password-input.js +27 -7
- package/dist/components/ui/rating.d.ts +25 -0
- package/dist/components/ui/rating.js +67 -27
- package/dist/components/ui/segmented.d.ts +12 -3
- package/dist/components/ui/segmented.js +17 -2
- package/dist/components/ui/separator.d.ts +12 -2
- package/dist/components/ui/separator.js +30 -9
- package/dist/components/ui/tag-input.d.ts +45 -0
- package/dist/components/ui/tag-input.js +109 -27
- package/dist/components/ui/toggle-group.d.ts +50 -5
- package/dist/components/ui/toggle-group.js +79 -20
- package/dist/components/ui/toggle.d.ts +31 -5
- package/dist/components/ui/toggle.js +42 -3
- package/dist/form/form-context.d.ts +29 -0
- package/dist/form/form-context.js +44 -4
- package/dist/form/form-field-array.d.ts +22 -0
- package/dist/form/form-field-array.js +50 -0
- package/dist/form/form-field-control.d.ts +2 -1
- package/dist/form/form-field-control.js +116 -40
- package/dist/form/form-root.d.ts +2 -1
- package/dist/form/form-root.js +142 -23
- package/dist/form/index.d.ts +3 -1
- package/dist/form/index.js +12 -1
- package/dist/i18n/messages/en.json +64 -5
- package/dist/i18n/messages/ja.json +64 -5
- package/dist/i18n/messages/vi.json +64 -5
- package/dist/inertia/index.d.ts +20 -0
- package/dist/inertia/index.js +60 -1
- package/dist/lib/control-styles.d.ts +25 -3
- package/dist/lib/control-styles.js +9 -3
- package/dist/lib/datetime/picker-format.d.ts +8 -0
- package/dist/lib/datetime/picker-format.js +54 -0
- package/dist/lib/slot.d.ts +32 -0
- package/dist/lib/slot.js +22 -0
- package/dist/lib/variants.d.ts +22 -3
- package/dist/lib/variants.js +56 -1
- package/dist/props/components/app.prop.d.ts +25 -1
- package/dist/props/components/charts.prop.d.ts +21 -0
- package/dist/props/components/data-display.prop.d.ts +72 -4
- package/dist/props/components/data-entry.prop.d.ts +718 -46
- package/dist/props/components/form.prop.d.ts +142 -4
- package/dist/props/components/general.prop.d.ts +17 -1
- package/dist/props/components/index.d.ts +3 -3
- package/dist/props/components/layout.prop.d.ts +281 -11
- package/dist/props/components/navigation.prop.d.ts +114 -4
- package/dist/props/registry.d.ts +397 -11
- package/dist/props/registry.js +526 -11
- package/dist/props/vocabulary/content.prop.d.ts +1 -1
- package/dist/props/vocabulary/data.prop.d.ts +179 -1
- package/dist/props/vocabulary/index.d.ts +5 -5
- package/dist/props/vocabulary/interaction.prop.d.ts +68 -3
- package/dist/props/vocabulary/layout.prop.d.ts +57 -1
- package/dist/props/vocabulary/navigation.prop.d.ts +40 -0
- package/dist/props/vocabulary/shared.prop.d.ts +33 -0
- package/dist/styles/badge-layout.css +2 -1
- package/dist/styles/card-layout.css +26 -4
- package/dist/styles/chart-layout.css +15 -0
- package/dist/styles/control.css +657 -15
- package/dist/styles/core.css +5 -2
- package/dist/styles/data-display-layout.css +313 -6
- package/dist/styles/data-entry-layout.css +13 -0
- package/dist/styles/focus-ring.css +4 -0
- package/dist/styles/index.css +5 -2
- package/dist/styles/layout.css +327 -4
- package/dist/styles/navigation-layout.css +227 -0
- package/dist/styles/shell-layout.css +369 -13
- package/dist/styles/table-layout.css +95 -6
- package/dist/styles/text-layout.css +10 -4
- package/dist/tokens/base.css +2 -1
- package/dist/tokens/components/badge.css +1 -0
- package/dist/tokens/components/chart.css +6 -0
- package/dist/tokens/components/control.css +79 -2
- package/dist/tokens/components/data-display.css +20 -0
- package/dist/tokens/components/descriptions.css +11 -0
- package/dist/tokens/components/flex.css +8 -0
- package/dist/tokens/components/navigation.css +55 -0
- package/dist/tokens/components/shell.css +53 -3
- package/dist/tokens/components/table.css +14 -0
- package/dist/tokens/foundation.css +2 -0
- package/dist/tokens/semantic/layout.css +9 -0
- package/docs/COMPONENTS.md +9 -3
- package/docs/CONSUMER-RULES.md +13 -0
- package/docs/DESIGN-AUTHORITY.md +145 -83
- package/docs/DEVELOPMENT.md +4 -3
- package/docs/FORMS.md +151 -6
- package/docs/FRAME-COVERAGE-REPORT.md +32 -17
- package/docs/README.md +15 -15
- package/docs/STANDARDS-vocabulary-tokens.md +1 -1
- package/docs/TESTING.md +15 -6
- package/docs/WHAT-BELONGS-HERE.md +179 -0
- package/docs/charts/cjk-category-axis.tsx +81 -0
- package/docs/data-display/card/index.tsx +22 -0
- package/docs/data-display/data-table/examples/antd-parity.tsx +257 -0
- package/docs/data-display/data-table/index.tsx +23 -0
- package/docs/data-display/descriptions.tsx +41 -0
- package/docs/data-display/legend.tsx +145 -0
- package/docs/data-display/progress.tsx +32 -0
- package/docs/data-display/scroll-area.tsx +31 -25
- package/docs/data-display/table.tsx +104 -45
- package/docs/data-display/timeline.tsx +41 -0
- package/docs/data-entry/date-picker.tsx +85 -0
- package/docs/data-entry/date-range-picker.tsx +26 -0
- package/docs/data-entry/form-dynamic-fields.tsx +199 -0
- package/docs/data-entry/form.tsx +74 -4
- package/docs/data-entry/input-otp.tsx +24 -0
- package/docs/data-entry/input.tsx +46 -1
- package/docs/data-entry/month-range-picker.tsx +1 -1
- package/docs/data-entry/segmented.tsx +1 -1
- package/docs/data-entry/select.tsx +29 -2
- package/docs/data-entry/switch.tsx +41 -0
- package/docs/data-entry/textarea.tsx +38 -0
- package/docs/data-entry/time-picker.tsx +85 -1
- package/docs/data-entry/time-range-picker.tsx +55 -0
- package/docs/data-entry/transfer.tsx +17 -0
- package/docs/data-entry/upload.tsx +56 -12
- package/docs/feedback/sheet.tsx +1 -1
- package/docs/feedback/tooltip.tsx +1 -1
- package/docs/general/activity.tsx +2 -2
- package/docs/general/button/index.tsx +14 -1
- package/docs/general/typography.tsx +14 -1
- package/docs/layout/app-launcher.tsx +151 -0
- package/docs/layout/app-shell-arrangements.tsx +225 -0
- package/docs/layout/app-shell.tsx +11 -0
- package/docs/layout/aspect-ratio.tsx +1 -1
- package/docs/layout/flex.tsx +50 -0
- package/docs/layout/responsive-grid.tsx +21 -1
- package/docs/layout/topbar.tsx +5 -9
- package/docs/navigation/app-setting-picker.tsx +37 -1
- package/docs/navigation/app-setting-toggle.tsx +111 -0
- package/docs/navigation/breadcrumb.tsx +48 -0
- package/docs/navigation/dropdown-menu.tsx +21 -0
- package/docs/navigation/pagination.tsx +52 -0
- package/docs/navigation/steps.tsx +37 -0
- package/docs/navigation/tabs.tsx +97 -1
- package/docs/query/button-refetch.tsx +1 -0
- package/package.json +28 -8
- package/scripts/_agent-setup.mjs +182 -3
- package/scripts/consumer-rule.md +98 -0
- package/scripts/guinea-pig-skill.md +322 -0
- package/scripts/init-guinea-pig.mjs +84 -0
- package/scripts/postinstall.mjs +13 -2
- package/scripts/ui-audit.mjs +351 -37
- /package/dist/tokens/{antd.generated.css → derived.css} +0 -0
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import
|
|
4
|
+
import {
|
|
5
|
+
Tab as AriaTab,
|
|
6
|
+
TabList as AriaTabList,
|
|
7
|
+
TabPanel as AriaTabPanel,
|
|
8
|
+
Tabs as AriaTabs
|
|
9
|
+
} from "react-aria-components";
|
|
10
|
+
import { Plus, X } from "lucide-react";
|
|
11
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
5
12
|
import { cn } from "../../lib/utils.js";
|
|
6
13
|
import { useKeepActiveTabVisible } from "./tabs-scroll.js";
|
|
14
|
+
const TabsFrameContext = React.createContext({
|
|
15
|
+
orientation: "horizontal",
|
|
16
|
+
selectionSuppressed: false
|
|
17
|
+
});
|
|
7
18
|
function resolveFallbackTabValue(items, requested) {
|
|
8
19
|
if (!items || items.length === 0) return requested;
|
|
9
20
|
if (requested !== void 0) {
|
|
@@ -12,182 +23,401 @@ function resolveFallbackTabValue(items, requested) {
|
|
|
12
23
|
}
|
|
13
24
|
return items.find((item) => !item.disabled)?.value;
|
|
14
25
|
}
|
|
26
|
+
function withDomProps(element, slot, raw, domProps, own) {
|
|
27
|
+
const dom = domProps;
|
|
28
|
+
return React.createElement(element, {
|
|
29
|
+
"data-slot": slot,
|
|
30
|
+
...raw,
|
|
31
|
+
...dom,
|
|
32
|
+
id: raw.id ?? dom.id,
|
|
33
|
+
...own
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function resolveTabsExtra(extra) {
|
|
37
|
+
if (extra === void 0 || extra === null || extra === false) return {};
|
|
38
|
+
const isSlotMap = typeof extra === "object" && !React.isValidElement(extra) && !Array.isArray(extra) && ("start" in extra || "end" in extra);
|
|
39
|
+
if (isSlotMap) return extra;
|
|
40
|
+
return { end: extra };
|
|
41
|
+
}
|
|
42
|
+
function resolveTabsAxis(tabPlacement, orientation) {
|
|
43
|
+
if (tabPlacement) {
|
|
44
|
+
return {
|
|
45
|
+
placement: tabPlacement,
|
|
46
|
+
orientation: orientation ?? (tabPlacement === "start" || tabPlacement === "end" ? "vertical" : "horizontal")
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
placement: orientation === "vertical" ? "start" : "top",
|
|
51
|
+
orientation: orientation ?? "horizontal"
|
|
52
|
+
};
|
|
53
|
+
}
|
|
15
54
|
function Tabs({
|
|
16
55
|
className,
|
|
17
|
-
orientation
|
|
56
|
+
orientation,
|
|
18
57
|
items,
|
|
19
58
|
value,
|
|
20
59
|
defaultValue,
|
|
60
|
+
onValueChange,
|
|
61
|
+
activationMode,
|
|
62
|
+
dir,
|
|
21
63
|
variant = "default",
|
|
64
|
+
tabPlacement,
|
|
65
|
+
size = "md",
|
|
66
|
+
centered,
|
|
67
|
+
extra,
|
|
68
|
+
destroyOnHidden = true,
|
|
69
|
+
onEdit,
|
|
70
|
+
addIcon,
|
|
71
|
+
hideAdd,
|
|
22
72
|
listClassName,
|
|
23
73
|
contentClassName,
|
|
74
|
+
children,
|
|
24
75
|
...props
|
|
25
76
|
}) {
|
|
77
|
+
const { t } = useTranslation();
|
|
26
78
|
const resolvedDefault = resolveFallbackTabValue(items, defaultValue);
|
|
27
|
-
|
|
28
|
-
|
|
79
|
+
const { placement, orientation: resolvedOrientation } = resolveTabsAxis(
|
|
80
|
+
tabPlacement,
|
|
81
|
+
orientation
|
|
82
|
+
);
|
|
83
|
+
const selectionSuppressed = value === void 0 && items != null && items.length > 0 && resolvedDefault === void 0;
|
|
84
|
+
const frame = React.useMemo(
|
|
85
|
+
() => ({ orientation: resolvedOrientation, selectionSuppressed }),
|
|
86
|
+
[resolvedOrientation, selectionSuppressed]
|
|
87
|
+
);
|
|
88
|
+
const editable = variant === "editable-card";
|
|
89
|
+
const [mirroredValue, setMirroredValue] = React.useState(
|
|
90
|
+
value ?? resolvedDefault
|
|
91
|
+
);
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
if (value !== void 0) setMirroredValue(value);
|
|
94
|
+
}, [value]);
|
|
95
|
+
const activeValue = value ?? mirroredValue;
|
|
96
|
+
const handleValueChange = React.useCallback(
|
|
97
|
+
(next) => {
|
|
98
|
+
setMirroredValue(next);
|
|
99
|
+
onValueChange?.(next);
|
|
100
|
+
},
|
|
101
|
+
[onValueChange]
|
|
102
|
+
);
|
|
103
|
+
const { start: extraStart, end: extraEnd } = resolveTabsExtra(extra);
|
|
104
|
+
const showAdd = editable && !hideAdd && Boolean(onEdit);
|
|
105
|
+
const needsBar = Boolean(extraStart || extraEnd || showAdd);
|
|
106
|
+
const card = variant === "card" || variant === "editable-card";
|
|
107
|
+
const sizeTriggerClassName = cn(
|
|
108
|
+
size === "sm" && "min-h-[var(--tabs-trigger-height-sm)] px-[var(--tabs-trigger-padding-x-sm)] text-[length:var(--tabs-trigger-font-size-sm)]",
|
|
109
|
+
size === "md" && "min-h-[var(--tabs-trigger-height-md)] px-[var(--tabs-trigger-padding-x-md)] text-[length:var(--tabs-trigger-font-size-md)]",
|
|
110
|
+
size === "lg" && "min-h-[var(--tabs-trigger-height-lg)] px-[var(--tabs-trigger-padding-x-lg)] text-[length:var(--tabs-trigger-font-size-lg)]"
|
|
111
|
+
);
|
|
112
|
+
const list = items ? /* @__PURE__ */ jsx(
|
|
113
|
+
TabsList,
|
|
114
|
+
{
|
|
115
|
+
"data-slot": "tabs-list",
|
|
116
|
+
variant: variant === "line" ? "line" : "default",
|
|
117
|
+
className: cn(
|
|
118
|
+
// The inset override is an arbitrary value reading the knob (never `p-0`): it must
|
|
119
|
+
// stay a UTILITY so tailwind-merge still drops the strip's own padding step.
|
|
120
|
+
//
|
|
121
|
+
// IT IS SPLIT INTO `px-`/`py-` FOR THE FOCUS RING (gh#376). The strip clips its
|
|
122
|
+
// block axis (`overflow-y: hidden` beside `overflow-x: auto`), and this variant's
|
|
123
|
+
// inset is 0px, so a focused trigger had NO block headroom at all — measured in
|
|
124
|
+
// Chromium, 0px top and bottom, i.e. the entire ring was shaved. The headroom has to
|
|
125
|
+
// come from layout: `overflow-y: visible` next to `overflow-x: auto` computes back
|
|
126
|
+
// to `auto` per spec (a second scroll container, not a ring), and Chromium honours
|
|
127
|
+
// `overflow-clip-margin` only when BOTH axes are `clip`. So the block padding carries
|
|
128
|
+
// the ring's outer reach and an equal negative block margin hands it straight back —
|
|
129
|
+
// the ring paints inside the scrollport and the strip's OUTER box is unchanged
|
|
130
|
+
// (measured: 38px before and after). Both values read the same token the ring is
|
|
131
|
+
// sized from, so the two can never drift apart.
|
|
132
|
+
//
|
|
133
|
+
// The cost, recorded rather than hidden: the rail hairline (`border-b`, drawn at the
|
|
134
|
+
// border box) now sits the ring's reach below the triggers instead of flush. That is
|
|
135
|
+
// where a hand-composed <TabsList variant="line"> already put it — its `p-1` gives
|
|
136
|
+
// the same block inset — so the two construction paths now agree. A service that
|
|
137
|
+
// wants the active bar parked back on the hairline raises `--tabs-indicator-offset`,
|
|
138
|
+
// which exists for exactly that.
|
|
139
|
+
variant === "line" && "my-[calc(-1_*_var(--tabs-list-focus-ring-space-inset,calc(var(--focus-ring-width)_+_var(--focus-ring-glow-width))))] h-auto w-full justify-start border-b px-[var(--tabs-list-line-space-inset)] py-[calc(var(--tabs-list-line-space-inset)_+_var(--tabs-list-focus-ring-space-inset,calc(var(--focus-ring-width)_+_var(--focus-ring-glow-width))))]",
|
|
140
|
+
// CARD strip. The list keeps `data-variant="default"` on purpose (a hand-composed
|
|
141
|
+
// <TabsList> must be unaffected), so the card face is selected from the ROOT — but the
|
|
142
|
+
// three properties the base list already claims as utilities (`bg-muted`, `p-1`,
|
|
143
|
+
// `rounded-lg`) have to be replaced with utilities too, or the components layer loses.
|
|
144
|
+
card && "w-full items-end justify-start gap-[var(--tabs-card-list-space-gap)] rounded-[var(--tabs-card-list-radius)] p-[var(--tabs-card-list-space-inset)] data-[variant=default]:bg-transparent",
|
|
145
|
+
// CENTERED. Two independent moves, because the strip has two shapes: the pill/card strip
|
|
146
|
+
// is `w-fit` (auto inline margins centre the BOX) and the line strip is `w-full`
|
|
147
|
+
// (`justify-content` centres its CONTENT). `safe` keeps the overflow rule the strip
|
|
148
|
+
// already depends on — plain `center` strands the leading tab outside the scrollport.
|
|
149
|
+
centered && "mx-auto justify-center-safe",
|
|
150
|
+
listClassName
|
|
151
|
+
),
|
|
152
|
+
children: items.map((item) => {
|
|
153
|
+
const removable = editable && Boolean(onEdit) && item.closable !== false && !item.disabled;
|
|
154
|
+
return /* @__PURE__ */ jsxs(
|
|
155
|
+
TabsTrigger,
|
|
156
|
+
{
|
|
157
|
+
value: item.value,
|
|
158
|
+
disabled: item.disabled,
|
|
159
|
+
"aria-keyshortcuts": removable ? "Delete" : void 0,
|
|
160
|
+
onKeyDown: removable ? (event) => {
|
|
161
|
+
if (event.key !== "Delete" && event.key !== "Backspace") return;
|
|
162
|
+
event.preventDefault();
|
|
163
|
+
onEdit?.(item.value, "remove");
|
|
164
|
+
} : void 0,
|
|
165
|
+
className: cn(
|
|
166
|
+
// Geometry only — the active underline is the token-owned `::after` bar the
|
|
167
|
+
// line variant already owns (never a second, hand-rolled border-b indicator).
|
|
168
|
+
// They are arbitrary-value utilities so tailwind-merge keeps dropping the pill
|
|
169
|
+
// trigger's own radius / padding steps from the base class list.
|
|
170
|
+
variant === "line" && "rounded-[var(--tabs-trigger-line-radius)] px-[var(--tabs-trigger-line-padding-x)] py-[var(--tabs-trigger-line-padding-y)]",
|
|
171
|
+
sizeTriggerClassName,
|
|
172
|
+
// A centred strip must let its triggers SHRINK to their labels first. The base
|
|
173
|
+
// trigger is `flex-1`, so on a full-width (`line`) strip they already fill every
|
|
174
|
+
// pixel and `justify-content` has nothing left to centre — measured, the leading and
|
|
175
|
+
// trailing gaps were both 0px with `centered` on and off.
|
|
176
|
+
centered && "flex-none",
|
|
177
|
+
// CARD face: a real boundary on every side, rounded on the leading block edge only.
|
|
178
|
+
// The ACTIVE face's block-end edge is the SURFACE colour, never `transparent`: the
|
|
179
|
+
// rail is an inset shadow at the strip's padding-box edge, so a see-through border
|
|
180
|
+
// would let that 1px of rail run straight across the tab it is joined to.
|
|
181
|
+
card && "data-[state=active]:border-b-background rounded-[var(--tabs-card-radius)_var(--tabs-card-radius)_0_0] border-[color:hsl(var(--border))] bg-[hsl(var(--tabs-card-background,var(--muted)))]"
|
|
182
|
+
),
|
|
183
|
+
children: [
|
|
184
|
+
item.icon ? /* @__PURE__ */ jsx(
|
|
185
|
+
"span",
|
|
186
|
+
{
|
|
187
|
+
"data-slot": "tabs-trigger-icon",
|
|
188
|
+
className: "ui-tabs-trigger-icon",
|
|
189
|
+
"aria-hidden": "true",
|
|
190
|
+
children: item.icon
|
|
191
|
+
}
|
|
192
|
+
) : null,
|
|
193
|
+
item.label,
|
|
194
|
+
removable ? (
|
|
195
|
+
// A POINTER SHORTCUT, not a control — and that is measured, not stylistic. antd's own
|
|
196
|
+
// TabNode puts a real <button> beside the role="tab" element; rendered through
|
|
197
|
+
// vitest-axe that markup fails `aria-required-children`, because a tablist may own
|
|
198
|
+
// nothing but tabs and axe walks straight through the generic wrapper to find the
|
|
199
|
+
// button. Putting the button INSIDE the tab is worse still (`nested-interactive`,
|
|
200
|
+
// plus a <button> in a <button> is invalid HTML). So the × is an aria-hidden span and
|
|
201
|
+
// the real, announced route is the Delete/Backspace shortcut above — the WAI-ARIA APG
|
|
202
|
+
// "tabs with deletion" shape.
|
|
203
|
+
/* @__PURE__ */ jsx(
|
|
204
|
+
"span",
|
|
205
|
+
{
|
|
206
|
+
"data-slot": "tabs-tab-remove",
|
|
207
|
+
className: "ui-tabs-tab-remove",
|
|
208
|
+
"aria-hidden": "true",
|
|
209
|
+
title: t("navigation.tabs.removeTab"),
|
|
210
|
+
onPointerDown: (event) => {
|
|
211
|
+
event.preventDefault();
|
|
212
|
+
event.stopPropagation();
|
|
213
|
+
},
|
|
214
|
+
onMouseDown: (event) => {
|
|
215
|
+
event.preventDefault();
|
|
216
|
+
event.stopPropagation();
|
|
217
|
+
},
|
|
218
|
+
onClick: (event) => {
|
|
219
|
+
event.preventDefault();
|
|
220
|
+
event.stopPropagation();
|
|
221
|
+
onEdit?.(item.value, "remove");
|
|
222
|
+
},
|
|
223
|
+
children: item.closeIcon ?? /* @__PURE__ */ jsx(X, { className: "ui-tabs-tab-remove-icon", "aria-hidden": "true" })
|
|
224
|
+
}
|
|
225
|
+
)
|
|
226
|
+
) : null
|
|
227
|
+
]
|
|
228
|
+
},
|
|
229
|
+
item.value
|
|
230
|
+
);
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
) : null;
|
|
234
|
+
return /* @__PURE__ */ jsx(TabsFrameContext.Provider, { value: frame, children: /* @__PURE__ */ jsx(
|
|
235
|
+
AriaTabs,
|
|
29
236
|
{
|
|
30
237
|
"data-slot": "tabs",
|
|
31
|
-
"data-orientation":
|
|
238
|
+
"data-orientation": resolvedOrientation,
|
|
32
239
|
"data-variant": variant,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
240
|
+
"data-placement": placement,
|
|
241
|
+
"data-size": size,
|
|
242
|
+
"data-centered": centered ? "true" : void 0,
|
|
243
|
+
orientation: resolvedOrientation,
|
|
244
|
+
keyboardActivation: activationMode,
|
|
245
|
+
selectedKey: value,
|
|
246
|
+
defaultSelectedKey: value === void 0 ? resolvedDefault : void 0,
|
|
247
|
+
onSelectionChange: (key) => handleValueChange(String(key)),
|
|
36
248
|
className: cn(
|
|
37
|
-
// Structure only.
|
|
38
|
-
//
|
|
249
|
+
// Structure only. The paint (and the placement flip, which is `order`/`flex-direction`)
|
|
250
|
+
// lives in src/styles/navigation-layout.css so the gap reads --tabs-root-gap and a service
|
|
251
|
+
// can retune it.
|
|
39
252
|
"group/tabs flex data-[orientation=horizontal]:flex-col",
|
|
253
|
+
// PLACEMENT is a flex REVERSAL, never a re-ordered tree: the strip stays first in the DOM
|
|
254
|
+
// at every placement, so reading order and the APG tablist → tabpanel relationship do not
|
|
255
|
+
// depend on which edge the bar is painted on. Both classes carry the same modifier as the
|
|
256
|
+
// base step they replace, so tailwind-merge drops that step instead of stacking two
|
|
257
|
+
// flex-direction values on one element.
|
|
258
|
+
placement === "bottom" && "data-[orientation=horizontal]:flex-col-reverse",
|
|
259
|
+
placement === "end" && "flex-row-reverse",
|
|
40
260
|
className
|
|
41
261
|
),
|
|
42
|
-
...props,
|
|
262
|
+
render: (domProps) => withDomProps("div", "tabs", { dir, ...props }, domProps, {
|
|
263
|
+
"data-orientation": resolvedOrientation,
|
|
264
|
+
"data-variant": variant,
|
|
265
|
+
"data-placement": placement,
|
|
266
|
+
"data-size": size,
|
|
267
|
+
"data-centered": centered ? "true" : void 0
|
|
268
|
+
}),
|
|
43
269
|
children: items ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
44
|
-
/* @__PURE__ */
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// the ring's outer reach and an equal negative block margin hands it straight back —
|
|
61
|
-
// the ring paints inside the scrollport and the strip's OUTER box is unchanged
|
|
62
|
-
// (measured: 38px before and after). Both values read the same token the ring is
|
|
63
|
-
// sized from, so the two can never drift apart.
|
|
64
|
-
//
|
|
65
|
-
// The cost, recorded rather than hidden: the rail hairline (`border-b`, drawn at the
|
|
66
|
-
// border box) now sits the ring's reach below the triggers instead of flush. That is
|
|
67
|
-
// where a hand-composed <TabsList variant="line"> already put it — its `p-1` gives
|
|
68
|
-
// the same block inset — so the two construction paths now agree. A service that
|
|
69
|
-
// wants the active bar parked back on the hairline raises `--tabs-indicator-offset`,
|
|
70
|
-
// which exists for exactly that.
|
|
71
|
-
variant === "line" && "my-[calc(-1_*_var(--tabs-list-focus-ring-space-inset,calc(var(--focus-ring-width)_+_var(--focus-ring-glow-width))))] h-auto w-full justify-start border-b px-[var(--tabs-list-line-space-inset)] py-[calc(var(--tabs-list-line-space-inset)_+_var(--tabs-list-focus-ring-space-inset,calc(var(--focus-ring-width)_+_var(--focus-ring-glow-width))))]",
|
|
72
|
-
variant === "card" && "w-full justify-start",
|
|
73
|
-
listClassName
|
|
74
|
-
),
|
|
75
|
-
children: items.map((item) => /* @__PURE__ */ jsx(
|
|
76
|
-
TabsTrigger,
|
|
77
|
-
{
|
|
78
|
-
value: item.value,
|
|
79
|
-
disabled: item.disabled,
|
|
80
|
-
className: cn(
|
|
81
|
-
// Geometry only — the active underline is the token-owned `::after` bar the
|
|
82
|
-
// line variant already owns (never a second, hand-rolled border-b indicator).
|
|
83
|
-
// utilities so tailwind-merge keeps dropping the pill trigger's own radius /
|
|
84
|
-
// padding steps from the base class list.
|
|
85
|
-
// `card` adds NOTHING here — its list is forwarded as data-variant="default", so
|
|
86
|
-
// the active lift already comes from the base trigger's
|
|
87
|
-
// `group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm`.
|
|
88
|
-
variant === "line" && "rounded-[var(--tabs-trigger-line-radius)] px-[var(--tabs-trigger-line-padding-x)] py-[var(--tabs-trigger-line-padding-y)]"
|
|
89
|
-
),
|
|
90
|
-
children: item.label
|
|
91
|
-
},
|
|
92
|
-
item.value
|
|
93
|
-
))
|
|
94
|
-
}
|
|
95
|
-
),
|
|
270
|
+
needsBar ? /* @__PURE__ */ jsxs("div", { "data-slot": "tabs-bar", className: "ui-tabs-bar", children: [
|
|
271
|
+
extraStart ? /* @__PURE__ */ jsx("div", { "data-slot": "tabs-extra", "data-side": "start", className: "ui-tabs-extra", children: extraStart }) : null,
|
|
272
|
+
list,
|
|
273
|
+
showAdd ? /* @__PURE__ */ jsx(
|
|
274
|
+
"button",
|
|
275
|
+
{
|
|
276
|
+
type: "button",
|
|
277
|
+
"data-slot": "tabs-add",
|
|
278
|
+
className: "ui-tabs-add",
|
|
279
|
+
"aria-label": t("navigation.tabs.addTab"),
|
|
280
|
+
onClick: (event) => onEdit?.(event, "add"),
|
|
281
|
+
children: addIcon ?? /* @__PURE__ */ jsx(Plus, { className: "ui-tabs-add-icon", "aria-hidden": "true" })
|
|
282
|
+
}
|
|
283
|
+
) : null,
|
|
284
|
+
extraEnd ? /* @__PURE__ */ jsx("div", { "data-slot": "tabs-extra", "data-side": "end", className: "ui-tabs-extra", children: extraEnd }) : null
|
|
285
|
+
] }) : list,
|
|
96
286
|
items.map((item) => /* @__PURE__ */ jsx(
|
|
97
287
|
TabsContent,
|
|
98
288
|
{
|
|
99
289
|
value: item.value,
|
|
100
290
|
"data-slot": "tabs-panel",
|
|
291
|
+
forceMount: destroyOnHidden ? void 0 : true,
|
|
292
|
+
hidden: destroyOnHidden ? void 0 : item.value !== activeValue,
|
|
101
293
|
className: contentClassName,
|
|
102
294
|
children: item.content
|
|
103
295
|
},
|
|
104
296
|
item.value
|
|
105
297
|
))
|
|
106
|
-
] }) :
|
|
298
|
+
] }) : children
|
|
107
299
|
}
|
|
108
|
-
);
|
|
300
|
+
) });
|
|
109
301
|
}
|
|
110
|
-
const TabsList = React.forwardRef(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
{
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
302
|
+
const TabsList = React.forwardRef(
|
|
303
|
+
({ className, variant = "default", loop: _loop, children, ...props }, ref) => {
|
|
304
|
+
const listRef = React.useRef(null);
|
|
305
|
+
const setListRef = React.useCallback(
|
|
306
|
+
(node) => {
|
|
307
|
+
listRef.current = node;
|
|
308
|
+
if (typeof ref === "function") ref(node);
|
|
309
|
+
else if (ref) ref.current = node;
|
|
310
|
+
},
|
|
311
|
+
[ref]
|
|
312
|
+
);
|
|
313
|
+
useKeepActiveTabVisible(listRef);
|
|
314
|
+
return /* @__PURE__ */ jsx(
|
|
315
|
+
AriaTabList,
|
|
316
|
+
{
|
|
317
|
+
ref: setListRef,
|
|
318
|
+
className: cn(
|
|
319
|
+
// `min-w-0 max-w-full` let the list shrink to (and never exceed) whatever width its
|
|
320
|
+
// ancestors actually give it instead of forcing them wider; horizontal orientation then
|
|
321
|
+
// scrolls its own overflow rather than clipping/hiding long localized labels in a narrow
|
|
322
|
+
// Hidden scrollbar keeps the strip visually clean while staying
|
|
323
|
+
// swipeable on touch and reachable via keyboard (arrow-key roving focus still scrolls the
|
|
324
|
+
// newly-focused trigger into view natively); `useKeepActiveTabVisible` above re-pins the
|
|
325
|
+
// Vertical
|
|
326
|
+
// orientation is untouched — it already stacks in a column and is sized by its own
|
|
327
|
+
// `h-*`/`w-*` overrides.
|
|
328
|
+
// justify-center-SAFE: the strip is a centred flex box that also scrolls. Plain `center`
|
|
329
|
+
// splits the overflow across BOTH edges, and scrollLeft only ever covers the trailing one
|
|
330
|
+
// — so the leading tab sat permanently outside the scrollport (an unreachable control at
|
|
331
|
+
// 320px, WCAG 2.2 SC 2.1.1). `safe` falls back to start alignment exactly when it
|
|
332
|
+
// overflows, and still centres whenever the tabs fit.
|
|
333
|
+
"group/tabs-list text-muted-foreground data-[variant=default]:bg-muted inline-flex w-fit max-w-full min-w-0 items-center justify-center-safe rounded-lg p-1 group-data-[orientation=vertical]/tabs:flex-col data-[orientation=horizontal]:[scrollbar-width:none] data-[orientation=horizontal]:overflow-x-auto data-[orientation=horizontal]:overflow-y-hidden data-[variant=line]:gap-1 data-[variant=line]:rounded-none data-[variant=line]:bg-transparent [&[data-orientation=horizontal]::-webkit-scrollbar]:hidden",
|
|
334
|
+
className
|
|
335
|
+
),
|
|
336
|
+
render: (domProps) => withDomProps("div", "tabs-list", props, domProps, { "data-variant": variant }),
|
|
337
|
+
children
|
|
338
|
+
}
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
);
|
|
342
|
+
TabsList.displayName = "TabsList";
|
|
343
|
+
const TabsTrigger = React.forwardRef(
|
|
344
|
+
({ className, value, disabled, children, onKeyDown, ...props }, ref) => {
|
|
345
|
+
const { orientation, selectionSuppressed } = React.useContext(TabsFrameContext);
|
|
346
|
+
return /* @__PURE__ */ jsx(
|
|
347
|
+
AriaTab,
|
|
348
|
+
{
|
|
349
|
+
ref,
|
|
350
|
+
id: value,
|
|
351
|
+
isDisabled: disabled,
|
|
352
|
+
className: cn(
|
|
353
|
+
// SELECTED IS A BORDER TINT, NOT A RING — and that is a WCAG fix, not a style preference.
|
|
354
|
+
// It used to be `ring-1 ring-primary/25`, which writes `--tw-ring-shadow` in the UTILITIES
|
|
355
|
+
// layer and therefore overwrote the focus ring that focus-ring.css feeds from `components`.
|
|
356
|
+
// Measured: a focused ACTIVE tab painted `oklab(… / 0.25) 0 0 0 1px` and nothing else — a
|
|
357
|
+
// 1px indicator at 25% alpha, failing both clauses of SC 2.4.13 (2px perimeter, ≥3:1),
|
|
358
|
+
// while every other control in the library carried a full 2px ring. The trigger already
|
|
359
|
+
// owns `border border-transparent`, so tinting that border reproduces the selected hairline
|
|
360
|
+
// at the same colour and width with no layout change, and leaves `--tw-ring-shadow` free for
|
|
361
|
+
// the focus ring. `shadow-sm` stays: its composite READS `--tw-ring-shadow`, so the lift and
|
|
362
|
+
// the ring coexist.
|
|
363
|
+
//
|
|
364
|
+
// No `focus-visible:outline-1` either. It was the fallback that survived the clobber — a 1px
|
|
365
|
+
// currentColor line squeezed between the border and the ring once both paint. The ring is
|
|
366
|
+
// the indicator now; two marks for one state is what this pass exists to remove.
|
|
367
|
+
//
|
|
368
|
+
// The line indicator lives in src/styles/navigation-layout.css so it reads --tabs-indicator-*.
|
|
369
|
+
// Selected and focused stay visually distinct (WCAG 2.4.7): selected is a 1px hairline in the
|
|
370
|
+
// border, focused is the 2px ring plus its halo outside it.
|
|
371
|
+
"text-muted-foreground ring-offset-background hover:text-foreground ui-focus-ring data-[state=active]:bg-background data-[state=active]:text-foreground group-data-[variant=default]/tabs-list:data-[state=active]:border-primary/25 relative inline-flex flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-3 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start group-data-[variant=line]/tabs-list:border-e-0 group-data-[variant=line]/tabs-list:border-b-0 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none",
|
|
372
|
+
className
|
|
373
|
+
),
|
|
374
|
+
render: (domProps, renderProps) => withDomProps("button", "tabs-trigger", { type: "button", ...props }, domProps, {
|
|
375
|
+
// `data-state` is what `src/styles/navigation-layout.css` and `tabs-scroll.ts` both
|
|
376
|
+
// key on. RAC only writes `data-selected` on the selected tab, so it is re-emitted
|
|
377
|
+
// here for BOTH states, exactly as Radix did.
|
|
378
|
+
"data-state": renderProps.isSelected && !selectionSuppressed ? "active" : "inactive",
|
|
379
|
+
"aria-selected": renderProps.isSelected && !selectionSuppressed,
|
|
380
|
+
"data-orientation": orientation,
|
|
381
|
+
disabled,
|
|
382
|
+
onKeyDown: (event) => {
|
|
383
|
+
onKeyDown?.(event);
|
|
384
|
+
if (!event.defaultPrevented) {
|
|
385
|
+
domProps.onKeyDown?.(
|
|
386
|
+
event
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
tabIndex: props.tabIndex ?? domProps.tabIndex
|
|
391
|
+
}),
|
|
392
|
+
children
|
|
393
|
+
}
|
|
394
|
+
);
|
|
178
395
|
}
|
|
179
|
-
)
|
|
180
|
-
TabsTrigger.displayName =
|
|
181
|
-
const TabsContent = React.forwardRef(
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
396
|
+
);
|
|
397
|
+
TabsTrigger.displayName = "TabsTrigger";
|
|
398
|
+
const TabsContent = React.forwardRef(
|
|
399
|
+
({ className, value, forceMount, children, ...props }, ref) => {
|
|
400
|
+
const { orientation, selectionSuppressed } = React.useContext(TabsFrameContext);
|
|
401
|
+
if (selectionSuppressed && !forceMount) return null;
|
|
402
|
+
return /* @__PURE__ */ jsx(
|
|
403
|
+
AriaTabPanel,
|
|
404
|
+
{
|
|
405
|
+
ref,
|
|
406
|
+
id: value,
|
|
407
|
+
shouldForceMount: forceMount,
|
|
408
|
+
className: cn("ui-focus-ring flex-1 outline-none", className),
|
|
409
|
+
render: (domProps, renderProps) => withDomProps("div", "tabs-content", props, domProps, {
|
|
410
|
+
// A force-mounted panel whose tab is not selected: Radix marked it `data-state="inactive"`
|
|
411
|
+
// and `hidden`; RAC marks it inert. The state hook is re-emitted so CSS keys the same way.
|
|
412
|
+
"data-state": renderProps.isInert || selectionSuppressed ? "inactive" : "active",
|
|
413
|
+
"data-orientation": orientation
|
|
414
|
+
}),
|
|
415
|
+
children
|
|
416
|
+
}
|
|
417
|
+
);
|
|
188
418
|
}
|
|
189
|
-
)
|
|
190
|
-
TabsContent.displayName =
|
|
419
|
+
);
|
|
420
|
+
TabsContent.displayName = "TabsContent";
|
|
191
421
|
export {
|
|
192
422
|
Tabs,
|
|
193
423
|
TabsContent,
|
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
interface AccordionBaseProps extends Omit<React.ComponentPropsWithRef<"div">, "dir"> {
|
|
3
|
+
/** Trục xếp mục — đổi cặp phím mũi tên điều hướng. */
|
|
4
|
+
orientation?: "vertical" | "horizontal";
|
|
5
|
+
/** Chiều viết; chỉ đổi mũi tên trái/phải khi `orientation="horizontal"`. */
|
|
6
|
+
dir?: "ltr" | "rtl";
|
|
7
|
+
/** Khoá mọi mục. */
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
}
|
|
10
|
+
interface AccordionSingleProps extends AccordionBaseProps {
|
|
11
|
+
/** Chỉ một mục mở tại một thời điểm. */
|
|
12
|
+
type: "single";
|
|
13
|
+
/** Mục đang mở, có kiểm soát. */
|
|
14
|
+
value?: string;
|
|
15
|
+
/** Mục mở ban đầu khi không kiểm soát. */
|
|
16
|
+
defaultValue?: string;
|
|
17
|
+
/** Gọi khi mục đang mở đổi; chuỗi rỗng nghĩa là đã đóng hết. */
|
|
18
|
+
onValueChange?: (value: string) => void;
|
|
19
|
+
/** Cho phép đóng chính mục đang mở. Mặc định sai, như Radix. */
|
|
20
|
+
collapsible?: boolean;
|
|
21
|
+
}
|
|
22
|
+
interface AccordionMultipleProps extends AccordionBaseProps {
|
|
23
|
+
/** Nhiều mục mở độc lập. */
|
|
24
|
+
type: "multiple";
|
|
25
|
+
/** Các mục đang mở, có kiểm soát. */
|
|
26
|
+
value?: string[];
|
|
27
|
+
/** Các mục mở ban đầu khi không kiểm soát. */
|
|
28
|
+
defaultValue?: string[];
|
|
29
|
+
/** Gọi khi tập mục đang mở đổi. */
|
|
30
|
+
onValueChange?: (value: string[]) => void;
|
|
31
|
+
}
|
|
32
|
+
type AccordionProps = AccordionSingleProps | AccordionMultipleProps;
|
|
33
|
+
export declare function Accordion(accordionProps: AccordionProps): React.JSX.Element;
|
|
34
|
+
interface AccordionItemProps extends React.ComponentPropsWithRef<"div"> {
|
|
35
|
+
/** Khoá riêng mục này. */
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
/** Khoá định danh của mục — khớp với `value` của `Accordion`. */
|
|
38
|
+
value: string;
|
|
39
|
+
}
|
|
40
|
+
export declare function AccordionItem({ className, disabled, value, children, ref, ...props }: AccordionItemProps): React.JSX.Element;
|
|
41
|
+
interface AccordionTriggerProps extends React.ComponentPropsWithRef<"button"> {
|
|
42
|
+
/** Mượn thẻ của con thay vì dựng `<button>` riêng. */
|
|
43
|
+
asChild?: boolean;
|
|
44
|
+
}
|
|
45
|
+
export declare function AccordionTrigger({ asChild, className, children, onClick, ...props }: AccordionTriggerProps): React.JSX.Element;
|
|
46
|
+
interface AccordionContentProps extends React.ComponentPropsWithRef<"div"> {
|
|
47
|
+
/** Giữ nội dung trong DOM cả khi đóng. */
|
|
48
|
+
forceMount?: true;
|
|
49
|
+
}
|
|
50
|
+
export declare function AccordionContent({ className, forceMount, children, ref, ...props }: AccordionContentProps): React.JSX.Element;
|
|
51
|
+
export {};
|