@tinybigui/react 0.1.0 → 0.2.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 +24 -28
- package/dist/index.cjs +1548 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1561 -3
- package/dist/index.d.ts +1561 -3
- package/dist/index.js +1534 -20
- package/dist/index.js.map +1 -1
- package/dist/styles.css +5 -2
- package/dist/styles.css.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -4,10 +4,10 @@ var clsx = require('clsx');
|
|
|
4
4
|
var tailwindMerge = require('tailwind-merge');
|
|
5
5
|
var materialColorUtilities = require('@material/material-color-utilities');
|
|
6
6
|
var react = require('react');
|
|
7
|
-
var reactAria = require('react-aria');
|
|
8
|
-
var utils = require('@react-aria/utils');
|
|
9
7
|
var jsxRuntime = require('react/jsx-runtime');
|
|
10
8
|
var classVarianceAuthority = require('class-variance-authority');
|
|
9
|
+
var reactAria = require('react-aria');
|
|
10
|
+
var utils = require('@react-aria/utils');
|
|
11
11
|
var reactStately = require('react-stately');
|
|
12
12
|
|
|
13
13
|
// src/utils/cn.ts
|
|
@@ -131,6 +131,162 @@ function truncateText(lines = 1) {
|
|
|
131
131
|
textOverflow: "ellipsis"
|
|
132
132
|
};
|
|
133
133
|
}
|
|
134
|
+
function useScrollElevation(options = {}) {
|
|
135
|
+
const { scrolled: controlledScrolled, onScrollStateChange, threshold = 0 } = options;
|
|
136
|
+
const isControlled = controlledScrolled !== void 0;
|
|
137
|
+
const [internalScrolled, setInternalScrolled] = react.useState(false);
|
|
138
|
+
const handleScroll = react.useCallback(() => {
|
|
139
|
+
const currentlyScrolled = window.scrollY > threshold;
|
|
140
|
+
setInternalScrolled((prev) => {
|
|
141
|
+
if (prev !== currentlyScrolled) {
|
|
142
|
+
onScrollStateChange?.(currentlyScrolled);
|
|
143
|
+
return currentlyScrolled;
|
|
144
|
+
}
|
|
145
|
+
return prev;
|
|
146
|
+
});
|
|
147
|
+
}, [threshold, onScrollStateChange]);
|
|
148
|
+
react.useEffect(() => {
|
|
149
|
+
if (isControlled) return;
|
|
150
|
+
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
151
|
+
handleScroll();
|
|
152
|
+
return () => {
|
|
153
|
+
window.removeEventListener("scroll", handleScroll);
|
|
154
|
+
};
|
|
155
|
+
}, [isControlled, handleScroll]);
|
|
156
|
+
return {
|
|
157
|
+
isScrolled: isControlled ? controlledScrolled : internalScrolled
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
var AppBarHeadless = react.forwardRef(
|
|
161
|
+
({ className, children, scrolled: scrolledProp, onScrollStateChange }, ref) => {
|
|
162
|
+
const { isScrolled } = useScrollElevation({
|
|
163
|
+
scrolled: scrolledProp,
|
|
164
|
+
onScrollStateChange
|
|
165
|
+
});
|
|
166
|
+
return /* @__PURE__ */ jsxRuntime.jsx("header", { ref, role: "banner", className, "data-scrolled": isScrolled, children });
|
|
167
|
+
}
|
|
168
|
+
);
|
|
169
|
+
AppBarHeadless.displayName = "AppBarHeadless";
|
|
170
|
+
var appBarVariants = classVarianceAuthority.cva(
|
|
171
|
+
[
|
|
172
|
+
// Base classes (always applied)
|
|
173
|
+
"w-full",
|
|
174
|
+
"bg-surface text-on-surface",
|
|
175
|
+
"flex flex-col",
|
|
176
|
+
// Elevation transition using MD3 motion tokens
|
|
177
|
+
"transition-shadow duration-medium2 ease-standard"
|
|
178
|
+
],
|
|
179
|
+
{
|
|
180
|
+
variants: {
|
|
181
|
+
/**
|
|
182
|
+
* Size variant (MD3 specification)
|
|
183
|
+
* Controls bar height, title placement, and type scale
|
|
184
|
+
*/
|
|
185
|
+
variant: {
|
|
186
|
+
/** 64dp, title left-aligned, title-large */
|
|
187
|
+
small: "h-appbar-small",
|
|
188
|
+
/** 64dp, title centered, title-large */
|
|
189
|
+
"center-aligned": "h-appbar-small variant-center-aligned",
|
|
190
|
+
/** 112dp, title bottom-left, headline-small */
|
|
191
|
+
medium: "h-appbar-medium",
|
|
192
|
+
/** 152dp, title bottom-left, display-small */
|
|
193
|
+
large: "h-appbar-large"
|
|
194
|
+
},
|
|
195
|
+
/**
|
|
196
|
+
* Scroll state — controls surface elevation
|
|
197
|
+
* MD3: flat at rest, elevated on scroll
|
|
198
|
+
*/
|
|
199
|
+
scrolled: {
|
|
200
|
+
false: "shadow-elevation-0",
|
|
201
|
+
true: "shadow-elevation-2"
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
defaultVariants: {
|
|
205
|
+
variant: "small",
|
|
206
|
+
scrolled: false
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
var appBarTitleVariants = classVarianceAuthority.cva("text-on-surface font-normal truncate", {
|
|
211
|
+
variants: {
|
|
212
|
+
variant: {
|
|
213
|
+
small: "text-title-large",
|
|
214
|
+
"center-aligned": "text-title-large",
|
|
215
|
+
medium: "text-headline-small",
|
|
216
|
+
large: "text-display-small"
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
defaultVariants: {
|
|
220
|
+
variant: "small"
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
var AppBar = react.forwardRef(
|
|
224
|
+
({
|
|
225
|
+
variant = "small",
|
|
226
|
+
title,
|
|
227
|
+
navigationIcon,
|
|
228
|
+
actions,
|
|
229
|
+
scrolled: scrolledProp,
|
|
230
|
+
onScrollStateChange,
|
|
231
|
+
className
|
|
232
|
+
}, ref) => {
|
|
233
|
+
const { isScrolled } = useScrollElevation({
|
|
234
|
+
scrolled: scrolledProp,
|
|
235
|
+
onScrollStateChange
|
|
236
|
+
});
|
|
237
|
+
const isExpandedVariant = variant === "medium" || variant === "large";
|
|
238
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
239
|
+
AppBarHeadless,
|
|
240
|
+
{
|
|
241
|
+
ref,
|
|
242
|
+
scrolled: isScrolled,
|
|
243
|
+
className: cn(appBarVariants({ variant, scrolled: isScrolled }), className),
|
|
244
|
+
children: [
|
|
245
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
246
|
+
"div",
|
|
247
|
+
{
|
|
248
|
+
"data-slot": "top-row",
|
|
249
|
+
className: cn(
|
|
250
|
+
"flex items-center",
|
|
251
|
+
"px-1",
|
|
252
|
+
// Small and center-aligned: fill the full bar height
|
|
253
|
+
!isExpandedVariant && "flex-1",
|
|
254
|
+
// Expanded variants: fixed height for the top row (64dp)
|
|
255
|
+
isExpandedVariant && "h-16 shrink-0"
|
|
256
|
+
),
|
|
257
|
+
children: [
|
|
258
|
+
navigationIcon != null && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-slot": "navigation", className: "flex shrink-0 items-center", children: navigationIcon }),
|
|
259
|
+
!isExpandedVariant && /* @__PURE__ */ jsxRuntime.jsx(
|
|
260
|
+
"span",
|
|
261
|
+
{
|
|
262
|
+
"data-testid": "appbar-title",
|
|
263
|
+
className: cn(
|
|
264
|
+
"min-w-0 flex-1 px-1",
|
|
265
|
+
appBarTitleVariants({ variant }),
|
|
266
|
+
// Center-aligned: center the title text
|
|
267
|
+
variant === "center-aligned" && "text-center"
|
|
268
|
+
),
|
|
269
|
+
children: title
|
|
270
|
+
}
|
|
271
|
+
),
|
|
272
|
+
actions != null && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-slot": "actions", className: "flex shrink-0 items-center gap-0.5", children: actions })
|
|
273
|
+
]
|
|
274
|
+
}
|
|
275
|
+
),
|
|
276
|
+
isExpandedVariant && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-slot": "expanded-title", className: cn("flex flex-1 items-end", "px-4 pb-4"), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
277
|
+
"span",
|
|
278
|
+
{
|
|
279
|
+
"data-testid": "appbar-title",
|
|
280
|
+
className: cn("min-w-0 truncate", appBarTitleVariants({ variant })),
|
|
281
|
+
children: title
|
|
282
|
+
}
|
|
283
|
+
) })
|
|
284
|
+
]
|
|
285
|
+
}
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
);
|
|
289
|
+
AppBar.displayName = "AppBar";
|
|
134
290
|
var ButtonHeadless = react.forwardRef(
|
|
135
291
|
({ className, children, tabIndex = 0, onMouseDown, type, ...restProps }, forwardedRef) => {
|
|
136
292
|
const internalRef = react.useRef(null);
|
|
@@ -304,7 +460,7 @@ var buttonVariants = classVarianceAuthority.cva(
|
|
|
304
460
|
{
|
|
305
461
|
variant: "tonal",
|
|
306
462
|
color: "primary",
|
|
307
|
-
className: "bg-
|
|
463
|
+
className: "bg-primary-container text-on-primary-container"
|
|
308
464
|
},
|
|
309
465
|
{
|
|
310
466
|
variant: "tonal",
|
|
@@ -575,7 +731,7 @@ IconButtonHeadless.displayName = "IconButtonHeadless";
|
|
|
575
731
|
var iconButtonVariants = classVarianceAuthority.cva(
|
|
576
732
|
[
|
|
577
733
|
// Base classes (always applied)
|
|
578
|
-
"relative inline-flex items-center justify-center",
|
|
734
|
+
"relative inline-flex items-center justify-center cursor-pointer",
|
|
579
735
|
"overflow-hidden rounded-full",
|
|
580
736
|
// Circular shape
|
|
581
737
|
"transition-all duration-200",
|
|
@@ -889,7 +1045,7 @@ FABHeadless.displayName = "FABHeadless";
|
|
|
889
1045
|
var fabVariants = classVarianceAuthority.cva(
|
|
890
1046
|
[
|
|
891
1047
|
// Base classes (always applied)
|
|
892
|
-
"relative inline-flex items-center justify-center",
|
|
1048
|
+
"relative inline-flex items-center justify-center cursor-pointer",
|
|
893
1049
|
"overflow-hidden",
|
|
894
1050
|
"transition-all duration-200",
|
|
895
1051
|
"focus-visible:outline-primary focus-visible:outline-2 focus-visible:outline-offset-2",
|
|
@@ -1330,8 +1486,8 @@ var textFieldLabelVariants = classVarianceAuthority.cva(
|
|
|
1330
1486
|
{
|
|
1331
1487
|
variants: {
|
|
1332
1488
|
variant: {
|
|
1333
|
-
filled: "top-
|
|
1334
|
-
outlined: "top-
|
|
1489
|
+
filled: "top-2.5",
|
|
1490
|
+
outlined: "top-2.5 bg-surface px-1"
|
|
1335
1491
|
},
|
|
1336
1492
|
size: {
|
|
1337
1493
|
small: "text-sm",
|
|
@@ -1339,7 +1495,7 @@ var textFieldLabelVariants = classVarianceAuthority.cva(
|
|
|
1339
1495
|
large: "text-lg"
|
|
1340
1496
|
},
|
|
1341
1497
|
floating: {
|
|
1342
|
-
true: "-translate-y-
|
|
1498
|
+
true: "-translate-y-5 scale-75",
|
|
1343
1499
|
false: "scale-100"
|
|
1344
1500
|
},
|
|
1345
1501
|
focused: {
|
|
@@ -1364,7 +1520,7 @@ var textFieldLabelVariants = classVarianceAuthority.cva(
|
|
|
1364
1520
|
{
|
|
1365
1521
|
variant: "outlined",
|
|
1366
1522
|
floating: true,
|
|
1367
|
-
className: "
|
|
1523
|
+
className: "top-2.5"
|
|
1368
1524
|
}
|
|
1369
1525
|
],
|
|
1370
1526
|
defaultVariants: {
|
|
@@ -1677,7 +1833,8 @@ var TextField = react.forwardRef(
|
|
|
1677
1833
|
hasLeadingIcon: !!leadingIcon,
|
|
1678
1834
|
hasTrailingIcon: !!trailingIcon,
|
|
1679
1835
|
multiline: true
|
|
1680
|
-
})
|
|
1836
|
+
}),
|
|
1837
|
+
label && "placeholder:opacity-0"
|
|
1681
1838
|
),
|
|
1682
1839
|
rows,
|
|
1683
1840
|
spellCheck: spellCheckProp
|
|
@@ -1695,7 +1852,9 @@ var TextField = react.forwardRef(
|
|
|
1695
1852
|
hasLeadingIcon: !!leadingIcon,
|
|
1696
1853
|
hasTrailingIcon: !!trailingIcon,
|
|
1697
1854
|
multiline: false
|
|
1698
|
-
})
|
|
1855
|
+
}),
|
|
1856
|
+
label && "placeholder:opacity-0"
|
|
1857
|
+
// Hide placeholder when there's a value to prevent overlap with floating label
|
|
1699
1858
|
),
|
|
1700
1859
|
spellCheck: spellCheckProp
|
|
1701
1860
|
}
|
|
@@ -1929,8 +2088,7 @@ var checkboxLabelVariants = classVarianceAuthority.cva(
|
|
|
1929
2088
|
// MD3: Body Medium (14px)
|
|
1930
2089
|
"text-on-surface",
|
|
1931
2090
|
"select-none",
|
|
1932
|
-
"ml-
|
|
1933
|
-
// 16px spacing between checkbox and label (MD3 standard)
|
|
2091
|
+
"ml-1.5"
|
|
1934
2092
|
],
|
|
1935
2093
|
{
|
|
1936
2094
|
variants: {
|
|
@@ -2192,7 +2350,7 @@ var switchHandleContainerVariants = classVarianceAuthority.cva(
|
|
|
2192
2350
|
*/
|
|
2193
2351
|
selected: {
|
|
2194
2352
|
true: [
|
|
2195
|
-
"left-[
|
|
2353
|
+
"left-[24px]",
|
|
2196
2354
|
// Position when ON (52px - 24px = 28px)
|
|
2197
2355
|
"text-primary"
|
|
2198
2356
|
// State layer color
|
|
@@ -2732,8 +2890,7 @@ var radioLabelVariants = classVarianceAuthority.cva(
|
|
|
2732
2890
|
// MD3: Body Medium (14px)
|
|
2733
2891
|
"text-on-surface",
|
|
2734
2892
|
"select-none",
|
|
2735
|
-
"ml-
|
|
2736
|
-
// 16px spacing between radio and label (MD3 standard)
|
|
2893
|
+
"ml-1.5"
|
|
2737
2894
|
],
|
|
2738
2895
|
{
|
|
2739
2896
|
variants: {
|
|
@@ -2985,6 +3142,1363 @@ var RadioHeadless = react.forwardRef(
|
|
|
2985
3142
|
}
|
|
2986
3143
|
);
|
|
2987
3144
|
RadioHeadless.displayName = "RadioHeadless";
|
|
3145
|
+
var HeadlessTabsContext = react.createContext(null);
|
|
3146
|
+
function useHeadlessTabsContext(componentName) {
|
|
3147
|
+
const context = react.useContext(HeadlessTabsContext);
|
|
3148
|
+
if (!context) {
|
|
3149
|
+
throw new Error(`${componentName} must be used within a Tabs component`);
|
|
3150
|
+
}
|
|
3151
|
+
return context;
|
|
3152
|
+
}
|
|
3153
|
+
var HeadlessTabList = react.forwardRef(
|
|
3154
|
+
({ children, className }, forwardedRef) => {
|
|
3155
|
+
const { state } = useHeadlessTabsContext("HeadlessTabList");
|
|
3156
|
+
const internalRef = react.useRef(null);
|
|
3157
|
+
const ref = forwardedRef ?? internalRef;
|
|
3158
|
+
const { tabListProps } = reactAria.useTabList({}, state, ref);
|
|
3159
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ...tabListProps, ref, className, children });
|
|
3160
|
+
}
|
|
3161
|
+
);
|
|
3162
|
+
HeadlessTabList.displayName = "HeadlessTabList";
|
|
3163
|
+
var HeadlessTab = react.forwardRef(
|
|
3164
|
+
({ item, className, onMouseDown, children, ...props }, forwardedRef) => {
|
|
3165
|
+
const { state } = useHeadlessTabsContext("HeadlessTab");
|
|
3166
|
+
const internalRef = react.useRef(null);
|
|
3167
|
+
const ref = forwardedRef ?? internalRef;
|
|
3168
|
+
const { tabProps, isSelected, isDisabled, isPressed } = reactAria.useTab(
|
|
3169
|
+
{
|
|
3170
|
+
key: item.key,
|
|
3171
|
+
...item.isDisabled !== void 0 && { isDisabled: item.isDisabled }
|
|
3172
|
+
},
|
|
3173
|
+
state,
|
|
3174
|
+
ref
|
|
3175
|
+
);
|
|
3176
|
+
const { isFocusVisible, focusProps } = reactAria.useFocusRing();
|
|
3177
|
+
const mergedProps = utils.mergeProps(tabProps, focusProps, {
|
|
3178
|
+
className,
|
|
3179
|
+
onMouseDown
|
|
3180
|
+
});
|
|
3181
|
+
return /* @__PURE__ */ jsxRuntime.jsx("button", { ...mergedProps, ref, type: "button", "data-key": String(item.key), ...props, children: typeof children === "function" ? children({ isSelected, isDisabled, isFocusVisible, isPressed }) : children });
|
|
3182
|
+
}
|
|
3183
|
+
);
|
|
3184
|
+
HeadlessTab.displayName = "HeadlessTab";
|
|
3185
|
+
var HeadlessTabPanel = react.forwardRef(
|
|
3186
|
+
({ children, className, ...props }, forwardedRef) => {
|
|
3187
|
+
const { state } = useHeadlessTabsContext("HeadlessTabPanel");
|
|
3188
|
+
const internalRef = react.useRef(null);
|
|
3189
|
+
const ref = forwardedRef ?? internalRef;
|
|
3190
|
+
const { tabPanelProps } = reactAria.useTabPanel(props, state, ref);
|
|
3191
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ...tabPanelProps, ref, className, children });
|
|
3192
|
+
}
|
|
3193
|
+
);
|
|
3194
|
+
HeadlessTabPanel.displayName = "HeadlessTabPanel";
|
|
3195
|
+
var TabsContext = react.createContext(null);
|
|
3196
|
+
function useTabsContext() {
|
|
3197
|
+
const context = react.useContext(TabsContext);
|
|
3198
|
+
if (!context) {
|
|
3199
|
+
throw new Error("Component must be used within a Tabs component");
|
|
3200
|
+
}
|
|
3201
|
+
return context;
|
|
3202
|
+
}
|
|
3203
|
+
function getComponentName(type) {
|
|
3204
|
+
if (typeof type === "string") return type;
|
|
3205
|
+
const exotic = type;
|
|
3206
|
+
return exotic.displayName ?? exotic.name ?? "";
|
|
3207
|
+
}
|
|
3208
|
+
function extractTabItemsFromChildren(children) {
|
|
3209
|
+
const items = [];
|
|
3210
|
+
react.Children.forEach(children, (child) => {
|
|
3211
|
+
if (!react.isValidElement(child)) return;
|
|
3212
|
+
if (getComponentName(child.type) === "TabList") {
|
|
3213
|
+
const tabListProps = child.props;
|
|
3214
|
+
react.Children.forEach(tabListProps.children, (tabChild) => {
|
|
3215
|
+
if (!react.isValidElement(tabChild)) return;
|
|
3216
|
+
if (getComponentName(tabChild.type) === "Tab") {
|
|
3217
|
+
const tabProps = tabChild.props;
|
|
3218
|
+
items.push({
|
|
3219
|
+
key: tabProps.id,
|
|
3220
|
+
...tabProps.label !== void 0 && { label: tabProps.label },
|
|
3221
|
+
...tabProps.icon !== void 0 && { icon: tabProps.icon },
|
|
3222
|
+
...tabProps.isDisabled !== void 0 && { isDisabled: tabProps.isDisabled },
|
|
3223
|
+
...tabProps["aria-label"] !== void 0 && { "aria-label": tabProps["aria-label"] }
|
|
3224
|
+
});
|
|
3225
|
+
}
|
|
3226
|
+
});
|
|
3227
|
+
}
|
|
3228
|
+
});
|
|
3229
|
+
return items;
|
|
3230
|
+
}
|
|
3231
|
+
var Tabs = react.forwardRef(
|
|
3232
|
+
({
|
|
3233
|
+
selectedKey,
|
|
3234
|
+
defaultSelectedKey,
|
|
3235
|
+
onSelectionChange,
|
|
3236
|
+
variant = "primary",
|
|
3237
|
+
layout = "fixed",
|
|
3238
|
+
children,
|
|
3239
|
+
className,
|
|
3240
|
+
"aria-label": ariaLabel,
|
|
3241
|
+
"aria-labelledby": ariaLabelledBy
|
|
3242
|
+
}, ref) => {
|
|
3243
|
+
const tabItems = react.useMemo(() => extractTabItemsFromChildren(children), [children]);
|
|
3244
|
+
const state = reactStately.useTabListState({
|
|
3245
|
+
...selectedKey !== void 0 && { selectedKey },
|
|
3246
|
+
...defaultSelectedKey !== void 0 && { defaultSelectedKey },
|
|
3247
|
+
...onSelectionChange !== void 0 && { onSelectionChange },
|
|
3248
|
+
disabledKeys: tabItems.filter((t) => t.isDisabled).map((t) => t.key),
|
|
3249
|
+
children: tabItems.map((item) => /* @__PURE__ */ jsxRuntime.jsx(reactStately.Item, { textValue: item.label ?? item["aria-label"] ?? String(item.key), children: item.label ?? item["aria-label"] ?? "" }, item.key))
|
|
3250
|
+
});
|
|
3251
|
+
const tabsContextValue = react.useMemo(
|
|
3252
|
+
() => ({
|
|
3253
|
+
selectedKey: state.selectedKey,
|
|
3254
|
+
variant,
|
|
3255
|
+
layout,
|
|
3256
|
+
disabledKeys: tabItems.filter((t) => t.isDisabled).map((t) => t.key),
|
|
3257
|
+
...ariaLabel !== void 0 && { "aria-label": ariaLabel },
|
|
3258
|
+
...ariaLabelledBy !== void 0 && { "aria-labelledby": ariaLabelledBy }
|
|
3259
|
+
}),
|
|
3260
|
+
[state.selectedKey, variant, layout, tabItems, ariaLabel, ariaLabelledBy]
|
|
3261
|
+
);
|
|
3262
|
+
const headlessContextValue = react.useMemo(() => ({ state }), [state]);
|
|
3263
|
+
return /* @__PURE__ */ jsxRuntime.jsx(HeadlessTabsContext.Provider, { value: headlessContextValue, children: /* @__PURE__ */ jsxRuntime.jsx(TabsContext.Provider, { value: tabsContextValue, children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn("flex flex-col", className), children }) }) });
|
|
3264
|
+
}
|
|
3265
|
+
);
|
|
3266
|
+
Tabs.displayName = "Tabs";
|
|
3267
|
+
var tabListVariants = classVarianceAuthority.cva(
|
|
3268
|
+
[
|
|
3269
|
+
// Base classes
|
|
3270
|
+
"relative flex",
|
|
3271
|
+
"bg-surface",
|
|
3272
|
+
// Bottom divider line (MD3 spec)
|
|
3273
|
+
"border-b border-outline-variant"
|
|
3274
|
+
],
|
|
3275
|
+
{
|
|
3276
|
+
variants: {
|
|
3277
|
+
layout: {
|
|
3278
|
+
fixed: "w-full",
|
|
3279
|
+
scrollable: "overflow-x-auto scrollbar-none"
|
|
3280
|
+
}
|
|
3281
|
+
},
|
|
3282
|
+
defaultVariants: {
|
|
3283
|
+
layout: "fixed"
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
);
|
|
3287
|
+
var tabVariants = classVarianceAuthority.cva(
|
|
3288
|
+
[
|
|
3289
|
+
// Base layout
|
|
3290
|
+
"relative flex flex-col items-center justify-center",
|
|
3291
|
+
"min-h-12 px-4",
|
|
3292
|
+
"cursor-pointer select-none",
|
|
3293
|
+
"overflow-hidden",
|
|
3294
|
+
// Typography: MD3 Title Small
|
|
3295
|
+
"text-sm font-medium tracking-[0.1px]",
|
|
3296
|
+
// Transition
|
|
3297
|
+
"transition-colors duration-200",
|
|
3298
|
+
// Focus visible
|
|
3299
|
+
"focus-visible:outline-none",
|
|
3300
|
+
// State layer via before pseudo-element
|
|
3301
|
+
"before:absolute before:inset-0 before:transition-opacity before:duration-200",
|
|
3302
|
+
"before:bg-current before:opacity-0",
|
|
3303
|
+
"hover:before:opacity-8",
|
|
3304
|
+
"active:before:opacity-12",
|
|
3305
|
+
"focus-visible:before:opacity-12"
|
|
3306
|
+
],
|
|
3307
|
+
{
|
|
3308
|
+
variants: {
|
|
3309
|
+
/**
|
|
3310
|
+
* Tab variant (Primary or Secondary)
|
|
3311
|
+
*/
|
|
3312
|
+
variant: {
|
|
3313
|
+
primary: "",
|
|
3314
|
+
secondary: ""
|
|
3315
|
+
},
|
|
3316
|
+
/**
|
|
3317
|
+
* Selected state
|
|
3318
|
+
*/
|
|
3319
|
+
selected: {
|
|
3320
|
+
true: "",
|
|
3321
|
+
false: ""
|
|
3322
|
+
},
|
|
3323
|
+
/**
|
|
3324
|
+
* Disabled state
|
|
3325
|
+
*/
|
|
3326
|
+
disabled: {
|
|
3327
|
+
true: "opacity-38 cursor-not-allowed pointer-events-none",
|
|
3328
|
+
false: ""
|
|
3329
|
+
},
|
|
3330
|
+
/**
|
|
3331
|
+
* Layout determines min-width behavior
|
|
3332
|
+
*/
|
|
3333
|
+
layout: {
|
|
3334
|
+
fixed: "flex-1",
|
|
3335
|
+
scrollable: "min-w-[90px] shrink-0"
|
|
3336
|
+
}
|
|
3337
|
+
},
|
|
3338
|
+
compoundVariants: [
|
|
3339
|
+
// Primary + selected
|
|
3340
|
+
{
|
|
3341
|
+
variant: "primary",
|
|
3342
|
+
selected: true,
|
|
3343
|
+
disabled: false,
|
|
3344
|
+
className: "text-primary"
|
|
3345
|
+
},
|
|
3346
|
+
// Primary + unselected
|
|
3347
|
+
{
|
|
3348
|
+
variant: "primary",
|
|
3349
|
+
selected: false,
|
|
3350
|
+
disabled: false,
|
|
3351
|
+
className: "text-on-surface-variant"
|
|
3352
|
+
},
|
|
3353
|
+
// Secondary + selected
|
|
3354
|
+
{
|
|
3355
|
+
variant: "secondary",
|
|
3356
|
+
selected: true,
|
|
3357
|
+
disabled: false,
|
|
3358
|
+
className: "text-on-surface"
|
|
3359
|
+
},
|
|
3360
|
+
// Secondary + unselected
|
|
3361
|
+
{
|
|
3362
|
+
variant: "secondary",
|
|
3363
|
+
selected: false,
|
|
3364
|
+
disabled: false,
|
|
3365
|
+
className: "text-on-surface-variant"
|
|
3366
|
+
}
|
|
3367
|
+
],
|
|
3368
|
+
defaultVariants: {
|
|
3369
|
+
variant: "primary",
|
|
3370
|
+
selected: false,
|
|
3371
|
+
disabled: false,
|
|
3372
|
+
layout: "fixed"
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
);
|
|
3376
|
+
var tabIndicatorVariants = classVarianceAuthority.cva(
|
|
3377
|
+
[
|
|
3378
|
+
// Base: absolutely positioned at bottom
|
|
3379
|
+
"absolute bottom-0 left-0",
|
|
3380
|
+
"pointer-events-none",
|
|
3381
|
+
// Transition using MD3 motion tokens (medium2 duration, emphasized easing)
|
|
3382
|
+
"transition-[left,width]",
|
|
3383
|
+
"duration-medium2",
|
|
3384
|
+
"ease-emphasized"
|
|
3385
|
+
],
|
|
3386
|
+
{
|
|
3387
|
+
variants: {
|
|
3388
|
+
variant: {
|
|
3389
|
+
primary: ["h-[3px]", "bg-primary", "rounded-t-sm"],
|
|
3390
|
+
secondary: ["h-[2px]", "bg-on-surface-variant"]
|
|
3391
|
+
}
|
|
3392
|
+
},
|
|
3393
|
+
defaultVariants: {
|
|
3394
|
+
variant: "primary"
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
);
|
|
3398
|
+
var tabPanelVariants = classVarianceAuthority.cva(
|
|
3399
|
+
[
|
|
3400
|
+
// Base panel styles
|
|
3401
|
+
"outline-none",
|
|
3402
|
+
"focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2"
|
|
3403
|
+
],
|
|
3404
|
+
{
|
|
3405
|
+
variants: {},
|
|
3406
|
+
defaultVariants: {}
|
|
3407
|
+
}
|
|
3408
|
+
);
|
|
3409
|
+
var tabBadgeVariants = classVarianceAuthority.cva(
|
|
3410
|
+
[
|
|
3411
|
+
// Base badge
|
|
3412
|
+
"absolute",
|
|
3413
|
+
"inline-flex items-center justify-center",
|
|
3414
|
+
"bg-error text-on-error",
|
|
3415
|
+
"font-medium leading-none",
|
|
3416
|
+
"pointer-events-none"
|
|
3417
|
+
],
|
|
3418
|
+
{
|
|
3419
|
+
variants: {
|
|
3420
|
+
type: {
|
|
3421
|
+
dot: ["top-1 right-1", "w-1.5 h-1.5", "rounded-full"],
|
|
3422
|
+
count: ["-top-1 -right-1", "min-w-[16px] h-4", "px-1", "rounded-full", "text-[11px]"]
|
|
3423
|
+
}
|
|
3424
|
+
},
|
|
3425
|
+
defaultVariants: {
|
|
3426
|
+
type: "count"
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
);
|
|
3430
|
+
var tabIconVariants = classVarianceAuthority.cva(
|
|
3431
|
+
["relative", "inline-flex items-center justify-center", "w-6 h-6"],
|
|
3432
|
+
{
|
|
3433
|
+
variants: {
|
|
3434
|
+
hasLabel: {
|
|
3435
|
+
true: "mb-1",
|
|
3436
|
+
false: ""
|
|
3437
|
+
}
|
|
3438
|
+
},
|
|
3439
|
+
defaultVariants: {
|
|
3440
|
+
hasLabel: false
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
);
|
|
3444
|
+
var TabList = react.forwardRef(
|
|
3445
|
+
({ children, className }, forwardedRef) => {
|
|
3446
|
+
const { state } = useHeadlessTabsContext("TabList");
|
|
3447
|
+
const {
|
|
3448
|
+
variant,
|
|
3449
|
+
layout,
|
|
3450
|
+
"aria-label": ariaLabel,
|
|
3451
|
+
"aria-labelledby": ariaLabelledBy
|
|
3452
|
+
} = useTabsContext();
|
|
3453
|
+
const internalRef = react.useRef(null);
|
|
3454
|
+
const ref = forwardedRef ?? internalRef;
|
|
3455
|
+
const { tabListProps } = reactAria.useTabList(
|
|
3456
|
+
{
|
|
3457
|
+
...ariaLabel !== void 0 && { "aria-label": ariaLabel },
|
|
3458
|
+
...ariaLabelledBy !== void 0 && { "aria-labelledby": ariaLabelledBy }
|
|
3459
|
+
},
|
|
3460
|
+
state,
|
|
3461
|
+
ref
|
|
3462
|
+
);
|
|
3463
|
+
const handleNavKeyDown = react.useCallback(
|
|
3464
|
+
(e) => {
|
|
3465
|
+
if (!["ArrowRight", "ArrowLeft", "Home", "End"].includes(e.key)) return;
|
|
3466
|
+
const container = ref.current;
|
|
3467
|
+
if (!container) return;
|
|
3468
|
+
const focusedEl = document.activeElement;
|
|
3469
|
+
const currentKey = focusedEl?.dataset?.key;
|
|
3470
|
+
if (!currentKey) return;
|
|
3471
|
+
const allTabEls = [...container.querySelectorAll("[data-key]")];
|
|
3472
|
+
const allKeys = allTabEls.map((el) => el.dataset.key).filter(Boolean);
|
|
3473
|
+
const enabledKeys = allKeys.filter(
|
|
3474
|
+
(k) => allTabEls.find((el) => el.dataset.key === k)?.getAttribute("aria-disabled") !== "true"
|
|
3475
|
+
);
|
|
3476
|
+
const currentIndex = enabledKeys.indexOf(currentKey);
|
|
3477
|
+
if (currentIndex === -1) return;
|
|
3478
|
+
let nextKey = null;
|
|
3479
|
+
switch (e.key) {
|
|
3480
|
+
case "ArrowRight":
|
|
3481
|
+
nextKey = enabledKeys[(currentIndex + 1) % enabledKeys.length] ?? null;
|
|
3482
|
+
break;
|
|
3483
|
+
case "ArrowLeft":
|
|
3484
|
+
nextKey = enabledKeys[(currentIndex - 1 + enabledKeys.length) % enabledKeys.length] ?? null;
|
|
3485
|
+
break;
|
|
3486
|
+
case "Home":
|
|
3487
|
+
nextKey = enabledKeys[0] ?? null;
|
|
3488
|
+
break;
|
|
3489
|
+
case "End":
|
|
3490
|
+
nextKey = enabledKeys[enabledKeys.length - 1] ?? null;
|
|
3491
|
+
break;
|
|
3492
|
+
}
|
|
3493
|
+
if (nextKey != null) {
|
|
3494
|
+
e.preventDefault();
|
|
3495
|
+
state.setSelectedKey(nextKey);
|
|
3496
|
+
const nextEl = container.querySelector(
|
|
3497
|
+
`[data-key="${CSS.escape(nextKey)}"]`
|
|
3498
|
+
);
|
|
3499
|
+
nextEl?.focus();
|
|
3500
|
+
}
|
|
3501
|
+
},
|
|
3502
|
+
[state, ref]
|
|
3503
|
+
);
|
|
3504
|
+
const { onKeyDown: tabListKeyDown } = tabListProps;
|
|
3505
|
+
const handleKeyDown = react.useCallback(
|
|
3506
|
+
(e) => {
|
|
3507
|
+
handleNavKeyDown(e);
|
|
3508
|
+
if (!e.defaultPrevented) {
|
|
3509
|
+
tabListKeyDown?.(e);
|
|
3510
|
+
}
|
|
3511
|
+
},
|
|
3512
|
+
[handleNavKeyDown, tabListKeyDown]
|
|
3513
|
+
);
|
|
3514
|
+
const [indicatorStyle, setIndicatorStyle] = react.useState({ left: 0, width: 0 });
|
|
3515
|
+
const [indicatorReady, setIndicatorReady] = react.useState(false);
|
|
3516
|
+
const updateIndicator = react.useCallback(() => {
|
|
3517
|
+
const container = ref.current;
|
|
3518
|
+
if (!container) return;
|
|
3519
|
+
const selectedTab = container.querySelector('[aria-selected="true"]');
|
|
3520
|
+
if (!selectedTab) return;
|
|
3521
|
+
const containerRect = container.getBoundingClientRect();
|
|
3522
|
+
const tabRect = selectedTab.getBoundingClientRect();
|
|
3523
|
+
const newLeft = tabRect.left - containerRect.left + container.scrollLeft;
|
|
3524
|
+
const newWidth = tabRect.width;
|
|
3525
|
+
setIndicatorStyle((prev) => {
|
|
3526
|
+
if (prev.left === newLeft && prev.width === newWidth) return prev;
|
|
3527
|
+
return { left: newLeft, width: newWidth };
|
|
3528
|
+
});
|
|
3529
|
+
setIndicatorReady(true);
|
|
3530
|
+
}, [ref]);
|
|
3531
|
+
react.useLayoutEffect(() => {
|
|
3532
|
+
updateIndicator();
|
|
3533
|
+
}, [state.selectedKey, updateIndicator]);
|
|
3534
|
+
const mergedTabListProps = { ...tabListProps, onKeyDown: handleKeyDown };
|
|
3535
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...mergedTabListProps, ref, className: cn(tabListVariants({ layout }), className), children: [
|
|
3536
|
+
children,
|
|
3537
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3538
|
+
"span",
|
|
3539
|
+
{
|
|
3540
|
+
"data-tab-indicator": true,
|
|
3541
|
+
"aria-hidden": "true",
|
|
3542
|
+
className: cn(
|
|
3543
|
+
tabIndicatorVariants({ variant }),
|
|
3544
|
+
// Hide until first position is calculated to avoid flash
|
|
3545
|
+
!indicatorReady && "opacity-0"
|
|
3546
|
+
),
|
|
3547
|
+
style: {
|
|
3548
|
+
// Dynamic left/width values from DOM measurements
|
|
3549
|
+
left: `${indicatorStyle.left}px`,
|
|
3550
|
+
width: `${indicatorStyle.width}px`
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
)
|
|
3554
|
+
] });
|
|
3555
|
+
}
|
|
3556
|
+
);
|
|
3557
|
+
TabList.displayName = "TabList";
|
|
3558
|
+
function resolveBadgeDisplay(badge) {
|
|
3559
|
+
if (badge === void 0 || badge === false) return null;
|
|
3560
|
+
if (badge === true) return "dot";
|
|
3561
|
+
if (typeof badge === "number") {
|
|
3562
|
+
if (badge === 0) return null;
|
|
3563
|
+
return badge > 999 ? "999+" : String(badge);
|
|
3564
|
+
}
|
|
3565
|
+
return null;
|
|
3566
|
+
}
|
|
3567
|
+
var Tab = react.forwardRef(
|
|
3568
|
+
({ id, icon, label, badge, isDisabled = false, disableRipple = false, className, ...htmlProps }, forwardedRef) => {
|
|
3569
|
+
const { state } = useHeadlessTabsContext("Tab");
|
|
3570
|
+
const { variant, layout } = useTabsContext();
|
|
3571
|
+
const internalRef = react.useRef(null);
|
|
3572
|
+
const ref = forwardedRef ?? internalRef;
|
|
3573
|
+
const {
|
|
3574
|
+
tabProps,
|
|
3575
|
+
isSelected,
|
|
3576
|
+
isDisabled: ariaIsDisabled
|
|
3577
|
+
} = reactAria.useTab({ key: id, isDisabled }, state, ref);
|
|
3578
|
+
const { isFocusVisible, focusProps } = reactAria.useFocusRing();
|
|
3579
|
+
const finalIsDisabled = isDisabled || ariaIsDisabled;
|
|
3580
|
+
const { onMouseDown: handleRipple, ripples } = useRipple({
|
|
3581
|
+
disabled: finalIsDisabled || disableRipple
|
|
3582
|
+
});
|
|
3583
|
+
const handleClick = react.useCallback(() => {
|
|
3584
|
+
if (!finalIsDisabled) {
|
|
3585
|
+
state.setSelectedKey(id);
|
|
3586
|
+
}
|
|
3587
|
+
}, [state, id, finalIsDisabled]);
|
|
3588
|
+
const handleFocus = react.useCallback(() => {
|
|
3589
|
+
if (!finalIsDisabled) {
|
|
3590
|
+
state.selectionManager.setFocusedKey(id);
|
|
3591
|
+
}
|
|
3592
|
+
}, [state.selectionManager, id, finalIsDisabled]);
|
|
3593
|
+
const mergedProps = utils.mergeProps(tabProps, focusProps, {
|
|
3594
|
+
onMouseDown: disableRipple ? void 0 : handleRipple,
|
|
3595
|
+
onClick: handleClick,
|
|
3596
|
+
onFocus: handleFocus
|
|
3597
|
+
});
|
|
3598
|
+
const badgeDisplay = resolveBadgeDisplay(badge);
|
|
3599
|
+
const isDotBadge = badgeDisplay === "dot";
|
|
3600
|
+
const hasIcon = Boolean(icon);
|
|
3601
|
+
const hasLabel = Boolean(label);
|
|
3602
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3603
|
+
"button",
|
|
3604
|
+
{
|
|
3605
|
+
...mergedProps,
|
|
3606
|
+
ref,
|
|
3607
|
+
type: "button",
|
|
3608
|
+
"data-key": String(id),
|
|
3609
|
+
tabIndex: finalIsDisabled ? -1 : isSelected ? 0 : -1,
|
|
3610
|
+
className: cn(
|
|
3611
|
+
tabVariants({
|
|
3612
|
+
variant,
|
|
3613
|
+
selected: isSelected,
|
|
3614
|
+
disabled: finalIsDisabled,
|
|
3615
|
+
layout
|
|
3616
|
+
}),
|
|
3617
|
+
isFocusVisible && "outline-primary outline-2 outline-offset-2",
|
|
3618
|
+
hasLabel && hasIcon && "min-h-16",
|
|
3619
|
+
className
|
|
3620
|
+
),
|
|
3621
|
+
...htmlProps,
|
|
3622
|
+
children: [
|
|
3623
|
+
!disableRipple && ripples,
|
|
3624
|
+
hasIcon && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn(tabIconVariants({ hasLabel })), children: [
|
|
3625
|
+
icon,
|
|
3626
|
+
badgeDisplay && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3627
|
+
"span",
|
|
3628
|
+
{
|
|
3629
|
+
"data-badge-type": isDotBadge ? "dot" : "count",
|
|
3630
|
+
"aria-hidden": "true",
|
|
3631
|
+
className: cn(tabBadgeVariants({ type: isDotBadge ? "dot" : "count" })),
|
|
3632
|
+
children: !isDotBadge && badgeDisplay
|
|
3633
|
+
}
|
|
3634
|
+
)
|
|
3635
|
+
] }),
|
|
3636
|
+
hasLabel && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative z-10 truncate", children: [
|
|
3637
|
+
label,
|
|
3638
|
+
!hasIcon && badgeDisplay && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3639
|
+
"span",
|
|
3640
|
+
{
|
|
3641
|
+
"data-badge-type": isDotBadge ? "dot" : "count",
|
|
3642
|
+
"aria-hidden": "true",
|
|
3643
|
+
className: cn(
|
|
3644
|
+
"relative ml-1",
|
|
3645
|
+
tabBadgeVariants({ type: isDotBadge ? "dot" : "count" })
|
|
3646
|
+
),
|
|
3647
|
+
children: !isDotBadge && badgeDisplay
|
|
3648
|
+
}
|
|
3649
|
+
)
|
|
3650
|
+
] })
|
|
3651
|
+
]
|
|
3652
|
+
}
|
|
3653
|
+
);
|
|
3654
|
+
}
|
|
3655
|
+
);
|
|
3656
|
+
Tab.displayName = "Tab";
|
|
3657
|
+
var TabPanel = react.forwardRef(
|
|
3658
|
+
({ id, children, className }, forwardedRef) => {
|
|
3659
|
+
const { state } = useHeadlessTabsContext("TabPanel");
|
|
3660
|
+
useTabsContext();
|
|
3661
|
+
const internalRef = react.useRef(null);
|
|
3662
|
+
const ref = forwardedRef ?? internalRef;
|
|
3663
|
+
const { tabPanelProps } = reactAria.useTabPanel({}, state, ref);
|
|
3664
|
+
if (state.selectedKey !== id) {
|
|
3665
|
+
return null;
|
|
3666
|
+
}
|
|
3667
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ...tabPanelProps, ref, className: cn(tabPanelVariants(), className), children });
|
|
3668
|
+
}
|
|
3669
|
+
);
|
|
3670
|
+
TabPanel.displayName = "TabPanel";
|
|
3671
|
+
var NavigationBarContext = react.createContext(null);
|
|
3672
|
+
function useNavigationBarContext() {
|
|
3673
|
+
const ctx = react.useContext(NavigationBarContext);
|
|
3674
|
+
if (ctx === null) {
|
|
3675
|
+
throw new Error("HeadlessNavigationBarItem must be rendered inside HeadlessNavigationBar.");
|
|
3676
|
+
}
|
|
3677
|
+
return ctx;
|
|
3678
|
+
}
|
|
3679
|
+
var HeadlessNavigationBar = react.forwardRef(
|
|
3680
|
+
({
|
|
3681
|
+
items,
|
|
3682
|
+
selectedKey,
|
|
3683
|
+
defaultSelectedKey,
|
|
3684
|
+
onSelectionChange,
|
|
3685
|
+
"aria-label": ariaLabel,
|
|
3686
|
+
className,
|
|
3687
|
+
renderItem
|
|
3688
|
+
}, ref) => {
|
|
3689
|
+
const collectionChildren = items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(reactStately.Item, { textValue: item.label ?? item["aria-label"] ?? item.key, children: item.key }, item.key));
|
|
3690
|
+
const disabledKeys = items.filter((item) => item.isDisabled).map((item) => item.key);
|
|
3691
|
+
const state = reactStately.useTabListState({
|
|
3692
|
+
children: collectionChildren,
|
|
3693
|
+
...selectedKey !== void 0 ? { selectedKey } : {},
|
|
3694
|
+
...defaultSelectedKey !== void 0 ? { defaultSelectedKey } : {},
|
|
3695
|
+
...onSelectionChange ? { onSelectionChange } : {},
|
|
3696
|
+
disabledKeys
|
|
3697
|
+
});
|
|
3698
|
+
const tabListRef = react.useRef(null);
|
|
3699
|
+
const { tabListProps } = reactAria.useTabList({ "aria-label": ariaLabel }, state, tabListRef);
|
|
3700
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3701
|
+
"nav",
|
|
3702
|
+
{
|
|
3703
|
+
ref,
|
|
3704
|
+
role: "navigation",
|
|
3705
|
+
"aria-label": ariaLabel,
|
|
3706
|
+
className,
|
|
3707
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(NavigationBarContext.Provider, { value: { state, hideLabels: false, disableRipple: false }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { ...tabListProps, ref: tabListRef, className: "flex h-full w-full items-stretch", children: [...state.collection].map((collectionItem) => {
|
|
3708
|
+
const itemConfig = items.find((i) => String(i.key) === String(collectionItem.key));
|
|
3709
|
+
if (!itemConfig) return null;
|
|
3710
|
+
return renderItem(itemConfig);
|
|
3711
|
+
}) }) })
|
|
3712
|
+
}
|
|
3713
|
+
);
|
|
3714
|
+
}
|
|
3715
|
+
);
|
|
3716
|
+
HeadlessNavigationBar.displayName = "HeadlessNavigationBar";
|
|
3717
|
+
var HeadlessNavigationBarItem = react.forwardRef(({ itemKey, children, className, "aria-label": ariaLabel }, ref) => {
|
|
3718
|
+
const { state } = useNavigationBarContext();
|
|
3719
|
+
const internalRef = react.useRef(null);
|
|
3720
|
+
const resolvedRef = ref ?? internalRef;
|
|
3721
|
+
const { tabProps, isSelected } = reactAria.useTab({ key: itemKey }, state, resolvedRef);
|
|
3722
|
+
const { "aria-controls": _controls, ...tabPropsWithoutControls } = tabProps;
|
|
3723
|
+
const { focusProps, isFocusVisible } = reactAria.useFocusRing();
|
|
3724
|
+
const content = typeof children === "function" ? children({ isSelected, isFocusVisible }) : children;
|
|
3725
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3726
|
+
"button",
|
|
3727
|
+
{
|
|
3728
|
+
type: "button",
|
|
3729
|
+
...utils.mergeProps(tabPropsWithoutControls, focusProps),
|
|
3730
|
+
ref: resolvedRef,
|
|
3731
|
+
className,
|
|
3732
|
+
"aria-label": ariaLabel,
|
|
3733
|
+
"data-focus-visible": isFocusVisible || void 0,
|
|
3734
|
+
"data-selected": isSelected,
|
|
3735
|
+
children: content
|
|
3736
|
+
}
|
|
3737
|
+
);
|
|
3738
|
+
});
|
|
3739
|
+
HeadlessNavigationBarItem.displayName = "HeadlessNavigationBarItem";
|
|
3740
|
+
var navigationBarVariants = classVarianceAuthority.cva([
|
|
3741
|
+
// Layout
|
|
3742
|
+
"fixed bottom-0 left-0 right-0 z-10",
|
|
3743
|
+
"w-full",
|
|
3744
|
+
"flex flex-row items-stretch",
|
|
3745
|
+
// MD3 surface
|
|
3746
|
+
"bg-surface-container",
|
|
3747
|
+
// MD3 height: 80dp
|
|
3748
|
+
"h-20",
|
|
3749
|
+
// Safe-area bottom (for mobile devices)
|
|
3750
|
+
"pb-safe"
|
|
3751
|
+
]);
|
|
3752
|
+
var navigationBarItemVariants = classVarianceAuthority.cva(
|
|
3753
|
+
[
|
|
3754
|
+
// Layout
|
|
3755
|
+
"relative flex flex-1 flex-col items-center justify-center",
|
|
3756
|
+
"cursor-pointer select-none outline-none",
|
|
3757
|
+
// State layer pseudo-element (covers the full item area)
|
|
3758
|
+
"before:absolute before:inset-0 before:rounded-none before:transition-opacity before:duration-short2 before:ease-standard",
|
|
3759
|
+
// Focus-visible ring
|
|
3760
|
+
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary",
|
|
3761
|
+
// Transition for color changes
|
|
3762
|
+
"transition-colors duration-short2 ease-standard"
|
|
3763
|
+
],
|
|
3764
|
+
{
|
|
3765
|
+
variants: {
|
|
3766
|
+
/**
|
|
3767
|
+
* Whether this item is the currently selected destination.
|
|
3768
|
+
* Controls icon and label colors per MD3 spec.
|
|
3769
|
+
*/
|
|
3770
|
+
isActive: {
|
|
3771
|
+
true: [
|
|
3772
|
+
// Active icon color applied via text-color on the icon wrapper
|
|
3773
|
+
"[&>[data-icon]]:text-on-secondary-container",
|
|
3774
|
+
// Active label color
|
|
3775
|
+
"[&>[data-label]]:text-on-surface",
|
|
3776
|
+
// State layer color for active item
|
|
3777
|
+
"before:bg-on-surface-variant"
|
|
3778
|
+
],
|
|
3779
|
+
false: [
|
|
3780
|
+
// Inactive icon color
|
|
3781
|
+
"[&>[data-icon]]:text-on-surface-variant",
|
|
3782
|
+
// Inactive label color
|
|
3783
|
+
"[&>[data-label]]:text-on-surface-variant",
|
|
3784
|
+
// State layer color for inactive item
|
|
3785
|
+
"before:bg-on-surface-variant"
|
|
3786
|
+
]
|
|
3787
|
+
},
|
|
3788
|
+
/**
|
|
3789
|
+
* Whether the item is disabled.
|
|
3790
|
+
* Applies `opacity-38` per MD3 disabled state.
|
|
3791
|
+
*/
|
|
3792
|
+
isDisabled: {
|
|
3793
|
+
true: ["cursor-not-allowed opacity-38 pointer-events-none"],
|
|
3794
|
+
false: []
|
|
3795
|
+
},
|
|
3796
|
+
/**
|
|
3797
|
+
* Hover and press state layer opacities.
|
|
3798
|
+
* Applied via compound variants on hover/active pseudo-classes.
|
|
3799
|
+
*/
|
|
3800
|
+
isHovered: {
|
|
3801
|
+
true: ["before:opacity-8"],
|
|
3802
|
+
false: ["before:opacity-0"]
|
|
3803
|
+
},
|
|
3804
|
+
isPressed: {
|
|
3805
|
+
true: ["before:opacity-12"],
|
|
3806
|
+
false: []
|
|
3807
|
+
}
|
|
3808
|
+
},
|
|
3809
|
+
compoundVariants: [
|
|
3810
|
+
// When not hovered or pressed, state layer is invisible
|
|
3811
|
+
{
|
|
3812
|
+
isHovered: false,
|
|
3813
|
+
isPressed: false,
|
|
3814
|
+
className: "before:opacity-0"
|
|
3815
|
+
}
|
|
3816
|
+
],
|
|
3817
|
+
defaultVariants: {
|
|
3818
|
+
isActive: false,
|
|
3819
|
+
isDisabled: false,
|
|
3820
|
+
isHovered: false,
|
|
3821
|
+
isPressed: false
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
);
|
|
3825
|
+
var indicatorPillVariants = classVarianceAuthority.cva(
|
|
3826
|
+
[
|
|
3827
|
+
"absolute left-1/2 -translate-x-1/2 top-1/2 -translate-y-1/2",
|
|
3828
|
+
"w-16 h-8",
|
|
3829
|
+
"rounded-full bg-secondary-container",
|
|
3830
|
+
"transition-[transform,opacity] duration-medium2 ease-emphasized",
|
|
3831
|
+
"origin-center"
|
|
3832
|
+
],
|
|
3833
|
+
{
|
|
3834
|
+
variants: {
|
|
3835
|
+
isActive: {
|
|
3836
|
+
true: ["scale-x-100 opacity-100"],
|
|
3837
|
+
false: ["scale-x-0 opacity-0"]
|
|
3838
|
+
}
|
|
3839
|
+
},
|
|
3840
|
+
defaultVariants: {
|
|
3841
|
+
isActive: false
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
);
|
|
3845
|
+
var badgeVariants = classVarianceAuthority.cva(
|
|
3846
|
+
[
|
|
3847
|
+
"absolute",
|
|
3848
|
+
"flex items-center justify-center",
|
|
3849
|
+
"bg-error text-on-error",
|
|
3850
|
+
"font-medium leading-none",
|
|
3851
|
+
// MD3 label-small
|
|
3852
|
+
"text-[0.6875rem]"
|
|
3853
|
+
],
|
|
3854
|
+
{
|
|
3855
|
+
variants: {
|
|
3856
|
+
isDot: {
|
|
3857
|
+
true: [
|
|
3858
|
+
// Dot: 6dp diameter, top-right of icon
|
|
3859
|
+
"top-0 right-0.5 z-10",
|
|
3860
|
+
"w-1.5 h-1.5 min-w-0 rounded-full"
|
|
3861
|
+
],
|
|
3862
|
+
false: [
|
|
3863
|
+
// Numeric: pill shape, top-right of icon
|
|
3864
|
+
"-top-1 left-3 z-10",
|
|
3865
|
+
"min-w-[1rem] h-4 px-1 rounded-full"
|
|
3866
|
+
]
|
|
3867
|
+
}
|
|
3868
|
+
},
|
|
3869
|
+
defaultVariants: {
|
|
3870
|
+
isDot: false
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
);
|
|
3874
|
+
var iconWrapperVariants = classVarianceAuthority.cva([
|
|
3875
|
+
"relative z-10 flex items-center justify-center",
|
|
3876
|
+
"w-6 h-6"
|
|
3877
|
+
]);
|
|
3878
|
+
var labelVariants = classVarianceAuthority.cva([
|
|
3879
|
+
"mt-1 select-none",
|
|
3880
|
+
"text-label-medium",
|
|
3881
|
+
"transition-colors duration-short2 ease-standard",
|
|
3882
|
+
"truncate max-w-full"
|
|
3883
|
+
]);
|
|
3884
|
+
var MIN_ITEMS = 3;
|
|
3885
|
+
var MAX_ITEMS = 5;
|
|
3886
|
+
function validateItemCount(count) {
|
|
3887
|
+
if (process.env.NODE_ENV !== "production" && (count < MIN_ITEMS || count > MAX_ITEMS)) {
|
|
3888
|
+
console.warn(
|
|
3889
|
+
`[NavigationBar] MD3 Navigation Bar requires between ${MIN_ITEMS} and ${MAX_ITEMS} destination items. Received ${count}. See: https://m3.material.io/components/navigation-bar/overview`
|
|
3890
|
+
);
|
|
3891
|
+
}
|
|
3892
|
+
}
|
|
3893
|
+
function getBadgeText(badge) {
|
|
3894
|
+
if (badge === void 0 || badge === 0) return null;
|
|
3895
|
+
if (badge === true) return null;
|
|
3896
|
+
if (badge > 999) return "999+";
|
|
3897
|
+
return String(badge);
|
|
3898
|
+
}
|
|
3899
|
+
function isBadgeVisible(badge) {
|
|
3900
|
+
if (badge === void 0) return false;
|
|
3901
|
+
if (badge === 0) return false;
|
|
3902
|
+
return true;
|
|
3903
|
+
}
|
|
3904
|
+
function ItemVisual({ config, isActive, hideLabels, disableRipple }) {
|
|
3905
|
+
const isItemDisabled = config.isDisabled === true;
|
|
3906
|
+
const { onMouseDown, ripples } = useRipple({
|
|
3907
|
+
...disableRipple || isItemDisabled ? { disabled: true } : {}
|
|
3908
|
+
});
|
|
3909
|
+
const showBadge = isBadgeVisible(config.badge);
|
|
3910
|
+
const isDot = config.badge === true;
|
|
3911
|
+
const badgeText = getBadgeText(config.badge);
|
|
3912
|
+
return (
|
|
3913
|
+
// Overflow-hidden wrapper required for ripple containment.
|
|
3914
|
+
// pointer-events-none is intentional: the parent <button> handles all interaction.
|
|
3915
|
+
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
3916
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3917
|
+
"span",
|
|
3918
|
+
{
|
|
3919
|
+
onMouseDown,
|
|
3920
|
+
className: "pointer-events-none relative flex h-full w-full flex-col items-center justify-center overflow-hidden",
|
|
3921
|
+
children: [
|
|
3922
|
+
ripples,
|
|
3923
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3924
|
+
"span",
|
|
3925
|
+
{
|
|
3926
|
+
"data-indicator-pill": true,
|
|
3927
|
+
"data-active": isActive,
|
|
3928
|
+
"aria-hidden": "true",
|
|
3929
|
+
className: cn(indicatorPillVariants({ isActive }), !hideLabels && "-mt-3.5")
|
|
3930
|
+
}
|
|
3931
|
+
),
|
|
3932
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3933
|
+
"span",
|
|
3934
|
+
{
|
|
3935
|
+
"data-icon": true,
|
|
3936
|
+
className: cn(
|
|
3937
|
+
iconWrapperVariants(),
|
|
3938
|
+
isActive ? "text-on-secondary-container" : "text-on-surface-variant"
|
|
3939
|
+
),
|
|
3940
|
+
children: [
|
|
3941
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative z-10 flex h-6 w-6 items-center justify-center", children: config.icon }),
|
|
3942
|
+
showBadge && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3943
|
+
"span",
|
|
3944
|
+
{
|
|
3945
|
+
"data-badge": true,
|
|
3946
|
+
"data-badge-dot": isDot || void 0,
|
|
3947
|
+
"aria-label": isDot ? "notification" : badgeText ? `${badgeText} notifications` : void 0,
|
|
3948
|
+
"aria-live": "polite",
|
|
3949
|
+
className: cn(badgeVariants({ isDot })),
|
|
3950
|
+
children: isDot ? null : badgeText
|
|
3951
|
+
}
|
|
3952
|
+
)
|
|
3953
|
+
]
|
|
3954
|
+
}
|
|
3955
|
+
),
|
|
3956
|
+
!hideLabels && config.label && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3957
|
+
"span",
|
|
3958
|
+
{
|
|
3959
|
+
"data-label": true,
|
|
3960
|
+
className: cn(labelVariants(), isActive ? "text-on-surface" : "text-on-surface-variant"),
|
|
3961
|
+
children: config.label
|
|
3962
|
+
}
|
|
3963
|
+
)
|
|
3964
|
+
]
|
|
3965
|
+
}
|
|
3966
|
+
)
|
|
3967
|
+
);
|
|
3968
|
+
}
|
|
3969
|
+
var NavigationBar = react.forwardRef(
|
|
3970
|
+
({
|
|
3971
|
+
items,
|
|
3972
|
+
activeKey,
|
|
3973
|
+
defaultActiveKey,
|
|
3974
|
+
onActiveChange,
|
|
3975
|
+
hideLabels = false,
|
|
3976
|
+
"aria-label": ariaLabel,
|
|
3977
|
+
disableRipple = false,
|
|
3978
|
+
className
|
|
3979
|
+
}, ref) => {
|
|
3980
|
+
validateItemCount(items.length);
|
|
3981
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3982
|
+
HeadlessNavigationBar,
|
|
3983
|
+
{
|
|
3984
|
+
ref,
|
|
3985
|
+
items,
|
|
3986
|
+
...activeKey !== void 0 ? { selectedKey: activeKey } : {},
|
|
3987
|
+
...defaultActiveKey !== void 0 ? { defaultSelectedKey: defaultActiveKey } : {},
|
|
3988
|
+
...onActiveChange ? { onSelectionChange: onActiveChange } : {},
|
|
3989
|
+
"aria-label": ariaLabel,
|
|
3990
|
+
className: cn(navigationBarVariants(), className),
|
|
3991
|
+
renderItem: (config) => (
|
|
3992
|
+
// HeadlessNavigationBarItem renders the <button role="tab"> with all
|
|
3993
|
+
// ARIA semantics. ItemVisual renders the icon/pill/badge/label inside
|
|
3994
|
+
// — no nested <button> elements.
|
|
3995
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3996
|
+
HeadlessNavigationBarItem,
|
|
3997
|
+
{
|
|
3998
|
+
itemKey: config.key,
|
|
3999
|
+
...config["aria-label"] !== void 0 ? { "aria-label": config["aria-label"] } : {},
|
|
4000
|
+
className: cn(
|
|
4001
|
+
"relative flex flex-1 flex-col items-center justify-center",
|
|
4002
|
+
"cursor-pointer outline-none select-none",
|
|
4003
|
+
"duration-short2 ease-standard transition-colors",
|
|
4004
|
+
// State layer pseudo-element
|
|
4005
|
+
"before:absolute before:inset-0 before:rounded-none",
|
|
4006
|
+
"before:bg-on-surface-variant before:opacity-0",
|
|
4007
|
+
"before:duration-short2 before:ease-standard before:transition-opacity",
|
|
4008
|
+
"hover:before:opacity-8",
|
|
4009
|
+
"active:before:opacity-12",
|
|
4010
|
+
// Focus ring
|
|
4011
|
+
"focus-visible:outline-primary focus-visible:outline-2 focus-visible:outline-offset-2",
|
|
4012
|
+
// Disabled styling
|
|
4013
|
+
config.isDisabled && "pointer-events-none cursor-not-allowed opacity-38"
|
|
4014
|
+
),
|
|
4015
|
+
children: ({ isSelected }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
4016
|
+
ItemVisual,
|
|
4017
|
+
{
|
|
4018
|
+
config,
|
|
4019
|
+
isActive: isSelected,
|
|
4020
|
+
hideLabels,
|
|
4021
|
+
disableRipple
|
|
4022
|
+
}
|
|
4023
|
+
)
|
|
4024
|
+
},
|
|
4025
|
+
config.key
|
|
4026
|
+
)
|
|
4027
|
+
)
|
|
4028
|
+
}
|
|
4029
|
+
);
|
|
4030
|
+
}
|
|
4031
|
+
);
|
|
4032
|
+
NavigationBar.displayName = "NavigationBar";
|
|
4033
|
+
function getBadgeContent(badge) {
|
|
4034
|
+
if (badge === true) return null;
|
|
4035
|
+
if (badge === 0) return "";
|
|
4036
|
+
if (badge > 999) return "999+";
|
|
4037
|
+
return String(badge);
|
|
4038
|
+
}
|
|
4039
|
+
function isBadgeVisible2(badge) {
|
|
4040
|
+
if (badge === void 0) return false;
|
|
4041
|
+
if (badge === 0) return false;
|
|
4042
|
+
return true;
|
|
4043
|
+
}
|
|
4044
|
+
var NavigationBarItem = react.forwardRef(
|
|
4045
|
+
({
|
|
4046
|
+
itemKey: _itemKey,
|
|
4047
|
+
icon,
|
|
4048
|
+
label,
|
|
4049
|
+
badge,
|
|
4050
|
+
isActive = false,
|
|
4051
|
+
hideLabels = false,
|
|
4052
|
+
isDisabled = false,
|
|
4053
|
+
disableRipple = false,
|
|
4054
|
+
className,
|
|
4055
|
+
"aria-label": ariaLabel,
|
|
4056
|
+
// Spread remaining props (e.g. tabProps from useTab)
|
|
4057
|
+
...rest
|
|
4058
|
+
}, ref) => {
|
|
4059
|
+
const { onMouseDown, ripples } = useRipple({
|
|
4060
|
+
...disableRipple || isDisabled ? { disabled: true } : {}
|
|
4061
|
+
});
|
|
4062
|
+
const showBadge = isBadgeVisible2(badge);
|
|
4063
|
+
const isDot = badge === true;
|
|
4064
|
+
const badgeContent = badge !== void 0 ? getBadgeContent(badge) : null;
|
|
4065
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4066
|
+
"button",
|
|
4067
|
+
{
|
|
4068
|
+
type: "button",
|
|
4069
|
+
ref,
|
|
4070
|
+
onMouseDown,
|
|
4071
|
+
disabled: isDisabled,
|
|
4072
|
+
"aria-label": ariaLabel,
|
|
4073
|
+
className: cn(
|
|
4074
|
+
navigationBarItemVariants({
|
|
4075
|
+
isActive,
|
|
4076
|
+
isDisabled
|
|
4077
|
+
}),
|
|
4078
|
+
className
|
|
4079
|
+
),
|
|
4080
|
+
...rest,
|
|
4081
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative flex h-full w-full flex-col items-center justify-center overflow-hidden rounded-none", children: [
|
|
4082
|
+
ripples,
|
|
4083
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4084
|
+
"span",
|
|
4085
|
+
{
|
|
4086
|
+
"data-indicator-pill": true,
|
|
4087
|
+
"data-active": isActive,
|
|
4088
|
+
className: cn(indicatorPillVariants({ isActive }), !hideLabels && "-mt-3.5"),
|
|
4089
|
+
"aria-hidden": "true"
|
|
4090
|
+
}
|
|
4091
|
+
),
|
|
4092
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { "data-icon": true, className: cn(iconWrapperVariants()), children: [
|
|
4093
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative z-10 flex h-6 w-6 items-center justify-center", children: icon }),
|
|
4094
|
+
showBadge && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4095
|
+
"span",
|
|
4096
|
+
{
|
|
4097
|
+
"data-badge": true,
|
|
4098
|
+
"data-badge-dot": isDot || void 0,
|
|
4099
|
+
"aria-label": isDot ? "notification" : badgeContent ? `${badgeContent} notifications` : void 0,
|
|
4100
|
+
"aria-live": "polite",
|
|
4101
|
+
className: cn(badgeVariants({ isDot })),
|
|
4102
|
+
children: isDot ? null : badgeContent
|
|
4103
|
+
}
|
|
4104
|
+
)
|
|
4105
|
+
] }),
|
|
4106
|
+
!hideLabels && label && /* @__PURE__ */ jsxRuntime.jsx("span", { "data-label": true, className: cn(labelVariants()), children: label })
|
|
4107
|
+
] })
|
|
4108
|
+
}
|
|
4109
|
+
);
|
|
4110
|
+
}
|
|
4111
|
+
);
|
|
4112
|
+
NavigationBarItem.displayName = "NavigationBarItem";
|
|
4113
|
+
var DrawerContext = react.createContext(null);
|
|
4114
|
+
var HeadlessDrawer = react.forwardRef(
|
|
4115
|
+
({
|
|
4116
|
+
variant = "standard",
|
|
4117
|
+
open,
|
|
4118
|
+
defaultOpen = false,
|
|
4119
|
+
onOpenChange,
|
|
4120
|
+
"aria-label": ariaLabel,
|
|
4121
|
+
children,
|
|
4122
|
+
className,
|
|
4123
|
+
scrimClassName,
|
|
4124
|
+
disableRipple = false
|
|
4125
|
+
}, ref) => {
|
|
4126
|
+
const state = reactStately.useOverlayTriggerState({
|
|
4127
|
+
...open !== void 0 ? { isOpen: open } : {},
|
|
4128
|
+
...defaultOpen !== void 0 ? { defaultOpen } : {},
|
|
4129
|
+
...onOpenChange !== void 0 ? { onOpenChange } : {}
|
|
4130
|
+
});
|
|
4131
|
+
const isOpen = state.isOpen;
|
|
4132
|
+
const close = react.useCallback(() => {
|
|
4133
|
+
state.close();
|
|
4134
|
+
}, [state]);
|
|
4135
|
+
const contextValue = {
|
|
4136
|
+
isOpen,
|
|
4137
|
+
close,
|
|
4138
|
+
disableRipple
|
|
4139
|
+
};
|
|
4140
|
+
if (variant === "modal") {
|
|
4141
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DrawerContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx("nav", { ref, role: "navigation", "aria-label": ariaLabel, children: isOpen && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4142
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4143
|
+
"div",
|
|
4144
|
+
{
|
|
4145
|
+
"data-testid": "drawer-scrim",
|
|
4146
|
+
className: scrimClassName,
|
|
4147
|
+
onClick: () => state.close(),
|
|
4148
|
+
"aria-hidden": "true"
|
|
4149
|
+
}
|
|
4150
|
+
),
|
|
4151
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactAria.FocusScope, { contain: true, restoreFocus: true, autoFocus: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4152
|
+
ModalDrawerPanel,
|
|
4153
|
+
{
|
|
4154
|
+
ariaLabel,
|
|
4155
|
+
onClose: () => state.close(),
|
|
4156
|
+
className,
|
|
4157
|
+
children
|
|
4158
|
+
}
|
|
4159
|
+
) })
|
|
4160
|
+
] }) }) });
|
|
4161
|
+
}
|
|
4162
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DrawerContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4163
|
+
"nav",
|
|
4164
|
+
{
|
|
4165
|
+
ref,
|
|
4166
|
+
role: "navigation",
|
|
4167
|
+
"aria-label": ariaLabel,
|
|
4168
|
+
className,
|
|
4169
|
+
children
|
|
4170
|
+
}
|
|
4171
|
+
) });
|
|
4172
|
+
}
|
|
4173
|
+
);
|
|
4174
|
+
HeadlessDrawer.displayName = "HeadlessDrawer";
|
|
4175
|
+
var ModalDrawerPanel = ({
|
|
4176
|
+
ariaLabel,
|
|
4177
|
+
onClose,
|
|
4178
|
+
className,
|
|
4179
|
+
children
|
|
4180
|
+
}) => {
|
|
4181
|
+
const panelRef = react.useRef(null);
|
|
4182
|
+
reactAria.usePreventScroll();
|
|
4183
|
+
const { dialogProps } = reactAria.useDialog({ "aria-label": ariaLabel }, panelRef);
|
|
4184
|
+
const { overlayProps } = reactAria.useOverlay(
|
|
4185
|
+
{
|
|
4186
|
+
isOpen: true,
|
|
4187
|
+
onClose,
|
|
4188
|
+
isDismissable: true,
|
|
4189
|
+
shouldCloseOnBlur: false
|
|
4190
|
+
},
|
|
4191
|
+
panelRef
|
|
4192
|
+
);
|
|
4193
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4194
|
+
"div",
|
|
4195
|
+
{
|
|
4196
|
+
...utils.mergeProps(overlayProps, dialogProps),
|
|
4197
|
+
ref: panelRef,
|
|
4198
|
+
className,
|
|
4199
|
+
"aria-modal": "true",
|
|
4200
|
+
children
|
|
4201
|
+
}
|
|
4202
|
+
);
|
|
4203
|
+
};
|
|
4204
|
+
ModalDrawerPanel.displayName = "ModalDrawerPanel";
|
|
4205
|
+
var HeadlessDrawerItem = react.forwardRef(
|
|
4206
|
+
({
|
|
4207
|
+
href,
|
|
4208
|
+
isActive = false,
|
|
4209
|
+
children,
|
|
4210
|
+
className,
|
|
4211
|
+
isDisabled,
|
|
4212
|
+
onMouseDown,
|
|
4213
|
+
onPress,
|
|
4214
|
+
onPressStart,
|
|
4215
|
+
onPressEnd,
|
|
4216
|
+
onPressChange,
|
|
4217
|
+
onPressUp,
|
|
4218
|
+
...restProps
|
|
4219
|
+
}, forwardedRef) => {
|
|
4220
|
+
const internalRef = react.useRef(null);
|
|
4221
|
+
const { isFocusVisible, focusProps } = reactAria.useFocusRing();
|
|
4222
|
+
if (href) {
|
|
4223
|
+
const linkRef = forwardedRef ?? internalRef;
|
|
4224
|
+
const { linkProps } = reactAria.useLink(
|
|
4225
|
+
{
|
|
4226
|
+
href,
|
|
4227
|
+
...isDisabled !== void 0 ? { isDisabled } : {},
|
|
4228
|
+
...onPress !== void 0 ? { onPress } : {}
|
|
4229
|
+
},
|
|
4230
|
+
linkRef
|
|
4231
|
+
);
|
|
4232
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4233
|
+
"a",
|
|
4234
|
+
{
|
|
4235
|
+
...utils.mergeProps(linkProps, focusProps, { onMouseDown }),
|
|
4236
|
+
ref: linkRef,
|
|
4237
|
+
href,
|
|
4238
|
+
className,
|
|
4239
|
+
"aria-current": isActive ? "page" : void 0,
|
|
4240
|
+
"data-focus-visible": isFocusVisible || void 0,
|
|
4241
|
+
"data-active": isActive || void 0,
|
|
4242
|
+
children
|
|
4243
|
+
}
|
|
4244
|
+
);
|
|
4245
|
+
}
|
|
4246
|
+
const buttonRef = forwardedRef ?? internalRef;
|
|
4247
|
+
const { buttonProps } = reactAria.useButton(
|
|
4248
|
+
{
|
|
4249
|
+
...restProps,
|
|
4250
|
+
...isDisabled !== void 0 ? { isDisabled } : {},
|
|
4251
|
+
...onPress !== void 0 ? { onPress } : {},
|
|
4252
|
+
...onPressStart !== void 0 ? { onPressStart } : {},
|
|
4253
|
+
...onPressEnd !== void 0 ? { onPressEnd } : {},
|
|
4254
|
+
...onPressChange !== void 0 ? { onPressChange } : {},
|
|
4255
|
+
...onPressUp !== void 0 ? { onPressUp } : {},
|
|
4256
|
+
elementType: "button"
|
|
4257
|
+
},
|
|
4258
|
+
buttonRef
|
|
4259
|
+
);
|
|
4260
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4261
|
+
"button",
|
|
4262
|
+
{
|
|
4263
|
+
type: "button",
|
|
4264
|
+
...utils.mergeProps(buttonProps, focusProps, { onMouseDown }),
|
|
4265
|
+
ref: buttonRef,
|
|
4266
|
+
className,
|
|
4267
|
+
"aria-current": isActive ? "page" : void 0,
|
|
4268
|
+
"data-focus-visible": isFocusVisible || void 0,
|
|
4269
|
+
"data-active": isActive || void 0,
|
|
4270
|
+
children
|
|
4271
|
+
}
|
|
4272
|
+
);
|
|
4273
|
+
}
|
|
4274
|
+
);
|
|
4275
|
+
HeadlessDrawerItem.displayName = "HeadlessDrawerItem";
|
|
4276
|
+
var drawerVariants = classVarianceAuthority.cva(
|
|
4277
|
+
[
|
|
4278
|
+
// Layout
|
|
4279
|
+
"fixed top-0 left-0 h-full w-drawer",
|
|
4280
|
+
"flex flex-col overflow-y-auto",
|
|
4281
|
+
// Stacking and shape
|
|
4282
|
+
"z-50",
|
|
4283
|
+
"rounded-r-xl",
|
|
4284
|
+
// Slide animation (transition applies to all open/closed state changes)
|
|
4285
|
+
"transition-transform duration-medium4 ease-emphasized-decelerate",
|
|
4286
|
+
// Focus outline removal (focus management handled by FocusScope / React Aria)
|
|
4287
|
+
"outline-none",
|
|
4288
|
+
// Padding for content spacing
|
|
4289
|
+
"px-3"
|
|
4290
|
+
],
|
|
4291
|
+
{
|
|
4292
|
+
variants: {
|
|
4293
|
+
/**
|
|
4294
|
+
* Structural variant — drives surface color and elevation.
|
|
4295
|
+
* - `standard`: inline nav panel, lower-elevation surface
|
|
4296
|
+
* - `modal`: overlay dialog with elevation shadow
|
|
4297
|
+
*/
|
|
4298
|
+
variant: {
|
|
4299
|
+
standard: ["bg-surface-container-low"],
|
|
4300
|
+
modal: ["bg-surface-container", "shadow-elevation-1"]
|
|
4301
|
+
},
|
|
4302
|
+
/**
|
|
4303
|
+
* Open/closed state — drives translation.
|
|
4304
|
+
* - `true`: drawer visible (`translate-x-0`)
|
|
4305
|
+
* - `false`: drawer off-screen (`-translate-x-full`)
|
|
4306
|
+
*/
|
|
4307
|
+
open: {
|
|
4308
|
+
true: ["translate-x-0"],
|
|
4309
|
+
false: ["-translate-x-full"]
|
|
4310
|
+
}
|
|
4311
|
+
},
|
|
4312
|
+
defaultVariants: {
|
|
4313
|
+
variant: "standard",
|
|
4314
|
+
open: false
|
|
4315
|
+
}
|
|
4316
|
+
}
|
|
4317
|
+
);
|
|
4318
|
+
var drawerItemVariants = classVarianceAuthority.cva(
|
|
4319
|
+
[
|
|
4320
|
+
// Layout
|
|
4321
|
+
"relative flex w-full items-center gap-3",
|
|
4322
|
+
"h-14 px-4",
|
|
4323
|
+
"rounded-full",
|
|
4324
|
+
// Typography
|
|
4325
|
+
"text-label-large",
|
|
4326
|
+
// Interaction
|
|
4327
|
+
"cursor-pointer select-none outline-none",
|
|
4328
|
+
// State layer pseudo-element
|
|
4329
|
+
"before:absolute before:inset-0 before:rounded-full",
|
|
4330
|
+
"before:transition-opacity before:duration-short2 before:ease-standard",
|
|
4331
|
+
"before:opacity-0",
|
|
4332
|
+
// Hover and focus visible state layers
|
|
4333
|
+
"hover:before:opacity-8",
|
|
4334
|
+
"focus-visible:before:opacity-12",
|
|
4335
|
+
// Active pressed state
|
|
4336
|
+
"active:before:opacity-12",
|
|
4337
|
+
// Transition for color changes
|
|
4338
|
+
"transition-colors duration-short2 ease-standard"
|
|
4339
|
+
],
|
|
4340
|
+
{
|
|
4341
|
+
variants: {
|
|
4342
|
+
/**
|
|
4343
|
+
* Whether this item is the currently active destination.
|
|
4344
|
+
* Controls background, text color, and icon color per MD3 spec.
|
|
4345
|
+
*/
|
|
4346
|
+
isActive: {
|
|
4347
|
+
true: [
|
|
4348
|
+
"bg-secondary-container",
|
|
4349
|
+
"text-on-secondary-container",
|
|
4350
|
+
"before:bg-on-secondary-container"
|
|
4351
|
+
],
|
|
4352
|
+
false: ["bg-transparent", "text-on-surface-variant", "before:bg-on-surface-variant"]
|
|
4353
|
+
},
|
|
4354
|
+
/**
|
|
4355
|
+
* Whether the item is disabled.
|
|
4356
|
+
* Applies `opacity-38` per MD3 disabled state spec.
|
|
4357
|
+
*/
|
|
4358
|
+
isDisabled: {
|
|
4359
|
+
true: ["opacity-38 cursor-not-allowed pointer-events-none"],
|
|
4360
|
+
false: []
|
|
4361
|
+
}
|
|
4362
|
+
},
|
|
4363
|
+
defaultVariants: {
|
|
4364
|
+
isActive: false,
|
|
4365
|
+
isDisabled: false
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
);
|
|
4369
|
+
var scrimVariants = classVarianceAuthority.cva([
|
|
4370
|
+
"fixed inset-0 z-40",
|
|
4371
|
+
"bg-scrim opacity-32",
|
|
4372
|
+
"transition-opacity duration-medium2 ease-standard"
|
|
4373
|
+
]);
|
|
4374
|
+
var drawerSectionVariants = classVarianceAuthority.cva(["flex flex-col w-full"]);
|
|
4375
|
+
var drawerSectionHeaderVariants = classVarianceAuthority.cva([
|
|
4376
|
+
"px-4 pt-4 pb-2",
|
|
4377
|
+
"text-title-small text-on-surface-variant",
|
|
4378
|
+
"select-none"
|
|
4379
|
+
]);
|
|
4380
|
+
var drawerDividerVariants = classVarianceAuthority.cva(["border-t border-outline-variant", "mx-4 my-2"]);
|
|
4381
|
+
var Drawer = react.forwardRef(
|
|
4382
|
+
({
|
|
4383
|
+
variant = "standard",
|
|
4384
|
+
open,
|
|
4385
|
+
defaultOpen = false,
|
|
4386
|
+
onOpenChange,
|
|
4387
|
+
"aria-label": ariaLabel,
|
|
4388
|
+
children,
|
|
4389
|
+
className,
|
|
4390
|
+
disableRipple = false,
|
|
4391
|
+
...restProps
|
|
4392
|
+
}, ref) => {
|
|
4393
|
+
const isOpen = open ?? defaultOpen;
|
|
4394
|
+
const drawerPanelClass = cn(
|
|
4395
|
+
drawerVariants({
|
|
4396
|
+
variant,
|
|
4397
|
+
open: isOpen
|
|
4398
|
+
}),
|
|
4399
|
+
className
|
|
4400
|
+
);
|
|
4401
|
+
const scrimClass = scrimVariants();
|
|
4402
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4403
|
+
HeadlessDrawer,
|
|
4404
|
+
{
|
|
4405
|
+
ref,
|
|
4406
|
+
variant,
|
|
4407
|
+
...open !== void 0 ? { open } : {},
|
|
4408
|
+
...defaultOpen !== void 0 ? { defaultOpen } : {},
|
|
4409
|
+
...onOpenChange !== void 0 ? { onOpenChange } : {},
|
|
4410
|
+
"aria-label": ariaLabel,
|
|
4411
|
+
className: drawerPanelClass,
|
|
4412
|
+
scrimClassName: scrimClass,
|
|
4413
|
+
disableRipple,
|
|
4414
|
+
...restProps,
|
|
4415
|
+
children
|
|
4416
|
+
}
|
|
4417
|
+
);
|
|
4418
|
+
}
|
|
4419
|
+
);
|
|
4420
|
+
Drawer.displayName = "Drawer";
|
|
4421
|
+
var DrawerItem = react.forwardRef(
|
|
4422
|
+
({
|
|
4423
|
+
href,
|
|
4424
|
+
icon,
|
|
4425
|
+
label,
|
|
4426
|
+
badge,
|
|
4427
|
+
secondaryText,
|
|
4428
|
+
isActive = false,
|
|
4429
|
+
isDisabled = false,
|
|
4430
|
+
disableRipple = false,
|
|
4431
|
+
className,
|
|
4432
|
+
onPress,
|
|
4433
|
+
onPressStart,
|
|
4434
|
+
onPressEnd,
|
|
4435
|
+
onPressChange,
|
|
4436
|
+
onPressUp,
|
|
4437
|
+
...restProps
|
|
4438
|
+
}, ref) => {
|
|
4439
|
+
const isItemDisabled = isDisabled;
|
|
4440
|
+
const { onMouseDown: handleRipple, ripples } = useRipple({
|
|
4441
|
+
disabled: isItemDisabled || disableRipple
|
|
4442
|
+
});
|
|
4443
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4444
|
+
HeadlessDrawerItem,
|
|
4445
|
+
{
|
|
4446
|
+
...restProps,
|
|
4447
|
+
ref,
|
|
4448
|
+
...href !== void 0 ? { href } : {},
|
|
4449
|
+
isActive,
|
|
4450
|
+
...isItemDisabled !== void 0 ? { isDisabled: isItemDisabled } : {},
|
|
4451
|
+
...onPress !== void 0 ? { onPress } : {},
|
|
4452
|
+
...onPressStart !== void 0 ? { onPressStart } : {},
|
|
4453
|
+
...onPressEnd !== void 0 ? { onPressEnd } : {},
|
|
4454
|
+
...onPressChange !== void 0 ? { onPressChange } : {},
|
|
4455
|
+
...onPressUp !== void 0 ? { onPressUp } : {},
|
|
4456
|
+
onMouseDown: handleRipple,
|
|
4457
|
+
className: cn(
|
|
4458
|
+
drawerItemVariants({
|
|
4459
|
+
isActive,
|
|
4460
|
+
isDisabled: isItemDisabled
|
|
4461
|
+
}),
|
|
4462
|
+
className
|
|
4463
|
+
),
|
|
4464
|
+
children: [
|
|
4465
|
+
ripples,
|
|
4466
|
+
icon && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4467
|
+
"span",
|
|
4468
|
+
{
|
|
4469
|
+
className: "relative z-10 flex shrink-0 items-center justify-center",
|
|
4470
|
+
"aria-hidden": "true",
|
|
4471
|
+
children: icon
|
|
4472
|
+
}
|
|
4473
|
+
),
|
|
4474
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative z-10 flex min-w-0 flex-1 flex-col text-left", children: [
|
|
4475
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: label }),
|
|
4476
|
+
secondaryText && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-body-small truncate opacity-70", children: secondaryText })
|
|
4477
|
+
] }),
|
|
4478
|
+
badge && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4479
|
+
"span",
|
|
4480
|
+
{
|
|
4481
|
+
className: "relative z-10 ml-auto flex shrink-0 items-center pr-2",
|
|
4482
|
+
"aria-hidden": "true",
|
|
4483
|
+
children: badge
|
|
4484
|
+
}
|
|
4485
|
+
)
|
|
4486
|
+
]
|
|
4487
|
+
}
|
|
4488
|
+
);
|
|
4489
|
+
}
|
|
4490
|
+
);
|
|
4491
|
+
DrawerItem.displayName = "DrawerItem";
|
|
4492
|
+
var DrawerSection = react.forwardRef(
|
|
4493
|
+
({ header, children, showDivider = false, className }, ref) => {
|
|
4494
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: cn(drawerSectionVariants(), className), children: [
|
|
4495
|
+
showDivider && /* @__PURE__ */ jsxRuntime.jsx("hr", { role: "separator", "aria-hidden": "true", className: drawerDividerVariants() }),
|
|
4496
|
+
header && /* @__PURE__ */ jsxRuntime.jsx("span", { className: drawerSectionHeaderVariants(), children: header }),
|
|
4497
|
+
children
|
|
4498
|
+
] });
|
|
4499
|
+
}
|
|
4500
|
+
);
|
|
4501
|
+
DrawerSection.displayName = "DrawerSection";
|
|
2988
4502
|
|
|
2989
4503
|
Object.defineProperty(exports, "argbFromHex", {
|
|
2990
4504
|
enumerable: true,
|
|
@@ -2994,12 +4508,26 @@ Object.defineProperty(exports, "hexFromArgb", {
|
|
|
2994
4508
|
enumerable: true,
|
|
2995
4509
|
get: function () { return materialColorUtilities.hexFromArgb; }
|
|
2996
4510
|
});
|
|
4511
|
+
exports.AppBar = AppBar;
|
|
4512
|
+
exports.AppBarHeadless = AppBarHeadless;
|
|
2997
4513
|
exports.Button = Button;
|
|
2998
4514
|
exports.Checkbox = Checkbox;
|
|
4515
|
+
exports.Drawer = Drawer;
|
|
4516
|
+
exports.DrawerItem = DrawerItem;
|
|
4517
|
+
exports.DrawerSection = DrawerSection;
|
|
2999
4518
|
exports.FAB = FAB;
|
|
3000
4519
|
exports.FABHeadless = FABHeadless;
|
|
4520
|
+
exports.HeadlessDrawer = HeadlessDrawer;
|
|
4521
|
+
exports.HeadlessDrawerItem = HeadlessDrawerItem;
|
|
4522
|
+
exports.HeadlessNavigationBar = HeadlessNavigationBar;
|
|
4523
|
+
exports.HeadlessNavigationBarItem = HeadlessNavigationBarItem;
|
|
4524
|
+
exports.HeadlessTab = HeadlessTab;
|
|
4525
|
+
exports.HeadlessTabList = HeadlessTabList;
|
|
4526
|
+
exports.HeadlessTabPanel = HeadlessTabPanel;
|
|
3001
4527
|
exports.IconButton = IconButton;
|
|
3002
4528
|
exports.IconButtonHeadless = IconButtonHeadless;
|
|
4529
|
+
exports.NavigationBar = NavigationBar;
|
|
4530
|
+
exports.NavigationBarItem = NavigationBarItem;
|
|
3003
4531
|
exports.Radio = Radio;
|
|
3004
4532
|
exports.RadioGroup = RadioGroup;
|
|
3005
4533
|
exports.RadioGroupHeadless = RadioGroupHeadless;
|
|
@@ -3008,6 +4536,10 @@ exports.STATE_LAYER_OPACITY = STATE_LAYER_OPACITY;
|
|
|
3008
4536
|
exports.Switch = Switch;
|
|
3009
4537
|
exports.TYPOGRAPHY_ELEMENT_MAP = TYPOGRAPHY_ELEMENT_MAP;
|
|
3010
4538
|
exports.TYPOGRAPHY_USAGE = TYPOGRAPHY_USAGE;
|
|
4539
|
+
exports.Tab = Tab;
|
|
4540
|
+
exports.TabList = TabList;
|
|
4541
|
+
exports.TabPanel = TabPanel;
|
|
4542
|
+
exports.Tabs = Tabs;
|
|
3011
4543
|
exports.TextField = TextField;
|
|
3012
4544
|
exports.applyStateLayer = applyStateLayer;
|
|
3013
4545
|
exports.cn = cn;
|