@nexub/react 7.4.0 → 7.6.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/accessibility.json +1 -1
- package/dist/components/Badge/Badge.d.ts +9 -0
- package/dist/components/Badge/Badge.js +40 -2
- package/dist/components/Button/Button.d.ts +1 -1
- package/dist/components/Combobox/Combobox.d.ts +6 -9
- package/dist/components/Combobox/Combobox.js +30 -9
- package/dist/components/CommandPalette/CommandPalette.d.ts +34 -13
- package/dist/components/CommandPalette/CommandPalette.js +174 -40
- package/dist/components/CommandPalette/index.d.ts +1 -1
- package/dist/components/DatePicker/DatePicker.d.ts +16 -1
- package/dist/components/DatePicker/DatePicker.js +100 -26
- package/dist/components/Dialog/Dialog.d.ts +7 -0
- package/dist/components/Dialog/Dialog.js +35 -8
- package/dist/components/EmptyState/EmptyState.d.ts +7 -1
- package/dist/components/EmptyState/EmptyState.js +21 -5
- package/dist/components/EmptyState/EmptyStatePreset.d.ts +2 -1
- package/dist/components/FileUpload/FileUpload.js +86 -32
- package/dist/components/Heading/Heading.d.ts +1 -1
- package/dist/components/IconButton/IconButton.d.ts +5 -1
- package/dist/components/IconButton/IconButton.js +14 -3
- package/dist/components/Input/Input.d.ts +6 -0
- package/dist/components/Input/Input.js +47 -5
- package/dist/components/Select/Select.d.ts +18 -3
- package/dist/components/Select/Select.js +170 -71
- package/dist/components/Slider/Slider.d.ts +10 -0
- package/dist/components/Slider/Slider.js +48 -3
- package/dist/components/Slider/index.d.ts +1 -1
- package/dist/components/Switch/Switch.d.ts +6 -0
- package/dist/components/Switch/Switch.js +29 -2
- package/dist/components/Tabs/Tabs.d.ts +5 -1
- package/dist/components/Tabs/Tabs.js +98 -35
- package/dist/components/Text/Text.d.ts +1 -1
- package/dist/components/Tooltip/Tooltip.d.ts +10 -3
- package/dist/components/Tooltip/Tooltip.js +16 -4
- package/dist/index.d.ts +3 -1
- package/dist/styles.css +1 -1
- package/package.json +6 -6
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
3
|
import * as React from "react";
|
|
4
|
+
import { X } from "@nexub/icons";
|
|
3
5
|
import { cva } from "class-variance-authority";
|
|
4
6
|
import { cn } from "../../utils/cn.js";
|
|
5
7
|
import { field, fieldDisabledWithin, fieldFocusWithin, fieldInvalid } from "../../utils/control.js";
|
|
8
|
+
import { useMergedRefs } from "../../hooks/useMergedRefs.js";
|
|
9
|
+
import { useNexubMessages } from "../NexubProvider/NexubProvider.js";
|
|
10
|
+
import { IconButton } from "../IconButton/IconButton.js";
|
|
11
|
+
import { Spinner } from "../Spinner/Spinner.js";
|
|
6
12
|
const inputVariants = cva(
|
|
7
13
|
[
|
|
8
14
|
// The wrapper owns ALL focus styling (border + ring). Strip every possible
|
|
@@ -67,6 +73,7 @@ const addonVariants = cva(
|
|
|
67
73
|
}
|
|
68
74
|
}
|
|
69
75
|
);
|
|
76
|
+
const nearLimit = (length, max) => length >= max - Math.max(1, Math.ceil(max * 0.1));
|
|
70
77
|
const Input = React.forwardRef(
|
|
71
78
|
({
|
|
72
79
|
className,
|
|
@@ -78,26 +85,61 @@ const Input = React.forwardRef(
|
|
|
78
85
|
trailingIcon,
|
|
79
86
|
wrapperClassName,
|
|
80
87
|
disabled,
|
|
88
|
+
clearable,
|
|
89
|
+
showCount,
|
|
90
|
+
loading,
|
|
81
91
|
...props
|
|
82
92
|
}, ref) => {
|
|
83
93
|
const hasAddon = Boolean(leadingAddon ?? trailingAddon);
|
|
94
|
+
const messages = useNexubMessages();
|
|
95
|
+
const innerRef = React.useRef(null);
|
|
96
|
+
const mergedRef = useMergedRefs(ref, innerRef);
|
|
97
|
+
const countId = React.useId();
|
|
98
|
+
const [uncontrolledLength, setLength] = React.useState(String(props.defaultValue ?? "").length);
|
|
99
|
+
const length = props.value !== void 0 && props.value !== null ? String(props.value).length : uncontrolledLength;
|
|
100
|
+
const max = props.maxLength;
|
|
101
|
+
const counting = Boolean(showCount && max !== void 0 && max >= 0);
|
|
102
|
+
const handleChange = (e) => {
|
|
103
|
+
setLength(e.target.value.length);
|
|
104
|
+
props.onChange?.(e);
|
|
105
|
+
};
|
|
106
|
+
const clear = () => {
|
|
107
|
+
const el = innerRef.current;
|
|
108
|
+
if (!el) return;
|
|
109
|
+
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set?.call(el, "");
|
|
110
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
111
|
+
el.focus();
|
|
112
|
+
};
|
|
84
113
|
const inputEl = /* @__PURE__ */ jsx(
|
|
85
114
|
"input",
|
|
86
115
|
{
|
|
87
|
-
ref,
|
|
88
116
|
className: cn(inputVariants({ size }), className),
|
|
89
117
|
"aria-invalid": invalid ?? void 0,
|
|
118
|
+
"aria-busy": loading || void 0,
|
|
90
119
|
disabled,
|
|
91
|
-
...props
|
|
120
|
+
...props,
|
|
121
|
+
"aria-describedby": [props["aria-describedby"], counting && countId].filter(Boolean).join(" ") || void 0,
|
|
122
|
+
onChange: handleChange,
|
|
123
|
+
ref: mergedRef
|
|
92
124
|
}
|
|
93
125
|
);
|
|
126
|
+
const showClear = clearable && length > 0 && !disabled && !props.readOnly;
|
|
127
|
+
const trailing = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
128
|
+
counting && /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-label-sm tabular-nums text-[var(--color-on-surface-muted)]", children: [
|
|
129
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: `${length}/${max}` }),
|
|
130
|
+
/* @__PURE__ */ jsx("span", { id: countId, className: "sr-only", "aria-live": nearLimit(length, max) ? "polite" : "off", children: messages.characterCount(length, max) })
|
|
131
|
+
] }),
|
|
132
|
+
loading && /* @__PURE__ */ jsx(Spinner, { size: "sm", className: "shrink-0" }),
|
|
133
|
+
showClear && /* @__PURE__ */ jsx(IconButton, { size: "xs", label: messages.clear, onClick: clear, className: "-me-1.5", children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4", "aria-hidden": "true" }) }),
|
|
134
|
+
trailingIcon && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[var(--color-on-surface-variant)]", children: trailingIcon })
|
|
135
|
+
] });
|
|
94
136
|
if (hasAddon) {
|
|
95
137
|
return /* @__PURE__ */ jsxs("div", { className: cn("flex w-full overflow-hidden", field, fieldFocusWithin, invalid && fieldInvalid), children: [
|
|
96
138
|
leadingAddon && /* @__PURE__ */ jsx("span", { className: cn(addonVariants({ side: "leading" })), children: leadingAddon }),
|
|
97
139
|
/* @__PURE__ */ jsxs("div", { className: cn(inputWrapperVariants({ size, invalid }), "rounded-none border-0 shadow-none focus-within:shadow-none", wrapperClassName), children: [
|
|
98
140
|
leadingIcon && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[var(--color-on-surface-variant)]", children: leadingIcon }),
|
|
99
141
|
inputEl,
|
|
100
|
-
|
|
142
|
+
trailing
|
|
101
143
|
] }),
|
|
102
144
|
trailingAddon && /* @__PURE__ */ jsx("span", { className: cn(addonVariants({ side: "trailing" })), children: trailingAddon })
|
|
103
145
|
] });
|
|
@@ -105,7 +147,7 @@ const Input = React.forwardRef(
|
|
|
105
147
|
return /* @__PURE__ */ jsxs("div", { className: cn(inputWrapperVariants({ size, invalid }), wrapperClassName), children: [
|
|
106
148
|
leadingIcon && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[var(--color-on-surface-variant)]", children: leadingIcon }),
|
|
107
149
|
inputEl,
|
|
108
|
-
|
|
150
|
+
trailing
|
|
109
151
|
] });
|
|
110
152
|
}
|
|
111
153
|
);
|
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import * as RadixSelect from '@radix-ui/react-select';
|
|
3
|
+
export interface SelectRootProps extends RadixSelect.SelectProps {
|
|
4
|
+
/** Shows a clear button on the Trigger while there is a value; clearing calls `onValueChange('')` and shows the placeholder. */
|
|
5
|
+
clearable?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface SelectContentProps extends RadixSelect.SelectContentProps {
|
|
8
|
+
/** A filter field at the top; items whose text (or `textValue`) doesn't match are hidden. Typing on an item moves to the field. */
|
|
9
|
+
searchable?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface SelectItemProps extends RadixSelect.SelectItemProps {
|
|
12
|
+
/** A secondary line under the label: the option's accessible description, not part of its name. */
|
|
13
|
+
description?: React.ReactNode;
|
|
14
|
+
}
|
|
3
15
|
export declare const Select: {
|
|
4
|
-
Root:
|
|
16
|
+
Root: {
|
|
17
|
+
({ clearable, value, defaultValue, onValueChange, onOpenChange, ...props }: SelectRootProps): React.JSX.Element;
|
|
18
|
+
displayName: string;
|
|
19
|
+
};
|
|
5
20
|
Group: React.ForwardRefExoticComponent<RadixSelect.SelectGroupProps & React.RefAttributes<HTMLDivElement>>;
|
|
6
21
|
Value: React.ForwardRefExoticComponent<RadixSelect.SelectValueProps & React.RefAttributes<HTMLSpanElement>>;
|
|
7
22
|
Trigger: React.ForwardRefExoticComponent<RadixSelect.SelectTriggerProps & {
|
|
@@ -9,8 +24,8 @@ export declare const Select: {
|
|
|
9
24
|
} & React.RefAttributes<HTMLButtonElement>>;
|
|
10
25
|
ScrollUpButton: React.ForwardRefExoticComponent<RadixSelect.SelectScrollUpButtonProps & React.RefAttributes<HTMLDivElement>>;
|
|
11
26
|
ScrollDownButton: React.ForwardRefExoticComponent<RadixSelect.SelectScrollDownButtonProps & React.RefAttributes<HTMLDivElement>>;
|
|
12
|
-
Content: React.ForwardRefExoticComponent<
|
|
27
|
+
Content: React.ForwardRefExoticComponent<SelectContentProps & React.RefAttributes<HTMLDivElement>>;
|
|
13
28
|
Label: React.ForwardRefExoticComponent<RadixSelect.SelectLabelProps & React.RefAttributes<HTMLDivElement>>;
|
|
14
|
-
Item: React.ForwardRefExoticComponent<
|
|
29
|
+
Item: React.ForwardRefExoticComponent<SelectItemProps & React.RefAttributes<HTMLDivElement>>;
|
|
15
30
|
Separator: React.ForwardRefExoticComponent<RadixSelect.SelectSeparatorProps & React.RefAttributes<HTMLDivElement>>;
|
|
16
31
|
};
|
|
@@ -2,37 +2,94 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import * as RadixSelect from "@radix-ui/react-select";
|
|
5
|
-
import { Check, ChevronDown, ChevronUp } from "@nexub/icons";
|
|
5
|
+
import { Check, ChevronDown, ChevronUp, X } from "@nexub/icons";
|
|
6
6
|
import { cn } from "../../utils/cn.js";
|
|
7
7
|
import { field, fieldFocusVisible, fieldInvalid } from "../../utils/control.js";
|
|
8
8
|
import { floatingSurface, menuLabel, menuSeparator } from "../../utils/surface.js";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
import { useControllableState } from "../../hooks/useControllableState.js";
|
|
10
|
+
import { useMergedRefs } from "../../hooks/useMergedRefs.js";
|
|
11
|
+
import { useNexubMessages } from "../NexubProvider/NexubProvider.js";
|
|
12
|
+
import { IconButton } from "../IconButton/IconButton.js";
|
|
13
|
+
const SelectContext = React.createContext({
|
|
14
|
+
clearable: false,
|
|
15
|
+
hasValue: false,
|
|
16
|
+
clear: () => {
|
|
17
|
+
},
|
|
18
|
+
query: "",
|
|
19
|
+
setQuery: () => {
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
const Root = ({ clearable = false, value, defaultValue, onValueChange, onOpenChange, ...props }) => {
|
|
23
|
+
const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
|
|
24
|
+
const [query, setQuery] = React.useState("");
|
|
25
|
+
const ctx = React.useMemo(
|
|
26
|
+
() => ({ clearable, hasValue: Boolean(current), clear: () => setCurrent(""), query, setQuery }),
|
|
27
|
+
[clearable, current, setCurrent, query]
|
|
28
|
+
);
|
|
29
|
+
return /* @__PURE__ */ jsx(SelectContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx(
|
|
30
|
+
RadixSelect.Root,
|
|
15
31
|
{
|
|
16
|
-
ref,
|
|
17
|
-
className: cn(
|
|
18
|
-
"flex h-control-md w-full items-center justify-between gap-2 px-3.5 text-body-md",
|
|
19
|
-
field,
|
|
20
|
-
fieldFocusVisible,
|
|
21
|
-
// Open reads like focus: the 2px primary border.
|
|
22
|
-
"data-[state=open]:border-[var(--color-primary)] data-[state=open]:shadow-[inset_0_0_0_1px_var(--color-primary)]",
|
|
23
|
-
"data-[placeholder]:text-[var(--color-on-surface-muted)]",
|
|
24
|
-
"disabled:cursor-not-allowed disabled:border-[var(--color-outline-variant)] disabled:bg-[var(--color-surface-container-low)] disabled:text-[var(--color-text-disabled)]",
|
|
25
|
-
invalid && fieldInvalid,
|
|
26
|
-
"[&>span]:line-clamp-1",
|
|
27
|
-
className
|
|
28
|
-
),
|
|
29
32
|
...props,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
...clearable ? { value: current ?? "", onValueChange: setCurrent } : { value, defaultValue, onValueChange },
|
|
34
|
+
onOpenChange: (open) => {
|
|
35
|
+
if (!open) setQuery("");
|
|
36
|
+
onOpenChange?.(open);
|
|
37
|
+
}
|
|
34
38
|
}
|
|
35
|
-
)
|
|
39
|
+
) });
|
|
40
|
+
};
|
|
41
|
+
Root.displayName = "Select.Root";
|
|
42
|
+
const Group = RadixSelect.Group;
|
|
43
|
+
const Value = RadixSelect.Value;
|
|
44
|
+
const Trigger = React.forwardRef(
|
|
45
|
+
({ className, children, invalid, ...props }, ref) => {
|
|
46
|
+
const ctx = React.useContext(SelectContext);
|
|
47
|
+
const messages = useNexubMessages();
|
|
48
|
+
const innerRef = React.useRef(null);
|
|
49
|
+
const mergedRef = useMergedRefs(ref, innerRef);
|
|
50
|
+
const showClear = ctx.clearable && ctx.hasValue && !props.disabled;
|
|
51
|
+
const trigger = /* @__PURE__ */ jsxs(
|
|
52
|
+
RadixSelect.Trigger,
|
|
53
|
+
{
|
|
54
|
+
ref: mergedRef,
|
|
55
|
+
className: cn(
|
|
56
|
+
"flex h-control-md w-full items-center justify-between gap-2 px-3.5 text-body-md",
|
|
57
|
+
field,
|
|
58
|
+
fieldFocusVisible,
|
|
59
|
+
// Open reads like focus: the 2px primary border.
|
|
60
|
+
"data-[state=open]:border-[var(--color-primary)] data-[state=open]:shadow-[inset_0_0_0_1px_var(--color-primary)]",
|
|
61
|
+
"data-[placeholder]:text-[var(--color-on-surface-muted)]",
|
|
62
|
+
"disabled:cursor-not-allowed disabled:border-[var(--color-outline-variant)] disabled:bg-[var(--color-surface-container-low)] disabled:text-[var(--color-text-disabled)]",
|
|
63
|
+
invalid && fieldInvalid,
|
|
64
|
+
"[&>span]:line-clamp-1",
|
|
65
|
+
className
|
|
66
|
+
),
|
|
67
|
+
...props,
|
|
68
|
+
children: [
|
|
69
|
+
children,
|
|
70
|
+
showClear && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ms-auto w-control-xs shrink-0" }),
|
|
71
|
+
/* @__PURE__ */ jsx(RadixSelect.Icon, { asChild: true, children: /* @__PURE__ */ jsx(ChevronDown, { size: "sm", className: "shrink-0 text-[var(--color-on-surface-variant)]", "aria-hidden": "true" }) })
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
if (!ctx.clearable) return trigger;
|
|
76
|
+
return /* @__PURE__ */ jsxs("div", { className: "relative w-full", children: [
|
|
77
|
+
trigger,
|
|
78
|
+
showClear && /* @__PURE__ */ jsx(
|
|
79
|
+
IconButton,
|
|
80
|
+
{
|
|
81
|
+
size: "xs",
|
|
82
|
+
label: messages.clearSelection,
|
|
83
|
+
className: "absolute end-9.5 top-1/2 -translate-y-1/2",
|
|
84
|
+
onClick: () => {
|
|
85
|
+
ctx.clear();
|
|
86
|
+
innerRef.current?.focus();
|
|
87
|
+
},
|
|
88
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4", "aria-hidden": "true" })
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
] });
|
|
92
|
+
}
|
|
36
93
|
);
|
|
37
94
|
Trigger.displayName = "Select.Trigger";
|
|
38
95
|
const ScrollUpButton = React.forwardRef(
|
|
@@ -60,35 +117,66 @@ const ScrollDownButton = React.forwardRef(
|
|
|
60
117
|
);
|
|
61
118
|
ScrollDownButton.displayName = "Select.ScrollDownButton";
|
|
62
119
|
const Content = React.forwardRef(
|
|
63
|
-
({ className, children, position = "popper", ...props }, ref) =>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
/* @__PURE__ */ jsx(ScrollUpButton, {}),
|
|
78
|
-
/* @__PURE__ */ jsx(
|
|
79
|
-
RadixSelect.Viewport,
|
|
80
|
-
{
|
|
81
|
-
className: cn(
|
|
82
|
-
"p-1.5",
|
|
83
|
-
position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
|
84
|
-
),
|
|
85
|
-
children
|
|
86
|
-
}
|
|
120
|
+
({ className, children, position = "popper", searchable, onKeyDown, ...props }, ref) => {
|
|
121
|
+
const ctx = React.useContext(SelectContext);
|
|
122
|
+
const messages = useNexubMessages();
|
|
123
|
+
const searchRef = React.useRef(null);
|
|
124
|
+
return /* @__PURE__ */ jsx(RadixSelect.Portal, { children: /* @__PURE__ */ jsxs(
|
|
125
|
+
RadixSelect.Content,
|
|
126
|
+
{
|
|
127
|
+
ref,
|
|
128
|
+
className: cn(
|
|
129
|
+
"relative z-[var(--z-popover)] max-h-96 min-w-32 overflow-hidden",
|
|
130
|
+
floatingSurface,
|
|
131
|
+
"nx-motion nx-pop origin-[var(--radix-select-content-transform-origin)]",
|
|
132
|
+
position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1 w-[var(--radix-select-trigger-width)]",
|
|
133
|
+
className
|
|
87
134
|
),
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
135
|
+
position,
|
|
136
|
+
onKeyDown: (e) => {
|
|
137
|
+
onKeyDown?.(e);
|
|
138
|
+
if (searchable && !e.defaultPrevented && e.target !== searchRef.current && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
139
|
+
const input = searchRef.current;
|
|
140
|
+
if (!input) return;
|
|
141
|
+
e.preventDefault();
|
|
142
|
+
input.focus();
|
|
143
|
+
input.setRangeText(e.key, input.value.length, input.value.length, "end");
|
|
144
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
...props,
|
|
148
|
+
children: [
|
|
149
|
+
searchable && /* @__PURE__ */ jsx("div", { className: "p-1.5 pb-0", children: /* @__PURE__ */ jsx(
|
|
150
|
+
"input",
|
|
151
|
+
{
|
|
152
|
+
ref: searchRef,
|
|
153
|
+
type: "text",
|
|
154
|
+
value: ctx.query,
|
|
155
|
+
onChange: (e) => ctx.setQuery(e.target.value),
|
|
156
|
+
onKeyDown: (e) => {
|
|
157
|
+
if (e.key.length === 1 || e.key === "Home" || e.key === "End") e.stopPropagation();
|
|
158
|
+
},
|
|
159
|
+
placeholder: messages.searchPlaceholder,
|
|
160
|
+
"aria-label": messages.search,
|
|
161
|
+
className: cn("h-control-sm w-full px-3 text-body-sm", field, fieldFocusVisible)
|
|
162
|
+
}
|
|
163
|
+
) }),
|
|
164
|
+
/* @__PURE__ */ jsx(ScrollUpButton, {}),
|
|
165
|
+
/* @__PURE__ */ jsx(
|
|
166
|
+
RadixSelect.Viewport,
|
|
167
|
+
{
|
|
168
|
+
className: cn(
|
|
169
|
+
"p-1.5",
|
|
170
|
+
position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
|
171
|
+
),
|
|
172
|
+
children
|
|
173
|
+
}
|
|
174
|
+
),
|
|
175
|
+
/* @__PURE__ */ jsx(ScrollDownButton, {})
|
|
176
|
+
]
|
|
177
|
+
}
|
|
178
|
+
) });
|
|
179
|
+
}
|
|
92
180
|
);
|
|
93
181
|
Content.displayName = "Select.Content";
|
|
94
182
|
const Label = React.forwardRef(
|
|
@@ -103,24 +191,35 @@ const Label = React.forwardRef(
|
|
|
103
191
|
);
|
|
104
192
|
Label.displayName = "Select.Label";
|
|
105
193
|
const Item = React.forwardRef(
|
|
106
|
-
({ className, children, ...props }, ref) =>
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
194
|
+
({ className, children, description, ...props }, ref) => {
|
|
195
|
+
const { query } = React.useContext(SelectContext);
|
|
196
|
+
const descriptionId = React.useId();
|
|
197
|
+
const text = props.textValue ?? (typeof children === "string" ? children : void 0);
|
|
198
|
+
const filtered = Boolean(query.trim()) && text !== void 0 && !text.toLowerCase().includes(query.trim().toLowerCase());
|
|
199
|
+
return /* @__PURE__ */ jsxs(
|
|
200
|
+
RadixSelect.Item,
|
|
201
|
+
{
|
|
202
|
+
ref,
|
|
203
|
+
"aria-describedby": description ? descriptionId : void 0,
|
|
204
|
+
className: cn(
|
|
205
|
+
"relative flex w-full cursor-default select-none items-center rounded-[10px] py-2 ps-8 pe-2 text-body-sm text-[var(--color-on-surface)] outline-none",
|
|
206
|
+
// Highlight = the on-surface state layer at hover opacity.
|
|
207
|
+
"focus:bg-[color-mix(in_srgb,var(--color-on-surface)_8%,transparent)]",
|
|
208
|
+
"data-[disabled]:pointer-events-none data-[disabled]:text-[var(--color-text-disabled)]",
|
|
209
|
+
className,
|
|
210
|
+
filtered && "hidden"
|
|
211
|
+
),
|
|
212
|
+
...props,
|
|
213
|
+
children: [
|
|
214
|
+
/* @__PURE__ */ jsx("span", { className: "absolute start-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx(RadixSelect.ItemIndicator, { children: /* @__PURE__ */ jsx(Check, { size: "xs", "aria-hidden": "true" }) }) }),
|
|
215
|
+
description ? /* @__PURE__ */ jsxs("span", { className: "flex flex-col", children: [
|
|
216
|
+
/* @__PURE__ */ jsx(RadixSelect.ItemText, { children }),
|
|
217
|
+
/* @__PURE__ */ jsx("span", { id: descriptionId, className: "text-label-sm text-[var(--color-on-surface-muted)]", children: description })
|
|
218
|
+
] }) : /* @__PURE__ */ jsx(RadixSelect.ItemText, { children })
|
|
219
|
+
]
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
}
|
|
124
223
|
);
|
|
125
224
|
Item.displayName = "Select.Item";
|
|
126
225
|
const Separator = React.forwardRef(
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import * as RadixSlider from '@radix-ui/react-slider';
|
|
3
|
+
export type SliderMark = number | {
|
|
4
|
+
value: number;
|
|
5
|
+
label?: string;
|
|
6
|
+
};
|
|
3
7
|
export interface SliderProps extends RadixSlider.SliderProps {
|
|
4
8
|
/** Accessible name per thumb (e.g. ['Minimum', 'Maximum']). Falls back to `aria-label`. */
|
|
5
9
|
thumbLabels?: string[];
|
|
10
|
+
/** Ticks along the track, optionally labelled (`[0, { value: 50, label: '50%' }, 100]`). */
|
|
11
|
+
marks?: SliderMark[];
|
|
12
|
+
/** Value bubble above each thumb: `true` always, `'hover'` on hover, focus and drag. */
|
|
13
|
+
showValue?: boolean | 'hover';
|
|
14
|
+
/** Formats the bubble and each thumb's `aria-valuetext`. */
|
|
15
|
+
formatValue?: (value: number) => string;
|
|
6
16
|
}
|
|
7
17
|
export declare const Slider: React.ForwardRefExoticComponent<SliderProps & React.RefAttributes<HTMLSpanElement>>;
|
|
@@ -4,25 +4,70 @@ import * as React from "react";
|
|
|
4
4
|
import * as RadixSlider from "@radix-ui/react-slider";
|
|
5
5
|
import { cn } from "../../utils/cn.js";
|
|
6
6
|
const Slider = React.forwardRef(
|
|
7
|
-
({ className, thumbLabels, "aria-label": ariaLabel, ...props }, ref) => {
|
|
7
|
+
({ className, thumbLabels, marks, showValue, formatValue, "aria-label": ariaLabel, onValueChange, ...props }, ref) => {
|
|
8
|
+
const { min = 0, max = 100, inverted } = props;
|
|
9
|
+
const [own, setOwn] = React.useState(props.defaultValue ?? [min]);
|
|
10
|
+
const values = props.value ?? own;
|
|
8
11
|
const count = (props.value ?? props.defaultValue ?? [0]).length;
|
|
12
|
+
const format = formatValue ?? String;
|
|
13
|
+
const at = (v) => {
|
|
14
|
+
const p = (v - min) / (max - min) * 100;
|
|
15
|
+
const pct = inverted ? 100 - p : p;
|
|
16
|
+
return { insetInlineStart: `calc(${pct}% + ${(0.5 - pct / 100) * 1.25}rem)` };
|
|
17
|
+
};
|
|
18
|
+
const list = marks?.map((m) => typeof m === "number" ? { value: m } : m);
|
|
19
|
+
const labelled = list?.some((m) => m.label);
|
|
9
20
|
return /* @__PURE__ */ jsxs(
|
|
10
21
|
RadixSlider.Root,
|
|
11
22
|
{
|
|
12
23
|
ref,
|
|
13
|
-
className: cn(
|
|
24
|
+
className: cn(
|
|
25
|
+
"group relative flex h-control-sm w-full touch-none select-none items-center",
|
|
26
|
+
labelled && "mb-5",
|
|
27
|
+
className
|
|
28
|
+
),
|
|
29
|
+
onValueChange: (v) => {
|
|
30
|
+
setOwn(v);
|
|
31
|
+
onValueChange?.(v);
|
|
32
|
+
},
|
|
14
33
|
...props,
|
|
15
34
|
children: [
|
|
16
35
|
/* @__PURE__ */ jsx(RadixSlider.Track, { className: "relative h-1.5 w-full grow overflow-hidden rounded-full bg-[var(--color-surface-container-highest)]", children: /* @__PURE__ */ jsx(RadixSlider.Range, { className: "absolute h-full bg-[var(--color-primary)] group-data-[disabled]:bg-[var(--color-text-disabled)]" }) }),
|
|
36
|
+
list?.map((m) => /* @__PURE__ */ jsxs(
|
|
37
|
+
"span",
|
|
38
|
+
{
|
|
39
|
+
"aria-hidden": "true",
|
|
40
|
+
"data-mark": m.value,
|
|
41
|
+
className: "pointer-events-none absolute inset-y-0 flex w-0 flex-col items-center justify-center",
|
|
42
|
+
style: at(m.value),
|
|
43
|
+
children: [
|
|
44
|
+
/* @__PURE__ */ jsx("span", { className: "h-1 w-1 shrink-0 rounded-full bg-[var(--color-on-surface-muted)]" }),
|
|
45
|
+
m.label && /* @__PURE__ */ jsx("span", { className: "absolute top-full whitespace-nowrap text-label-sm text-[var(--color-on-surface-muted)]", children: m.label })
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
m.value
|
|
49
|
+
)),
|
|
17
50
|
Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsx(
|
|
18
51
|
RadixSlider.Thumb,
|
|
19
52
|
{
|
|
20
53
|
"aria-label": thumbLabels?.[i] ?? ariaLabel,
|
|
54
|
+
"aria-valuetext": formatValue && values[i] !== void 0 ? formatValue(values[i]) : void 0,
|
|
21
55
|
className: cn(
|
|
22
|
-
"nx-state-layer block h-5 w-5 rounded-full border-2 border-[var(--color-surface-container-lowest)] bg-[var(--color-primary)] text-[var(--color-primary)] shadow-elevation-1",
|
|
56
|
+
"nx-state-layer group/thumb relative block h-5 w-5 rounded-full border-2 border-[var(--color-surface-container-lowest)] bg-[var(--color-primary)] text-[var(--color-primary)] shadow-elevation-1",
|
|
23
57
|
"before:-inset-2.5 cursor-grab active:cursor-grabbing",
|
|
24
58
|
// Opaque disabled knob (text-disabled is translucent and would show the range through it).
|
|
25
59
|
"group-data-[disabled]:pointer-events-none group-data-[disabled]:bg-[color-mix(in_srgb,var(--color-on-surface)_38%,var(--color-surface))]"
|
|
60
|
+
),
|
|
61
|
+
children: showValue && values[i] !== void 0 && /* @__PURE__ */ jsx(
|
|
62
|
+
"span",
|
|
63
|
+
{
|
|
64
|
+
"aria-hidden": "true",
|
|
65
|
+
className: cn(
|
|
66
|
+
"pointer-events-none absolute bottom-full left-1/2 mb-2 -translate-x-1/2 whitespace-nowrap rounded-shape-tooltip bg-[var(--color-inverse-surface)] px-2 py-1 text-label-md text-[var(--color-inverse-on-surface)] shadow-elevation-1 transition-opacity",
|
|
67
|
+
showValue === "hover" && "opacity-0 group-hover/thumb:opacity-100 group-focus/thumb:opacity-100"
|
|
68
|
+
),
|
|
69
|
+
children: format(values[i])
|
|
70
|
+
}
|
|
26
71
|
)
|
|
27
72
|
},
|
|
28
73
|
i
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { Slider } from './Slider.js';
|
|
2
|
-
export type { SliderProps } from './Slider.js';
|
|
2
|
+
export type { SliderProps, SliderMark } from './Slider.js';
|
|
@@ -8,5 +8,11 @@ export declare const thumbVariants: (props?: ({
|
|
|
8
8
|
size?: "sm" | "md" | "lg" | null | undefined;
|
|
9
9
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
10
10
|
export interface SwitchProps extends RadixSwitch.SwitchProps, VariantProps<typeof switchVariants> {
|
|
11
|
+
/** Spinner in the thumb and `aria-busy`; clicks don't toggle while loading. Stays focusable. */
|
|
12
|
+
loading?: boolean;
|
|
13
|
+
/** Small icon inside the thumb while on. */
|
|
14
|
+
checkedIcon?: React.ReactNode;
|
|
15
|
+
/** Small icon inside the thumb while off. */
|
|
16
|
+
uncheckedIcon?: React.ReactNode;
|
|
11
17
|
}
|
|
12
18
|
export declare const Switch: React.ForwardRefExoticComponent<SwitchProps & React.RefAttributes<HTMLButtonElement>>;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import * as RadixSwitch from "@radix-ui/react-switch";
|
|
5
5
|
import { cn } from "../../utils/cn.js";
|
|
6
6
|
import { cva } from "class-variance-authority";
|
|
7
|
+
import { spinnerVariants } from "../Spinner/Spinner.js";
|
|
7
8
|
const switchVariants = cva(
|
|
8
9
|
// Off: tonal track with a 2px outline (>= 3:1, WCAG 1.4.11). On: primary.
|
|
9
10
|
// Focus = the global ring (it follows the capsule).
|
|
@@ -43,8 +44,34 @@ const thumbVariants = cva(
|
|
|
43
44
|
defaultVariants: { size: "md" }
|
|
44
45
|
}
|
|
45
46
|
);
|
|
47
|
+
const thumbSlot = "hidden h-full w-full items-center justify-center [&>svg]:h-2/3 [&>svg]:w-2/3";
|
|
46
48
|
const Switch = React.forwardRef(
|
|
47
|
-
({ className, size,
|
|
49
|
+
({ className, size, loading, checkedIcon, uncheckedIcon, onClick, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
50
|
+
RadixSwitch.Root,
|
|
51
|
+
{
|
|
52
|
+
ref,
|
|
53
|
+
className: cn(switchVariants({ size }), "nx-hit", loading && "cursor-progress", className),
|
|
54
|
+
"aria-busy": loading || void 0,
|
|
55
|
+
onClick: (e) => {
|
|
56
|
+
onClick?.(e);
|
|
57
|
+
if (loading) e.preventDefault();
|
|
58
|
+
},
|
|
59
|
+
...props,
|
|
60
|
+
children: /* @__PURE__ */ jsx(
|
|
61
|
+
RadixSwitch.Thumb,
|
|
62
|
+
{
|
|
63
|
+
className: cn(
|
|
64
|
+
thumbVariants({ size }),
|
|
65
|
+
(loading || checkedIcon || uncheckedIcon) && "group/thumb flex items-center justify-center data-[state=checked]:text-[var(--color-primary)] data-[state=unchecked]:text-[var(--color-surface-container-highest)]"
|
|
66
|
+
),
|
|
67
|
+
children: loading ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn(spinnerVariants({ size: "xs" }), "h-2/3 w-2/3 border-current border-t-transparent") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
68
|
+
checkedIcon && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn(thumbSlot, "group-data-[state=checked]/thumb:flex"), children: checkedIcon }),
|
|
69
|
+
uncheckedIcon && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn(thumbSlot, "group-data-[state=unchecked]/thumb:flex"), children: uncheckedIcon })
|
|
70
|
+
] })
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
)
|
|
48
75
|
);
|
|
49
76
|
Switch.displayName = "Switch";
|
|
50
77
|
export {
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import * as RadixTabs from '@radix-ui/react-tabs';
|
|
3
|
+
export interface TabsListProps extends RadixTabs.TabsListProps {
|
|
4
|
+
/** `line` (default): underline. `pill`: a raised thumb slides behind the active tab. */
|
|
5
|
+
variant?: 'line' | 'pill';
|
|
6
|
+
}
|
|
3
7
|
export declare const Tabs: {
|
|
4
8
|
Root: React.ForwardRefExoticComponent<RadixTabs.TabsProps & React.RefAttributes<HTMLDivElement>>;
|
|
5
|
-
List: React.ForwardRefExoticComponent<
|
|
9
|
+
List: React.ForwardRefExoticComponent<TabsListProps & React.RefAttributes<HTMLDivElement>>;
|
|
6
10
|
Trigger: React.ForwardRefExoticComponent<RadixTabs.TabsTriggerProps & React.RefAttributes<HTMLButtonElement>>;
|
|
7
11
|
Content: React.ForwardRefExoticComponent<RadixTabs.TabsContentProps & React.RefAttributes<HTMLDivElement>>;
|
|
8
12
|
};
|