@godxjp/ui 20.0.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 +12 -1
- package/dist/components/data-display/card.js +22 -4
- package/dist/components/data-display/code-block.js +6 -2
- 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 +2 -0
- package/dist/components/data-display/index.js +2 -0
- package/dist/components/data-display/list-row.d.ts +10 -1
- package/dist/components/data-display/list-row.js +7 -1
- package/dist/components/data-display/popover.d.ts +2 -2
- package/dist/components/data-display/popover.js +61 -11
- 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 +12 -1
- package/dist/components/data-entry/cascader.d.ts +1 -1
- package/dist/components/data-entry/cascader.js +188 -24
- 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 +1 -1
- package/dist/components/data-entry/date-range-picker.js +278 -140
- 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 +4 -2
- 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/month-picker.d.ts +1 -1
- package/dist/components/data-entry/month-picker.js +47 -10
- package/dist/components/data-entry/month-range-picker.d.ts +1 -1
- 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 +86 -28
- package/dist/components/data-entry/tree-select.d.ts +1 -1
- package/dist/components/data-entry/tree-select.js +201 -113
- 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 +475 -115
- package/dist/components/feedback/dialog.js +16 -19
- package/dist/components/general/button.d.ts +1 -1
- package/dist/components/general/button.js +5 -1
- package/dist/components/general/index.d.ts +1 -0
- package/dist/components/general/index.js +2 -0
- 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 +2 -0
- package/dist/components/layout/app-shell.js +20 -7
- 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 +23 -2
- package/dist/components/layout/index.d.ts +3 -0
- package/dist/components/layout/index.js +5 -1
- package/dist/components/layout/org-switcher.js +20 -1
- 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/topbar-item.d.ts +3 -0
- package/dist/components/layout/topbar-item.js +18 -3
- package/dist/components/layout/topbar.d.ts +1 -1
- package/dist/components/layout/topbar.js +27 -6
- package/dist/components/navigation/dropdown-menu.d.ts +30 -2
- package/dist/components/navigation/dropdown-menu.js +68 -11
- 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 +5 -33
- package/dist/components/navigation/tabs.js +232 -64
- package/dist/components/ui/input-otp.d.ts +26 -28
- package/dist/components/ui/input-otp.js +50 -7
- 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 +9 -0
- package/dist/components/ui/segmented.js +17 -2
- package/dist/components/ui/separator.d.ts +4 -0
- package/dist/components/ui/separator.js +13 -0
- package/dist/components/ui/tag-input.d.ts +45 -0
- package/dist/components/ui/tag-input.js +109 -27
- 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 +56 -5
- package/dist/i18n/messages/ja.json +56 -5
- package/dist/i18n/messages/vi.json +56 -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/props/components/charts.prop.d.ts +21 -0
- package/dist/props/components/data-display.prop.d.ts +55 -4
- package/dist/props/components/data-entry.prop.d.ts +687 -43
- 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 +2 -2
- package/dist/props/components/layout.prop.d.ts +163 -6
- package/dist/props/components/navigation.prop.d.ts +113 -3
- package/dist/props/registry.d.ts +376 -10
- package/dist/props/registry.js +493 -9
- package/dist/props/vocabulary/data.prop.d.ts +179 -1
- package/dist/props/vocabulary/index.d.ts +4 -4
- package/dist/props/vocabulary/interaction.prop.d.ts +51 -2
- 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 +515 -14
- package/dist/styles/data-display-layout.css +241 -6
- package/dist/styles/data-entry-layout.css +13 -0
- package/dist/styles/layout.css +221 -4
- package/dist/styles/navigation-layout.css +227 -0
- package/dist/styles/shell-layout.css +229 -5
- package/dist/styles/table-layout.css +95 -6
- package/dist/styles/text-layout.css +5 -0
- package/dist/tokens/base.css +1 -0
- 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 +9 -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 +50 -2
- package/dist/tokens/components/table.css +14 -0
- package/dist/tokens/semantic/layout.css +9 -0
- package/docs/CONSUMER-RULES.md +13 -0
- package/docs/DESIGN-AUTHORITY.md +43 -6
- package/docs/DEVELOPMENT.md +4 -3
- package/docs/FORMS.md +151 -6
- package/docs/FRAME-COVERAGE-REPORT.md +29 -16
- package/docs/README.md +1 -1
- package/docs/STANDARDS-vocabulary-tokens.md +1 -1
- package/docs/TESTING.md +15 -6
- 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/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/select.tsx +27 -0
- 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/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.tsx +11 -0
- 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 +11 -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 +19 -2
- package/scripts/_agent-setup.mjs +19 -2
- package/scripts/init-guinea-pig.mjs +26 -4
- package/scripts/ui-audit.mjs +243 -30
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
TabPanel as AriaTabPanel,
|
|
8
8
|
Tabs as AriaTabs
|
|
9
9
|
} from "react-aria-components";
|
|
10
|
+
import { Plus, X } from "lucide-react";
|
|
11
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
10
12
|
import { cn } from "../../lib/utils.js";
|
|
11
13
|
import { useKeepActiveTabVisible } from "./tabs-scroll.js";
|
|
12
14
|
const TabsFrameContext = React.createContext({
|
|
@@ -31,9 +33,27 @@ function withDomProps(element, slot, raw, domProps, own) {
|
|
|
31
33
|
...own
|
|
32
34
|
});
|
|
33
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
|
+
}
|
|
34
54
|
function Tabs({
|
|
35
55
|
className,
|
|
36
|
-
orientation
|
|
56
|
+
orientation,
|
|
37
57
|
items,
|
|
38
58
|
value,
|
|
39
59
|
defaultValue,
|
|
@@ -41,95 +61,235 @@ function Tabs({
|
|
|
41
61
|
activationMode,
|
|
42
62
|
dir,
|
|
43
63
|
variant = "default",
|
|
64
|
+
tabPlacement,
|
|
65
|
+
size = "md",
|
|
66
|
+
centered,
|
|
67
|
+
extra,
|
|
68
|
+
destroyOnHidden = true,
|
|
69
|
+
onEdit,
|
|
70
|
+
addIcon,
|
|
71
|
+
hideAdd,
|
|
44
72
|
listClassName,
|
|
45
73
|
contentClassName,
|
|
46
74
|
children,
|
|
47
75
|
...props
|
|
48
76
|
}) {
|
|
77
|
+
const { t } = useTranslation();
|
|
49
78
|
const resolvedDefault = resolveFallbackTabValue(items, defaultValue);
|
|
79
|
+
const { placement, orientation: resolvedOrientation } = resolveTabsAxis(
|
|
80
|
+
tabPlacement,
|
|
81
|
+
orientation
|
|
82
|
+
);
|
|
50
83
|
const selectionSuppressed = value === void 0 && items != null && items.length > 0 && resolvedDefault === void 0;
|
|
51
84
|
const frame = React.useMemo(
|
|
52
|
-
() => ({ orientation, selectionSuppressed }),
|
|
53
|
-
[
|
|
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]
|
|
54
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;
|
|
55
234
|
return /* @__PURE__ */ jsx(TabsFrameContext.Provider, { value: frame, children: /* @__PURE__ */ jsx(
|
|
56
235
|
AriaTabs,
|
|
57
236
|
{
|
|
58
|
-
|
|
237
|
+
"data-slot": "tabs",
|
|
238
|
+
"data-orientation": resolvedOrientation,
|
|
239
|
+
"data-variant": variant,
|
|
240
|
+
"data-placement": placement,
|
|
241
|
+
"data-size": size,
|
|
242
|
+
"data-centered": centered ? "true" : void 0,
|
|
243
|
+
orientation: resolvedOrientation,
|
|
59
244
|
keyboardActivation: activationMode,
|
|
60
245
|
selectedKey: value,
|
|
61
246
|
defaultSelectedKey: value === void 0 ? resolvedDefault : void 0,
|
|
62
|
-
onSelectionChange:
|
|
63
|
-
onValueChange(String(key));
|
|
64
|
-
} : void 0,
|
|
247
|
+
onSelectionChange: (key) => handleValueChange(String(key)),
|
|
65
248
|
className: cn(
|
|
66
|
-
// Structure only.
|
|
67
|
-
//
|
|
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.
|
|
68
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",
|
|
69
260
|
className
|
|
70
261
|
),
|
|
71
262
|
render: (domProps) => withDomProps("div", "tabs", { dir, ...props }, domProps, {
|
|
72
|
-
"data-orientation":
|
|
73
|
-
"data-variant": variant
|
|
263
|
+
"data-orientation": resolvedOrientation,
|
|
264
|
+
"data-variant": variant,
|
|
265
|
+
"data-placement": placement,
|
|
266
|
+
"data-size": size,
|
|
267
|
+
"data-centered": centered ? "true" : void 0
|
|
74
268
|
}),
|
|
75
269
|
children: items ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
76
|
-
/* @__PURE__ */
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// the ring's outer reach and an equal negative block margin hands it straight back —
|
|
93
|
-
// the ring paints inside the scrollport and the strip's OUTER box is unchanged
|
|
94
|
-
// (measured: 38px before and after). Both values read the same token the ring is
|
|
95
|
-
// sized from, so the two can never drift apart.
|
|
96
|
-
//
|
|
97
|
-
// The cost, recorded rather than hidden: the rail hairline (`border-b`, drawn at the
|
|
98
|
-
// border box) now sits the ring's reach below the triggers instead of flush. That is
|
|
99
|
-
// where a hand-composed <TabsList variant="line"> already put it — its `p-1` gives
|
|
100
|
-
// the same block inset — so the two construction paths now agree. A service that
|
|
101
|
-
// wants the active bar parked back on the hairline raises `--tabs-indicator-offset`,
|
|
102
|
-
// which exists for exactly that.
|
|
103
|
-
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))))]",
|
|
104
|
-
variant === "card" && "w-full justify-start",
|
|
105
|
-
listClassName
|
|
106
|
-
),
|
|
107
|
-
children: items.map((item) => /* @__PURE__ */ jsx(
|
|
108
|
-
TabsTrigger,
|
|
109
|
-
{
|
|
110
|
-
value: item.value,
|
|
111
|
-
disabled: item.disabled,
|
|
112
|
-
className: cn(
|
|
113
|
-
// Geometry only — the active underline is the token-owned `::after` bar the
|
|
114
|
-
// line variant already owns (never a second, hand-rolled border-b indicator).
|
|
115
|
-
// utilities so tailwind-merge keeps dropping the pill trigger's own radius /
|
|
116
|
-
// padding steps from the base class list.
|
|
117
|
-
// `card` adds NOTHING here — its list is forwarded as data-variant="default", so
|
|
118
|
-
// the active lift already comes from the base trigger's
|
|
119
|
-
// `group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm`.
|
|
120
|
-
variant === "line" && "rounded-[var(--tabs-trigger-line-radius)] px-[var(--tabs-trigger-line-padding-x)] py-[var(--tabs-trigger-line-padding-y)]"
|
|
121
|
-
),
|
|
122
|
-
children: item.label
|
|
123
|
-
},
|
|
124
|
-
item.value
|
|
125
|
-
))
|
|
126
|
-
}
|
|
127
|
-
),
|
|
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,
|
|
128
286
|
items.map((item) => /* @__PURE__ */ jsx(
|
|
129
287
|
TabsContent,
|
|
130
288
|
{
|
|
131
289
|
value: item.value,
|
|
132
290
|
"data-slot": "tabs-panel",
|
|
291
|
+
forceMount: destroyOnHidden ? void 0 : true,
|
|
292
|
+
hidden: destroyOnHidden ? void 0 : item.value !== activeValue,
|
|
133
293
|
className: contentClassName,
|
|
134
294
|
children: item.content
|
|
135
295
|
},
|
|
@@ -181,7 +341,7 @@ const TabsList = React.forwardRef(
|
|
|
181
341
|
);
|
|
182
342
|
TabsList.displayName = "TabsList";
|
|
183
343
|
const TabsTrigger = React.forwardRef(
|
|
184
|
-
({ className, value, disabled, children, ...props }, ref) => {
|
|
344
|
+
({ className, value, disabled, children, onKeyDown, ...props }, ref) => {
|
|
185
345
|
const { orientation, selectionSuppressed } = React.useContext(TabsFrameContext);
|
|
186
346
|
return /* @__PURE__ */ jsx(
|
|
187
347
|
AriaTab,
|
|
@@ -219,6 +379,14 @@ const TabsTrigger = React.forwardRef(
|
|
|
219
379
|
"aria-selected": renderProps.isSelected && !selectionSuppressed,
|
|
220
380
|
"data-orientation": orientation,
|
|
221
381
|
disabled,
|
|
382
|
+
onKeyDown: (event) => {
|
|
383
|
+
onKeyDown?.(event);
|
|
384
|
+
if (!event.defaultPrevented) {
|
|
385
|
+
domProps.onKeyDown?.(
|
|
386
|
+
event
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
},
|
|
222
390
|
tabIndex: props.tabIndex ?? domProps.tabIndex
|
|
223
391
|
}),
|
|
224
392
|
children
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import type { InputOTPAlignProp } from "../../props/components/data-entry.prop.js";
|
|
3
2
|
/**
|
|
4
3
|
* InputOTP — the code-challenge field.
|
|
5
4
|
*
|
|
@@ -8,39 +7,38 @@ import type { InputOTPAlignProp } from "../../props/components/data-entry.prop.j
|
|
|
8
7
|
* hidden input because `input-otp` owns the container element; `.ui-otp-container` reads it back
|
|
9
8
|
* through `:has()`, exactly as it already does for the invalid and disabled states. A service that
|
|
10
9
|
* wants every code field centred sets `--otp-container-align` once instead.
|
|
10
|
+
*
|
|
11
|
+
* `size`/`status`/`variant` ride the same `controlSurfaceAttrs` helper as Input, Select, Cascader
|
|
12
|
+
* and TagInput, so the code field sits on the shared control-height ladder and paints the same
|
|
13
|
+
* error edge — they reach the CSS by the same `:has()` route as `align`.
|
|
14
|
+
*
|
|
15
|
+
* The VALUE is owned here, not by `input-otp`: `formatter` and `readOnly` have to hold for paste
|
|
16
|
+
* too, and `input-otp` writes its own internal state inside its paste handler, which a wrapper
|
|
17
|
+
* that only intercepted `onChange` could not undo.
|
|
11
18
|
*/
|
|
12
|
-
export declare const InputOTP: React.ForwardRefExoticComponent<
|
|
13
|
-
value?: string;
|
|
14
|
-
onChange?: (newValue: string) => unknown;
|
|
19
|
+
export declare const InputOTP: React.ForwardRefExoticComponent<Omit<React.InputHTMLAttributes<HTMLInputElement>, "children" | "defaultValue" | "onChange" | "size" | "value"> & {
|
|
15
20
|
maxLength: number;
|
|
16
|
-
textAlign?: 'left' | 'center' | 'right';
|
|
17
|
-
onComplete?: (...args: any[]) => unknown;
|
|
18
|
-
pushPasswordManagerStrategy?: 'increase-width' | 'none';
|
|
19
|
-
pasteTransformer?: (pasted: string) => string;
|
|
20
|
-
containerClassName?: string;
|
|
21
|
-
noScriptCSSFallback?: string | null;
|
|
22
|
-
nonce?: string;
|
|
23
|
-
} & {
|
|
24
|
-
render: (props: import("input-otp").RenderProps) => React.ReactNode;
|
|
25
|
-
children?: never;
|
|
26
|
-
} & React.RefAttributes<HTMLInputElement>, "ref"> | Omit<Omit<React.InputHTMLAttributes<HTMLInputElement>, "containerClassName" | "maxLength" | "noScriptCSSFallback" | "nonce" | "onChange" | "onComplete" | "pasteTransformer" | "pushPasswordManagerStrategy" | "textAlign" | "value"> & {
|
|
27
21
|
value?: string;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
onComplete?: (
|
|
32
|
-
|
|
22
|
+
defaultValue?: string;
|
|
23
|
+
onValueChange?: (value: string) => void;
|
|
24
|
+
onChange?: (value: string) => void;
|
|
25
|
+
onComplete?: (value: string) => void;
|
|
26
|
+
formatter?: (value: string) => string;
|
|
27
|
+
mask?: import("./input-otp.js").InputOTPMaskProp;
|
|
28
|
+
align?: import("./input-otp.js").InputOTPAlignProp;
|
|
29
|
+
size?: import("../../props/index.js").SizeProp;
|
|
30
|
+
status?: import("../../props/index.js").ControlStatusProp;
|
|
31
|
+
variant?: import("../../props/index.js").ControlVariantProp;
|
|
32
|
+
pattern?: string;
|
|
33
33
|
pasteTransformer?: (pasted: string) => string;
|
|
34
|
-
containerClassName?:
|
|
34
|
+
containerClassName?: import("../../props/index.js").ClassNameProp;
|
|
35
|
+
pushPasswordManagerStrategy?: "increase-width" | "none";
|
|
35
36
|
noScriptCSSFallback?: string | null;
|
|
36
37
|
nonce?: string;
|
|
37
|
-
|
|
38
|
-
render?:
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
align?: InputOTPAlignProp;
|
|
42
|
-
}) & React.RefAttributes<HTMLInputElement>>;
|
|
43
|
-
export type { InputOTPAlignProp, InputOTPGroupAppearanceProp, InputOTPGroupProp, InputOTPGroupProp as InputOTPGroupProps, } from "../../props/components/data-entry.prop.js";
|
|
38
|
+
children?: React.ReactNode;
|
|
39
|
+
render?: (props: import("input-otp").RenderProps) => React.ReactNode;
|
|
40
|
+
} & React.RefAttributes<HTMLInputElement>>;
|
|
41
|
+
export type { InputOTPAlignProp, InputOTPGroupAppearanceProp, InputOTPGroupProp, InputOTPGroupProp as InputOTPGroupProps, InputOTPMaskProp, InputOTPProp, InputOTPProp as InputOTPProps, } from "../../props/components/data-entry.prop.js";
|
|
44
42
|
export declare const InputOTPGroup: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
|
|
45
43
|
appearance?: import("./input-otp.js").InputOTPGroupAppearanceProp;
|
|
46
44
|
} & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -4,18 +4,58 @@ import * as React from "react";
|
|
|
4
4
|
import { OTPInput, OTPInputContext } from "input-otp";
|
|
5
5
|
import { Minus } from "lucide-react";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import { controlSurfaceAttrs, resolveAriaInvalid } from "../data-entry/control-surface.js";
|
|
8
|
+
const DEFAULT_MASK_CHAR = "\u2022";
|
|
9
|
+
const InputOTPMaskContext = React.createContext(null);
|
|
10
|
+
const InputOTP = React.forwardRef(function InputOTP2({
|
|
11
|
+
className,
|
|
12
|
+
containerClassName,
|
|
13
|
+
align,
|
|
14
|
+
mask,
|
|
15
|
+
formatter,
|
|
16
|
+
size,
|
|
17
|
+
status,
|
|
18
|
+
variant,
|
|
19
|
+
value,
|
|
20
|
+
defaultValue,
|
|
21
|
+
onChange,
|
|
22
|
+
onValueChange,
|
|
23
|
+
readOnly,
|
|
24
|
+
"aria-invalid": ariaInvalid,
|
|
25
|
+
children,
|
|
26
|
+
render,
|
|
27
|
+
...props
|
|
28
|
+
}, ref) {
|
|
29
|
+
const [uncontrolled, setUncontrolled] = React.useState(defaultValue ?? "");
|
|
30
|
+
const isControlled = value !== void 0;
|
|
31
|
+
const current = isControlled ? value : uncontrolled;
|
|
32
|
+
const handleChange = (next) => {
|
|
33
|
+
if (readOnly) return;
|
|
34
|
+
const formatted = formatter ? formatter(next) : next;
|
|
35
|
+
if (!isControlled) setUncontrolled(formatted);
|
|
36
|
+
onValueChange?.(formatted);
|
|
37
|
+
if (onChange !== onValueChange) onChange?.(formatted);
|
|
38
|
+
};
|
|
39
|
+
const maskChar = mask === true ? DEFAULT_MASK_CHAR : typeof mask === "string" && mask ? mask.charAt(0) : null;
|
|
40
|
+
const shared = {
|
|
10
41
|
ref,
|
|
11
42
|
"data-slot": "input-otp",
|
|
43
|
+
// INERT DEFAULT: `start` emits no attribute, so an existing field keeps its exact DOM.
|
|
12
44
|
"data-align": align && align !== "start" ? align : void 0,
|
|
45
|
+
...controlSurfaceAttrs({ size, status, variant }),
|
|
46
|
+
"aria-invalid": resolveAriaInvalid(ariaInvalid, status),
|
|
47
|
+
// Native `readonly` already conveys the state, but the library's readOnly contract names
|
|
48
|
+
// `aria-readonly` explicitly and half the controls did not honour it.
|
|
49
|
+
"aria-readonly": readOnly ? true : void 0,
|
|
50
|
+
readOnly,
|
|
51
|
+
value: current,
|
|
52
|
+
onChange: handleChange,
|
|
13
53
|
containerClassName: cn("ui-otp-container", containerClassName),
|
|
14
54
|
className: cn("ui-otp-input", className),
|
|
15
55
|
...props
|
|
16
|
-
}
|
|
17
|
-
));
|
|
18
|
-
|
|
56
|
+
};
|
|
57
|
+
return /* @__PURE__ */ jsx(InputOTPMaskContext.Provider, { value: maskChar, children: render ? /* @__PURE__ */ jsx(OTPInput, { ...shared, render }) : /* @__PURE__ */ jsx(OTPInput, { ...shared, children }) });
|
|
58
|
+
});
|
|
19
59
|
const InputOTPGroup = React.forwardRef(
|
|
20
60
|
({ appearance = "slots", className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
21
61
|
"div",
|
|
@@ -31,17 +71,20 @@ const InputOTPGroup = React.forwardRef(
|
|
|
31
71
|
InputOTPGroup.displayName = "InputOTPGroup";
|
|
32
72
|
const InputOTPSlot = React.forwardRef(({ index, className, ...props }, ref) => {
|
|
33
73
|
const context = React.useContext(OTPInputContext);
|
|
74
|
+
const maskChar = React.useContext(InputOTPMaskContext);
|
|
34
75
|
const slot = context.slots[index] ?? { char: null, hasFakeCaret: false, isActive: false };
|
|
76
|
+
const painted = slot.char === null ? null : maskChar ?? slot.char;
|
|
35
77
|
return /* @__PURE__ */ jsxs(
|
|
36
78
|
"div",
|
|
37
79
|
{
|
|
38
80
|
ref,
|
|
39
81
|
"data-slot": "input-otp-slot",
|
|
40
82
|
"data-active": slot.isActive || void 0,
|
|
83
|
+
"data-masked": maskChar && slot.char !== null ? "" : void 0,
|
|
41
84
|
className: cn("ui-otp-slot", className),
|
|
42
85
|
...props,
|
|
43
86
|
children: [
|
|
44
|
-
|
|
87
|
+
painted,
|
|
45
88
|
slot.hasFakeCaret ? /* @__PURE__ */ jsx("div", { className: "ui-otp-caret-wrapper", "aria-hidden": "true", children: /* @__PURE__ */ jsx("div", { className: "ui-otp-caret" }) }) : null
|
|
46
89
|
]
|
|
47
90
|
}
|
|
@@ -1,4 +1,38 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { Input } from "../data-entry/input.js";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* antd `Input.Password.visibilityToggle`. `false` removes the eye entirely — a kiosk, a shared
|
|
5
|
+
* screen, or a security policy that forbids ever revealing a secret has no other way to say so.
|
|
6
|
+
* The object form is the controlled triad for the reveal state: `visible` wins when provided, and
|
|
7
|
+
* `onVisibleChange` fires on both the controlled and the uncontrolled path.
|
|
8
|
+
*/
|
|
9
|
+
export type PasswordVisibilityToggleProp = boolean | {
|
|
10
|
+
visible?: boolean;
|
|
11
|
+
onVisibleChange?: (visible: boolean) => void;
|
|
12
|
+
};
|
|
13
|
+
export type PasswordInputProps = Omit<React.ComponentPropsWithoutRef<typeof Input>, "type"> & {
|
|
14
|
+
/** antd `visibilityToggle` — see {@link PasswordVisibilityToggleProp}. Default `true`. */
|
|
15
|
+
visibilityToggle?: PasswordVisibilityToggleProp;
|
|
16
|
+
/** antd `iconRender` — replace the eye glyph. Receives the CURRENT visibility. */
|
|
17
|
+
iconRender?: (visible: boolean) => React.ReactNode;
|
|
18
|
+
};
|
|
19
|
+
export declare const PasswordInput: React.ForwardRefExoticComponent<Omit<Omit<Omit<React.InputHTMLAttributes<HTMLInputElement>, "prefix" | "size"> & {
|
|
20
|
+
onValueChange?: (value: string) => void;
|
|
21
|
+
size?: "sm" | "md" | "lg";
|
|
22
|
+
status?: import("../../props/index.js").ControlStatusProp;
|
|
23
|
+
variant?: import("../../props/index.js").ControlVariantProp;
|
|
24
|
+
allowClear?: import("../../props/index.js").AllowClearProp;
|
|
25
|
+
onClear?: () => void;
|
|
26
|
+
leadingIcon?: React.ReactNode;
|
|
27
|
+
trailingIcon?: React.ReactNode;
|
|
28
|
+
prefix?: React.ReactNode;
|
|
29
|
+
suffix?: React.ReactNode;
|
|
30
|
+
addonBefore?: React.ReactNode;
|
|
31
|
+
addonAfter?: React.ReactNode;
|
|
32
|
+
count?: import("../../props/components/data-entry.prop.js").ControlCountProp;
|
|
33
|
+
} & React.RefAttributes<HTMLInputElement>, "ref">, "type"> & {
|
|
34
|
+
/** antd `visibilityToggle` — see {@link PasswordVisibilityToggleProp}. Default `true`. */
|
|
35
|
+
visibilityToggle?: PasswordVisibilityToggleProp;
|
|
36
|
+
/** antd `iconRender` — replace the eye glyph. Receives the CURRENT visibility. */
|
|
37
|
+
iconRender?: (visible: boolean) => React.ReactNode;
|
|
38
|
+
} & React.RefAttributes<HTMLInputElement>>;
|
|
@@ -6,30 +6,50 @@ import { useTranslation } from "../../i18n/use-translation.js";
|
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
7
|
import { Input } from "../data-entry/input.js";
|
|
8
8
|
const PasswordInput = React.forwardRef(
|
|
9
|
-
({ className, ...props }, ref) => {
|
|
9
|
+
({ className, visibilityToggle = true, iconRender, suffix, trailingIcon, allowClear, ...props }, ref) => {
|
|
10
10
|
const { t } = useTranslation();
|
|
11
|
-
const
|
|
11
|
+
const controlledVisible = typeof visibilityToggle === "object" ? visibilityToggle.visible : void 0;
|
|
12
|
+
const onVisibleChange = typeof visibilityToggle === "object" ? visibilityToggle.onVisibleChange : void 0;
|
|
13
|
+
const showToggle = visibilityToggle !== false;
|
|
14
|
+
const [internalVisible, setInternalVisible] = React.useState(false);
|
|
15
|
+
const isVisibleControlled = controlledVisible !== void 0;
|
|
16
|
+
const visible = showToggle ? isVisibleControlled ? controlledVisible : internalVisible : false;
|
|
17
|
+
const setVisible = (next) => {
|
|
18
|
+
if (!isVisibleControlled) setInternalVisible(next);
|
|
19
|
+
onVisibleChange?.(next);
|
|
20
|
+
};
|
|
21
|
+
const toggleOwnsTrailing = showToggle;
|
|
22
|
+
if (toggleOwnsTrailing && process.env?.NODE_ENV !== "production" && (suffix != null || trailingIcon != null || allowClear)) {
|
|
23
|
+
console.warn(
|
|
24
|
+
"[godxjp-ui] PasswordInput: the reveal toggle owns the trailing slot, so `suffix` / `trailingIcon` / `allowClear` are ignored. Use `leadingIcon` (or `prefix`), or pass `visibilityToggle={false}` to hand the slot back."
|
|
25
|
+
);
|
|
26
|
+
}
|
|
12
27
|
return /* @__PURE__ */ jsxs("div", { className: "ui-password-input", "data-slot": "password-input", children: [
|
|
13
28
|
/* @__PURE__ */ jsx(
|
|
14
29
|
Input,
|
|
15
30
|
{
|
|
16
31
|
ref,
|
|
17
32
|
type: visible ? "text" : "password",
|
|
18
|
-
className: cn("ui-password-input-field", className),
|
|
33
|
+
className: cn(showToggle && "ui-password-input-field", className),
|
|
34
|
+
suffix: toggleOwnsTrailing ? void 0 : suffix,
|
|
35
|
+
trailingIcon: toggleOwnsTrailing ? void 0 : trailingIcon,
|
|
36
|
+
allowClear: toggleOwnsTrailing ? false : allowClear,
|
|
19
37
|
...props
|
|
20
38
|
}
|
|
21
39
|
),
|
|
22
|
-
/* @__PURE__ */ jsx(
|
|
40
|
+
showToggle ? /* @__PURE__ */ jsx(
|
|
23
41
|
"button",
|
|
24
42
|
{
|
|
25
43
|
type: "button",
|
|
26
44
|
className: "ui-password-input-toggle",
|
|
27
|
-
|
|
45
|
+
disabled: props.disabled,
|
|
46
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
47
|
+
onClick: () => setVisible(!visible),
|
|
28
48
|
"aria-label": visible ? t("ui.passwordInput.hide") : t("ui.passwordInput.show"),
|
|
29
49
|
"aria-pressed": visible,
|
|
30
|
-
children: visible ? /* @__PURE__ */ jsx(EyeOff, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Eye, { "aria-hidden": "true" })
|
|
50
|
+
children: iconRender ? iconRender(visible) : visible ? /* @__PURE__ */ jsx(EyeOff, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Eye, { "aria-hidden": "true" })
|
|
31
51
|
}
|
|
32
|
-
)
|
|
52
|
+
) : null
|
|
33
53
|
] });
|
|
34
54
|
}
|
|
35
55
|
);
|
|
@@ -3,7 +3,32 @@ export type RatingProps = {
|
|
|
3
3
|
value?: number;
|
|
4
4
|
defaultValue?: number;
|
|
5
5
|
onValueChange?: (value: number) => void;
|
|
6
|
+
/** Number of symbols. This library's older name for antd's `count`; both are accepted. */
|
|
6
7
|
max?: number;
|
|
8
|
+
/** antd `count` — number of symbols. Default 5. */
|
|
9
|
+
count?: number;
|
|
10
|
+
/**
|
|
11
|
+
* antd `allowHalf` — a symbol can be half-filled, so the scale is 0.5 … count in steps of 0.5.
|
|
12
|
+
* The keyboard steps by a half too, and each symbol becomes TWO hit areas (leading / trailing).
|
|
13
|
+
*/
|
|
14
|
+
allowHalf?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* antd `allowClear` — choosing the value that is already chosen clears it back to 0. On by
|
|
17
|
+
* default in antd; off here, because a rating inside a form is usually required and a silent
|
|
18
|
+
* reset on a second click reads as a lost answer.
|
|
19
|
+
*/
|
|
20
|
+
allowClear?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* antd `character` — what a symbol IS. A node for every symbol, or a function of the 1-based
|
|
23
|
+
* index for a scale whose symbols differ (A/B/C, 松竹梅, a heart for the top step).
|
|
24
|
+
*/
|
|
25
|
+
character?: React.ReactNode | ((index: number) => React.ReactNode);
|
|
26
|
+
/**
|
|
27
|
+
* antd `tooltips` — a label per step, in order. It is folded into each symbol's ACCESSIBLE NAME
|
|
28
|
+
* rather than shown only on hover: a `title` attribute is invisible to a keyboard and to touch,
|
|
29
|
+
* and the whole point of the prop is to say what "3 of 5" means.
|
|
30
|
+
*/
|
|
31
|
+
tooltips?: readonly string[];
|
|
7
32
|
readOnly?: boolean;
|
|
8
33
|
disabled?: boolean;
|
|
9
34
|
name?: string;
|