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