@stll/ui 0.59.0 → 0.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -4
- package/dist/calendar/day-calendar.d.ts +14 -4
- package/dist/calendar/day-calendar.js +52 -20
- package/dist/calendar/day-calendar.logic.d.ts +3 -0
- package/dist/calendar/day-calendar.logic.js +4 -1
- package/dist/components/accordion.d.ts +17 -3
- package/dist/components/accordion.js +17 -3
- package/dist/components/button.d.ts +56 -5
- package/dist/components/button.js +97 -15
- package/dist/components/button.native-classes.d.ts +17 -0
- package/dist/components/button.native-classes.js +176 -0
- package/dist/components/button.native.d.ts +5 -2
- package/dist/components/button.native.js +17 -134
- package/dist/components/button.shared.d.ts +24 -2
- package/dist/components/button.shared.js +15 -1
- package/dist/components/combobox.d.ts +3 -1
- package/dist/components/combobox.js +3 -2
- package/dist/components/date-picker-popover.d.ts +2 -0
- package/dist/components/date-picker-popover.js +5 -3
- package/dist/components/details-grid.d.ts +21 -2
- package/dist/components/details-grid.js +75 -20
- package/dist/components/form.d.ts +9 -3
- package/dist/components/form.js +24 -3
- package/dist/components/input-control.d.ts +9 -0
- package/dist/components/input-control.js +10 -1
- package/dist/components/input.d.ts +7 -1
- package/dist/components/input.js +4 -4
- package/dist/components/label.d.ts +24 -2
- package/dist/components/label.js +21 -2
- package/dist/components/number-input.d.ts +9 -2
- package/dist/components/number-input.js +18 -6
- package/dist/components/textarea.d.ts +14 -1
- package/dist/components/textarea.js +11 -5
- package/dist/index.d.ts +10 -6
- package/dist/index.js +6 -3
- package/dist/kanban/index.d.ts +5 -2
- package/dist/kanban/index.js +4 -1
- package/dist/kanban/sortable-edge.d.ts +39 -0
- package/dist/kanban/sortable-edge.js +27 -0
- package/dist/kanban/sortable-interactions.d.ts +145 -0
- package/dist/kanban/sortable-interactions.js +503 -0
- package/dist/kanban/sortable-interactions.logic.d.ts +79 -0
- package/dist/kanban/sortable-interactions.logic.js +210 -0
- package/dist/kanban/subgroup-board.d.ts +27 -2
- package/dist/kanban/subgroup-board.js +26 -1
- package/dist/kanban/virtual-cell.d.ts +18 -1
- package/dist/kanban/virtual-cell.js +70 -17
- package/package.json +5 -1
|
@@ -3,7 +3,7 @@ import { cn } from "../lib/utils.js";
|
|
|
3
3
|
import { renderTooltipTrigger } from "./tooltip-trigger-helper.js";
|
|
4
4
|
import { BUTTON_DISPOSITION, blockDisabledActivation, blockDisabledKeyActivation, resolveButtonDisposition } from "../lib/button-disposition.js";
|
|
5
5
|
import { MENU_ROW_CLASS_NAME } from "../lib/menu-row.js";
|
|
6
|
-
import { BUTTON_VARIANT_TOKENS } from "./button.shared.js";
|
|
6
|
+
import { BUTTON_PLAIN_VARIANT, BUTTON_VARIANT_TOKENS } from "./button.shared.js";
|
|
7
7
|
import { Loader } from "./loader.js";
|
|
8
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
import { mergeProps } from "@base-ui/react/merge-props";
|
|
@@ -44,7 +44,9 @@ const BUTTON_SIZE_CLASS_NAMES = {
|
|
|
44
44
|
"icon-lg": "size-10 sm:size-9",
|
|
45
45
|
"icon-sm": "size-8 sm:size-7",
|
|
46
46
|
"icon-xl": "size-11 sm:size-10 [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5",
|
|
47
|
+
"icon-2xs": "size-5 rounded-md before:rounded-[calc(var(--radius-md)-1px)] sm:size-4 [&_svg:not([class*='size-'])]:size-3.5 sm:[&_svg:not([class*='size-'])]:size-3",
|
|
47
48
|
"icon-xs": "size-7 rounded-md before:rounded-[calc(var(--radius-md)-1px)] sm:size-6 not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-4 sm:not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-3.5",
|
|
49
|
+
inline: "inline h-auto rounded-sm border-0 p-0 text-start text-[length:inherit] leading-[inherit] font-normal whitespace-normal select-text before:rounded-[calc(var(--radius-sm)-1px)] sm:text-[length:inherit] [&_svg]:mx-0 [&_svg]:inline [&_svg]:align-[-0.125em] [&_svg:not(:last-child)]:me-1",
|
|
48
50
|
lg: "h-10 px-[calc(--spacing(3.5)-1px)] sm:h-9",
|
|
49
51
|
row: `${MENU_ROW_CLASS_NAME} [&_svg]:mx-0`,
|
|
50
52
|
sm: "h-8 gap-1.5 px-[calc(--spacing(2.5)-1px)] text-sm sm:h-7 sm:text-xs",
|
|
@@ -52,26 +54,68 @@ const BUTTON_SIZE_CLASS_NAMES = {
|
|
|
52
54
|
xs: "h-7 gap-1 rounded-md px-[calc(--spacing(2)-1px)] text-sm before:rounded-[calc(var(--radius-md)-1px)] sm:h-6 sm:text-xs [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5"
|
|
53
55
|
};
|
|
54
56
|
const BUTTON_VARIANT_CLASS_NAMES = {
|
|
55
|
-
default: `${tokens("default")} shadow-primary/24 [:hover
|
|
56
|
-
destructive: `${tokens("destructive")} shadow-destructive/8 [:hover
|
|
57
|
-
"destructive-outline": `${tokens("destructive-outline")} dark:bg-input/32 [:hover
|
|
58
|
-
"destructive-ghost": `${tokens("destructive-ghost")} data-pressed:bg-destructive/
|
|
59
|
-
ghost: `${tokens("ghost")} data-pressed:bg-accent [:hover
|
|
60
|
-
link: `${tokens("link")} underline-offset-4 [:hover
|
|
61
|
-
muted: `${tokens("muted")} data-pressed:bg-
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
default: `${tokens("default")} shadow-primary/24 data-pressed:bg-primary/80 [:hover:not([data-pressed])]:bg-primary/90 shadow-xs not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] [:active,[data-pressed]]:inset-shadow-[0_1px_--theme(--color-black/8%)] [:disabled,:active,[data-pressed]]:shadow-none`,
|
|
58
|
+
destructive: `${tokens("destructive")} shadow-destructive/8 data-pressed:border-destructive/56 data-pressed:bg-destructive/20 [:hover:not([data-pressed])]:border-destructive/40 [:hover:not([data-pressed])]:bg-destructive/14 active:bg-destructive/18 shadow-xs not-dark:bg-clip-padding not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] [:disabled,:active,[data-pressed]]:shadow-none`,
|
|
59
|
+
"destructive-outline": `${tokens("destructive-outline")} dark:bg-input/32 data-pressed:border-destructive/56 data-pressed:bg-destructive/8 [:hover:not([data-pressed])]:border-destructive/32 [:hover:not([data-pressed])]:bg-destructive/4 shadow-xs/5 not-dark:bg-clip-padding not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none`,
|
|
60
|
+
"destructive-ghost": `${tokens("destructive-ghost")} data-pressed:bg-destructive/14 data-pressed:inset-ring-destructive/32 [:hover:not([data-pressed])]:bg-destructive/10 data-pressed:inset-ring`,
|
|
61
|
+
ghost: `${tokens("ghost")} data-pressed:bg-accent data-pressed:inset-ring-input [:hover:not([data-pressed])]:bg-accent data-pressed:shadow-xs/5 data-pressed:inset-ring`,
|
|
62
|
+
link: `${tokens("link")} underline-offset-4 data-pressed:underline data-pressed:decoration-2 [:hover:not([data-pressed])]:underline`,
|
|
63
|
+
"muted-destructive": `${tokens("muted-destructive")} data-pressed:bg-destructive/14 data-pressed:text-destructive data-pressed:inset-ring-destructive/32 [:hover:not([data-pressed])]:bg-destructive/10 [:hover:not([data-pressed])]:text-destructive data-pressed:inset-ring`,
|
|
64
|
+
muted: `${tokens("muted")} data-pressed:bg-accent data-pressed:text-foreground data-pressed:inset-ring-input [:hover:not([data-pressed])]:bg-accent [:hover:not([data-pressed])]:text-foreground data-pressed:shadow-xs/5 data-pressed:inset-ring`,
|
|
65
|
+
outline: `${tokens("outline")} dark:bg-input/32 data-pressed:border-primary/48 data-pressed:bg-accent dark:data-pressed:bg-input/64 [:hover:not([data-pressed])]:bg-accent/50 dark:[:hover:not([data-pressed])]:bg-input/64 shadow-xs/5 not-dark:bg-clip-padding not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none`,
|
|
66
|
+
secondary: `${tokens("secondary")} data-pressed:bg-background data-pressed:inset-ring-input active:bg-secondary/80 [:hover:not([data-pressed])]:bg-secondary/90 data-pressed:shadow-xs/5 data-pressed:inset-ring`
|
|
64
67
|
};
|
|
65
68
|
const buttonVariants = cva("focus-visible:ring-ring focus-visible:ring-offset-background relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-lg border text-base font-medium whitespace-nowrap outline-none select-none before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] focus-visible:ring-2 focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-64 sm:text-sm pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 [&_svg]:pointer-events-none [&_svg]:-mx-0.5 [&_svg]:shrink-0 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4", {
|
|
69
|
+
compoundVariants: [{
|
|
70
|
+
className: "[:hover:not([data-pressed])]:bg-transparent",
|
|
71
|
+
size: "inline",
|
|
72
|
+
variant: [
|
|
73
|
+
"destructive-ghost",
|
|
74
|
+
"ghost",
|
|
75
|
+
"link",
|
|
76
|
+
"muted",
|
|
77
|
+
"muted-destructive"
|
|
78
|
+
]
|
|
79
|
+
}, {
|
|
80
|
+
className: "underline",
|
|
81
|
+
size: "inline",
|
|
82
|
+
variant: "link"
|
|
83
|
+
}],
|
|
66
84
|
defaultVariants: {
|
|
67
85
|
size: BUTTON_DEFAULT_SIZE,
|
|
68
86
|
variant: "default"
|
|
69
87
|
},
|
|
70
88
|
variants: {
|
|
71
89
|
size: BUTTON_SIZE_CLASS_NAMES,
|
|
90
|
+
layout: { wrap: "h-auto min-h-11 items-start justify-start py-2 text-start whitespace-normal sm:h-auto" },
|
|
72
91
|
variant: BUTTON_VARIANT_CLASS_NAMES
|
|
73
92
|
}
|
|
74
93
|
});
|
|
94
|
+
/**
|
|
95
|
+
* `plain` (./button.shared.ts) on the web. It bypasses `buttonVariants` and
|
|
96
|
+
* its box, border, type and press motion instead of undoing them class by
|
|
97
|
+
* class, and keeps the disabled disposition, the tooltip and the focus ring.
|
|
98
|
+
* It lays out like a native button (`inline-block`, content flowing from the
|
|
99
|
+
* start), so a card's children stack as they would in a `div`; `className`
|
|
100
|
+
* places it (`w-full`, `block`). `shape` gives it the outline its focus ring
|
|
101
|
+
* follows. Like a native button it does not dim when disabled: the content
|
|
102
|
+
* says it is unavailable.
|
|
103
|
+
*
|
|
104
|
+
* Selected, it draws the inset edge the other variants draw, as an outline
|
|
105
|
+
* inside the box: an outline paints above the content, so a card's own
|
|
106
|
+
* surface cannot cover it the way it would cover an inset shadow.
|
|
107
|
+
*/
|
|
108
|
+
const BUTTON_PLAIN_CLASS = "focus-visible:ring-ring focus-visible:ring-offset-background data-pressed:outline-primary/48 relative inline-block cursor-pointer text-start outline-none focus-visible:ring-2 focus-visible:ring-offset-1 disabled:cursor-default data-pressed:outline-2 data-pressed:-outline-offset-2 data-pressed:outline-solid";
|
|
109
|
+
/**
|
|
110
|
+
* The outline a plain button's focus ring follows, matching its content:
|
|
111
|
+
* `box` for a card, row or block of text (the container radius), `round` for
|
|
112
|
+
* an avatar, a dot or a pill.
|
|
113
|
+
*/
|
|
114
|
+
const BUTTON_PLAIN_SHAPE_CLASS = {
|
|
115
|
+
box: "rounded-lg",
|
|
116
|
+
round: "rounded-full"
|
|
117
|
+
};
|
|
118
|
+
const BUTTON_FOCUS_RING_INSIDE_CLASS = "focus-visible:ring-inset focus-visible:ring-offset-0";
|
|
75
119
|
/** What a button's box holds: a glyph alone, or a visible label. */
|
|
76
120
|
const BUTTON_CONTENT = {
|
|
77
121
|
icon: "icon",
|
|
@@ -95,7 +139,9 @@ const BUTTON_SIZE_CONTENT = {
|
|
|
95
139
|
"icon-lg": BUTTON_CONTENT.icon,
|
|
96
140
|
"icon-sm": BUTTON_CONTENT.icon,
|
|
97
141
|
"icon-xl": BUTTON_CONTENT.icon,
|
|
142
|
+
"icon-2xs": BUTTON_CONTENT.icon,
|
|
98
143
|
"icon-xs": BUTTON_CONTENT.icon,
|
|
144
|
+
inline: BUTTON_CONTENT.text,
|
|
99
145
|
lg: BUTTON_CONTENT.text,
|
|
100
146
|
row: BUTTON_CONTENT.text,
|
|
101
147
|
sm: BUTTON_CONTENT.text,
|
|
@@ -110,7 +156,8 @@ const implicitTooltip = (size, accessibleName) => {
|
|
|
110
156
|
default: return panic(`Unhandled button content: ${String(content)}`);
|
|
111
157
|
}
|
|
112
158
|
};
|
|
113
|
-
function Button({ className, variant, size, render, loading, icon, children, disabled, tooltip, "data-accessible-disabled": accessibleDisabled, ...props }) {
|
|
159
|
+
function Button({ className, variant: variantProp, size, render, layout, loading, icon, children, disabled, tooltip, selected, focusRing = "outside", shape = "box", "data-accessible-disabled": accessibleDisabled, ...props }) {
|
|
160
|
+
const variant = variantProp ?? (size === "inline" ? "link" : void 0);
|
|
114
161
|
const typeValue = render ? void 0 : "button";
|
|
115
162
|
const tooltipContent = tooltip ?? implicitTooltip(size, props["aria-label"] ?? props.title);
|
|
116
163
|
const disposition = resolveButtonDisposition({
|
|
@@ -128,12 +175,14 @@ function Button({ className, variant, size, render, loading, icon, children, dis
|
|
|
128
175
|
size: "sm",
|
|
129
176
|
variant: "decorative"
|
|
130
177
|
}) : icon, children] }),
|
|
131
|
-
className: cn(disposition === BUTTON_DISPOSITION.enabled && !isActivationPreservingDisabled && size !== "row" && variant !== "link" && buttonPressClass, buttonVariants({
|
|
178
|
+
className: cn(variant === "plain" ? cn(BUTTON_PLAIN_CLASS, BUTTON_PLAIN_SHAPE_CLASS[shape], className) : cn(disposition === BUTTON_DISPOSITION.enabled && !isActivationPreservingDisabled && size !== "row" && size !== "inline" && variant !== "link" && buttonPressClass, buttonVariants({
|
|
132
179
|
className,
|
|
133
180
|
size,
|
|
134
|
-
variant
|
|
135
|
-
|
|
181
|
+
variant,
|
|
182
|
+
layout
|
|
183
|
+
})), focusRing === "inside" && BUTTON_FOCUS_RING_INSIDE_CLASS, (isAccessibleDisabled || isActivationPreservingDisabled) && "cursor-not-allowed opacity-64"),
|
|
136
184
|
"data-slot": "button",
|
|
185
|
+
"data-pressed": selected === true ? "" : void 0,
|
|
137
186
|
"aria-busy": loading || void 0,
|
|
138
187
|
...isAccessibleDisabled || isActivationPreservingDisabled ? {
|
|
139
188
|
"aria-disabled": true,
|
|
@@ -159,5 +208,38 @@ function Button({ className, variant, size, render, loading, icon, children, dis
|
|
|
159
208
|
trigger: button
|
|
160
209
|
});
|
|
161
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* The run is the visible target: underlined like a link, ringed while its
|
|
213
|
+
* button has keyboard focus (per line, so a wrapped run rings each line).
|
|
214
|
+
*/
|
|
215
|
+
const BUTTON_TEXT_RUN_CLASS = "has-focus-visible:ring-ring cursor-pointer rounded-xs underline underline-offset-2 box-decoration-clone select-text hover:decoration-2 has-focus-visible:ring-2 has-disabled:cursor-not-allowed has-disabled:opacity-64";
|
|
216
|
+
const BUTTON_TEXT_RUN_TARGET_CLASS = "absolute size-px overflow-hidden whitespace-nowrap [clip-path:inset(50%)]";
|
|
217
|
+
/**
|
|
218
|
+
* A long run of prose that acts as one button: a citation or a
|
|
219
|
+
* cross-reference in reader text. A <button> is an atomic box even at
|
|
220
|
+
* `display: inline`, so its label cannot break across the paragraph's lines;
|
|
221
|
+
* here the text sits in a native <label>, an inline box that wraps and copies
|
|
222
|
+
* with the prose, and the label forwards a press anywhere on the run to the
|
|
223
|
+
* <button> it holds, which takes its accessible name from the label. Use
|
|
224
|
+
* `Button size="inline"` for a short action.
|
|
225
|
+
*/
|
|
226
|
+
const ButtonTextRun = ({ children, className, disabled, ...props }) => /* @__PURE__ */ jsxs("label", {
|
|
227
|
+
className: cn(BUTTON_TEXT_RUN_CLASS, className),
|
|
228
|
+
"data-slot": "button-text-run",
|
|
229
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
230
|
+
...props,
|
|
231
|
+
className: BUTTON_TEXT_RUN_TARGET_CLASS,
|
|
232
|
+
disabled,
|
|
233
|
+
focusRing: "outside",
|
|
234
|
+
icon: void 0,
|
|
235
|
+
loading: false,
|
|
236
|
+
render: void 0,
|
|
237
|
+
selected: false,
|
|
238
|
+
shape: "box",
|
|
239
|
+
size: void 0,
|
|
240
|
+
tooltip: void 0,
|
|
241
|
+
variant: BUTTON_PLAIN_VARIANT
|
|
242
|
+
}), children]
|
|
243
|
+
});
|
|
162
244
|
//#endregion
|
|
163
|
-
export { BUTTON_CONTENT, BUTTON_SIZE_CONTENT, Button, buttonAccessibleDisabledClass, buttonVariants };
|
|
245
|
+
export { BUTTON_CONTENT, BUTTON_SIZE_CONTENT, Button, ButtonTextRun, buttonAccessibleDisabledClass, buttonVariants };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ButtonSize, ButtonVariant } from "./button.shared.js";
|
|
2
|
+
//#region src/components/button.native-classes.d.ts
|
|
3
|
+
type NativeButtonClassNamesOptions = {
|
|
4
|
+
className?: string | undefined;
|
|
5
|
+
inactive: boolean;
|
|
6
|
+
selected: boolean;
|
|
7
|
+
size?: ButtonSize | null | undefined;
|
|
8
|
+
variant?: ButtonVariant | null | undefined;
|
|
9
|
+
};
|
|
10
|
+
/** The Pressable's and the label's classes, and the touch slop, for a button. */
|
|
11
|
+
export declare const nativeButtonClassNames: ({ className, inactive, selected, size, variant }: NativeButtonClassNamesOptions) => {
|
|
12
|
+
label: string;
|
|
13
|
+
pressable: string;
|
|
14
|
+
slop: 0 | 6 | 8 | 12;
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { BUTTON_PLAIN_VARIANT, BUTTON_VARIANT_TOKENS } from "./button.shared.js";
|
|
2
|
+
//#region src/components/button.native-classes.ts
|
|
3
|
+
/** A control's box: centred content inside the variant's border. */
|
|
4
|
+
const CONTROL = "items-center justify-center border";
|
|
5
|
+
/**
|
|
6
|
+
* Every shared size in a phone's metrics. A text size grows to the platform's
|
|
7
|
+
* 44-point touch target where the web relies on a coarse-pointer hit area; a
|
|
8
|
+
* smaller control keeps its look and reaches the target through `slop`.
|
|
9
|
+
*/
|
|
10
|
+
const SIZES = {
|
|
11
|
+
chip: {
|
|
12
|
+
box: `${CONTROL} min-h-7 gap-1.5 rounded-full px-2.5`,
|
|
13
|
+
label: "text-xs",
|
|
14
|
+
slop: 8
|
|
15
|
+
},
|
|
16
|
+
default: {
|
|
17
|
+
box: `${CONTROL} min-h-11 gap-2 rounded-lg px-4`,
|
|
18
|
+
label: "",
|
|
19
|
+
slop: 0
|
|
20
|
+
},
|
|
21
|
+
icon: {
|
|
22
|
+
box: `${CONTROL} size-11 rounded-lg`,
|
|
23
|
+
label: "",
|
|
24
|
+
slop: 0
|
|
25
|
+
},
|
|
26
|
+
"icon-lg": {
|
|
27
|
+
box: `${CONTROL} size-12 rounded-lg`,
|
|
28
|
+
label: "",
|
|
29
|
+
slop: 0
|
|
30
|
+
},
|
|
31
|
+
"icon-sm": {
|
|
32
|
+
box: `${CONTROL} size-8 rounded-lg`,
|
|
33
|
+
label: "",
|
|
34
|
+
slop: 6
|
|
35
|
+
},
|
|
36
|
+
"icon-xl": {
|
|
37
|
+
box: `${CONTROL} size-11 rounded-full`,
|
|
38
|
+
label: "",
|
|
39
|
+
slop: 0
|
|
40
|
+
},
|
|
41
|
+
"icon-2xs": {
|
|
42
|
+
box: `${CONTROL} size-5 rounded-md`,
|
|
43
|
+
label: "",
|
|
44
|
+
slop: 12
|
|
45
|
+
},
|
|
46
|
+
"icon-xs": {
|
|
47
|
+
box: `${CONTROL} size-7 rounded-md`,
|
|
48
|
+
label: "",
|
|
49
|
+
slop: 8
|
|
50
|
+
},
|
|
51
|
+
inline: {
|
|
52
|
+
box: "items-center gap-1",
|
|
53
|
+
label: "",
|
|
54
|
+
slop: 8
|
|
55
|
+
},
|
|
56
|
+
lg: {
|
|
57
|
+
box: `${CONTROL} min-h-12 gap-2 rounded-lg px-5`,
|
|
58
|
+
label: "",
|
|
59
|
+
slop: 0
|
|
60
|
+
},
|
|
61
|
+
row: {
|
|
62
|
+
box: "min-h-11 w-full items-start gap-3 py-3",
|
|
63
|
+
label: "",
|
|
64
|
+
slop: 0
|
|
65
|
+
},
|
|
66
|
+
sm: {
|
|
67
|
+
box: `${CONTROL} min-h-9 gap-1.5 rounded-lg px-3`,
|
|
68
|
+
label: "text-sm",
|
|
69
|
+
slop: 0
|
|
70
|
+
},
|
|
71
|
+
xl: {
|
|
72
|
+
box: `${CONTROL} min-h-14 gap-2 rounded-lg px-6`,
|
|
73
|
+
label: "text-lg",
|
|
74
|
+
slop: 0
|
|
75
|
+
},
|
|
76
|
+
xs: {
|
|
77
|
+
box: `${CONTROL} min-h-7 gap-1 rounded-md px-2`,
|
|
78
|
+
label: "text-sm",
|
|
79
|
+
slop: 8
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* A phone has no hover: a pressed button answers with the fill the web shows
|
|
84
|
+
* on hover, or dims when it already has one. `selected` is the pressed state
|
|
85
|
+
* held (a row open beside the list, a chosen option).
|
|
86
|
+
*/
|
|
87
|
+
const PRESS = {
|
|
88
|
+
default: {
|
|
89
|
+
pressed: "active:opacity-80",
|
|
90
|
+
selected: ""
|
|
91
|
+
},
|
|
92
|
+
destructive: {
|
|
93
|
+
pressed: "active:bg-destructive/18",
|
|
94
|
+
selected: ""
|
|
95
|
+
},
|
|
96
|
+
"destructive-outline": {
|
|
97
|
+
pressed: "active:bg-destructive/4",
|
|
98
|
+
selected: ""
|
|
99
|
+
},
|
|
100
|
+
"destructive-ghost": {
|
|
101
|
+
pressed: "active:bg-destructive/10",
|
|
102
|
+
selected: "bg-destructive/10"
|
|
103
|
+
},
|
|
104
|
+
ghost: {
|
|
105
|
+
pressed: "active:bg-accent",
|
|
106
|
+
selected: "bg-accent"
|
|
107
|
+
},
|
|
108
|
+
link: {
|
|
109
|
+
pressed: "active:opacity-60",
|
|
110
|
+
selected: ""
|
|
111
|
+
},
|
|
112
|
+
muted: {
|
|
113
|
+
pressed: "active:bg-accent",
|
|
114
|
+
selected: "bg-accent"
|
|
115
|
+
},
|
|
116
|
+
"muted-destructive": {
|
|
117
|
+
pressed: "active:bg-destructive/10",
|
|
118
|
+
selected: "bg-destructive/10"
|
|
119
|
+
},
|
|
120
|
+
outline: {
|
|
121
|
+
pressed: "active:bg-accent",
|
|
122
|
+
selected: "bg-accent"
|
|
123
|
+
},
|
|
124
|
+
secondary: {
|
|
125
|
+
pressed: "active:bg-secondary/80",
|
|
126
|
+
selected: ""
|
|
127
|
+
},
|
|
128
|
+
plain: {
|
|
129
|
+
pressed: "active:opacity-80",
|
|
130
|
+
selected: "border-primary"
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* `plain` has no fill, no label colour and no size; like the web's, it does
|
|
135
|
+
* not dim when disabled, because its content says it is unavailable. It keeps
|
|
136
|
+
* a transparent border that only `selected` colours, the phone's stand-in for
|
|
137
|
+
* the web's inset ring.
|
|
138
|
+
*/
|
|
139
|
+
const PLAIN = {
|
|
140
|
+
shape: {
|
|
141
|
+
box: "rounded-lg border-2 border-transparent",
|
|
142
|
+
label: "",
|
|
143
|
+
slop: 0
|
|
144
|
+
},
|
|
145
|
+
tokens: {
|
|
146
|
+
surface: "",
|
|
147
|
+
label: ""
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
/** The Pressable's and the label's classes, and the touch slop, for a button. */
|
|
151
|
+
const nativeButtonClassNames = ({ className, inactive, selected, size, variant }) => {
|
|
152
|
+
const plain = variant === BUTTON_PLAIN_VARIANT;
|
|
153
|
+
const shape = plain ? PLAIN.shape : SIZES[size ?? "default"];
|
|
154
|
+
const tokens = plain ? PLAIN.tokens : BUTTON_VARIANT_TOKENS[variant ?? "default"];
|
|
155
|
+
const press = PRESS[variant ?? "default"];
|
|
156
|
+
const dimmed = plain ? "" : "opacity-64";
|
|
157
|
+
const join = (names) => names.filter((name) => name !== "").join(" ");
|
|
158
|
+
return {
|
|
159
|
+
label: join([
|
|
160
|
+
tokens.label,
|
|
161
|
+
variant === "link" ? "text-foreground underline" : "",
|
|
162
|
+
shape.label
|
|
163
|
+
]),
|
|
164
|
+
pressable: join([
|
|
165
|
+
plain ? "" : "flex-row",
|
|
166
|
+
tokens.surface,
|
|
167
|
+
shape.box,
|
|
168
|
+
inactive ? dimmed : press.pressed,
|
|
169
|
+
selected ? press.selected : "",
|
|
170
|
+
className ?? ""
|
|
171
|
+
]),
|
|
172
|
+
slop: shape.slop
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
//#endregion
|
|
176
|
+
export { nativeButtonClassNames };
|
|
@@ -8,8 +8,11 @@ type ButtonProps = Omit<PressableProps, "children" | "disabled"> & ButtonContrac
|
|
|
8
8
|
* The web's `Button` (./button.tsx) on a phone: the same variants and sizes,
|
|
9
9
|
* read from ./button.shared.ts, on a Pressable. A string child is set as the
|
|
10
10
|
* button's label in its variant's colour; any other child is laid out as
|
|
11
|
-
* given, which is how a row holds a glyph beside two lines of text.
|
|
11
|
+
* given, which is how a row holds a glyph beside two lines of text. A
|
|
12
|
+
* `plain` button paints no fill or label colour and ignores `size`: its
|
|
13
|
+
* children are its look, stacked as in a View, inside a border that shows
|
|
14
|
+
* only when it is selected. Its classes come from ./button.native-classes.ts.
|
|
12
15
|
*/
|
|
13
|
-
export declare const Button: ({ accessibilityState, children, className, disabled, hitSlop, icon, loading, size, variant, ...props }: ButtonProps) => import("react").JSX.Element;
|
|
16
|
+
export declare const Button: ({ accessibilityState, children, className, disabled, hitSlop, icon, loading, selected: selectedProp, size, variant: variantProp, ...props }: ButtonProps) => import("react").JSX.Element;
|
|
14
17
|
//#endregion
|
|
15
18
|
export {};
|
|
@@ -1,138 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { nativeButtonClassNames } from "./button.native-classes.js";
|
|
2
2
|
import { Loader } from "./loader.native.js";
|
|
3
3
|
import { Text as Text$1 } from "./text.native.js";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
import { Pressable } from "react-native";
|
|
6
6
|
//#region src/components/button.native.tsx
|
|
7
|
-
/** A control's box: centred content inside the variant's border. */
|
|
8
|
-
const CONTROL = "items-center justify-center border";
|
|
9
|
-
/**
|
|
10
|
-
* Every shared size in a phone's metrics. A text size grows to the platform's
|
|
11
|
-
* 44-point touch target where the web relies on a coarse-pointer hit area; a
|
|
12
|
-
* smaller control keeps its look and reaches the target through `slop`.
|
|
13
|
-
*/
|
|
14
|
-
const SIZES = {
|
|
15
|
-
chip: {
|
|
16
|
-
box: `${CONTROL} min-h-7 gap-1.5 rounded-full px-2.5`,
|
|
17
|
-
label: "text-xs",
|
|
18
|
-
slop: 8
|
|
19
|
-
},
|
|
20
|
-
default: {
|
|
21
|
-
box: `${CONTROL} min-h-11 gap-2 rounded-lg px-4`,
|
|
22
|
-
label: "",
|
|
23
|
-
slop: 0
|
|
24
|
-
},
|
|
25
|
-
icon: {
|
|
26
|
-
box: `${CONTROL} size-11 rounded-lg`,
|
|
27
|
-
label: "",
|
|
28
|
-
slop: 0
|
|
29
|
-
},
|
|
30
|
-
"icon-lg": {
|
|
31
|
-
box: `${CONTROL} size-12 rounded-lg`,
|
|
32
|
-
label: "",
|
|
33
|
-
slop: 0
|
|
34
|
-
},
|
|
35
|
-
"icon-sm": {
|
|
36
|
-
box: `${CONTROL} size-8 rounded-lg`,
|
|
37
|
-
label: "",
|
|
38
|
-
slop: 6
|
|
39
|
-
},
|
|
40
|
-
"icon-xl": {
|
|
41
|
-
box: `${CONTROL} size-11 rounded-full`,
|
|
42
|
-
label: "",
|
|
43
|
-
slop: 0
|
|
44
|
-
},
|
|
45
|
-
"icon-xs": {
|
|
46
|
-
box: `${CONTROL} size-7 rounded-md`,
|
|
47
|
-
label: "",
|
|
48
|
-
slop: 8
|
|
49
|
-
},
|
|
50
|
-
lg: {
|
|
51
|
-
box: `${CONTROL} min-h-12 gap-2 rounded-lg px-5`,
|
|
52
|
-
label: "",
|
|
53
|
-
slop: 0
|
|
54
|
-
},
|
|
55
|
-
row: {
|
|
56
|
-
box: "min-h-11 w-full items-start gap-3 py-3",
|
|
57
|
-
label: "",
|
|
58
|
-
slop: 0
|
|
59
|
-
},
|
|
60
|
-
sm: {
|
|
61
|
-
box: `${CONTROL} min-h-9 gap-1.5 rounded-lg px-3`,
|
|
62
|
-
label: "text-sm",
|
|
63
|
-
slop: 0
|
|
64
|
-
},
|
|
65
|
-
xl: {
|
|
66
|
-
box: `${CONTROL} min-h-14 gap-2 rounded-lg px-6`,
|
|
67
|
-
label: "text-lg",
|
|
68
|
-
slop: 0
|
|
69
|
-
},
|
|
70
|
-
xs: {
|
|
71
|
-
box: `${CONTROL} min-h-7 gap-1 rounded-md px-2`,
|
|
72
|
-
label: "text-sm",
|
|
73
|
-
slop: 8
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
/**
|
|
77
|
-
* A phone has no hover: a pressed button answers with the fill the web shows
|
|
78
|
-
* on hover, or dims when it already has one. `selected` is the pressed state
|
|
79
|
-
* held (a row open beside the list, a chosen option).
|
|
80
|
-
*/
|
|
81
|
-
const PRESS = {
|
|
82
|
-
default: {
|
|
83
|
-
pressed: "active:opacity-80",
|
|
84
|
-
selected: ""
|
|
85
|
-
},
|
|
86
|
-
destructive: {
|
|
87
|
-
pressed: "active:bg-destructive/18",
|
|
88
|
-
selected: ""
|
|
89
|
-
},
|
|
90
|
-
"destructive-outline": {
|
|
91
|
-
pressed: "active:bg-destructive/4",
|
|
92
|
-
selected: ""
|
|
93
|
-
},
|
|
94
|
-
"destructive-ghost": {
|
|
95
|
-
pressed: "active:bg-destructive/10",
|
|
96
|
-
selected: "bg-destructive/10"
|
|
97
|
-
},
|
|
98
|
-
ghost: {
|
|
99
|
-
pressed: "active:bg-accent",
|
|
100
|
-
selected: "bg-accent"
|
|
101
|
-
},
|
|
102
|
-
link: {
|
|
103
|
-
pressed: "active:opacity-60",
|
|
104
|
-
selected: ""
|
|
105
|
-
},
|
|
106
|
-
muted: {
|
|
107
|
-
pressed: "active:bg-accent",
|
|
108
|
-
selected: "bg-accent"
|
|
109
|
-
},
|
|
110
|
-
outline: {
|
|
111
|
-
pressed: "active:bg-accent",
|
|
112
|
-
selected: "bg-accent"
|
|
113
|
-
},
|
|
114
|
-
secondary: {
|
|
115
|
-
pressed: "active:bg-secondary/80",
|
|
116
|
-
selected: ""
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
7
|
/**
|
|
120
8
|
* The web's `Button` (./button.tsx) on a phone: the same variants and sizes,
|
|
121
9
|
* read from ./button.shared.ts, on a Pressable. A string child is set as the
|
|
122
10
|
* button's label in its variant's colour; any other child is laid out as
|
|
123
|
-
* given, which is how a row holds a glyph beside two lines of text.
|
|
11
|
+
* given, which is how a row holds a glyph beside two lines of text. A
|
|
12
|
+
* `plain` button paints no fill or label colour and ignores `size`: its
|
|
13
|
+
* children are its look, stacked as in a View, inside a border that shows
|
|
14
|
+
* only when it is selected. Its classes come from ./button.native-classes.ts.
|
|
124
15
|
*/
|
|
125
|
-
const Button = ({ accessibilityState, children, className, disabled = false, hitSlop, icon, loading = false, size, variant, ...props }) => {
|
|
126
|
-
const
|
|
127
|
-
const tokens = BUTTON_VARIANT_TOKENS[variant ?? "default"];
|
|
128
|
-
const press = PRESS[variant ?? "default"];
|
|
16
|
+
const Button = ({ accessibilityState, children, className, disabled = false, hitSlop, icon, loading = false, selected: selectedProp, size, variant: variantProp, ...props }) => {
|
|
17
|
+
const variant = variantProp ?? (size === "inline" ? "link" : void 0);
|
|
129
18
|
const inactive = disabled || loading;
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
19
|
+
const look = nativeButtonClassNames({
|
|
20
|
+
className,
|
|
21
|
+
inactive,
|
|
22
|
+
selected: selectedProp === true || accessibilityState?.selected === true,
|
|
23
|
+
size,
|
|
24
|
+
variant
|
|
25
|
+
});
|
|
136
26
|
return /* @__PURE__ */ jsxs(Pressable, {
|
|
137
27
|
accessibilityRole: "button",
|
|
138
28
|
accessibilityState: {
|
|
@@ -140,22 +30,15 @@ const Button = ({ accessibilityState, children, className, disabled = false, hit
|
|
|
140
30
|
busy: loading,
|
|
141
31
|
disabled: inactive
|
|
142
32
|
},
|
|
143
|
-
className:
|
|
144
|
-
"flex-row",
|
|
145
|
-
tokens.surface,
|
|
146
|
-
shape.box,
|
|
147
|
-
inactive ? "opacity-64" : press.pressed,
|
|
148
|
-
selected ? press.selected : "",
|
|
149
|
-
className ?? ""
|
|
150
|
-
].filter((name) => name !== "").join(" "),
|
|
33
|
+
className: look.pressable,
|
|
151
34
|
disabled: inactive,
|
|
152
|
-
hitSlop: hitSlop ?? (
|
|
35
|
+
hitSlop: hitSlop ?? (look.slop === 0 ? void 0 : look.slop),
|
|
153
36
|
...props,
|
|
154
37
|
children: [loading ? /* @__PURE__ */ jsx(Loader, {
|
|
155
38
|
size: "sm",
|
|
156
39
|
variant: "decorative"
|
|
157
40
|
}) : icon, typeof children === "string" || typeof children === "number" ? /* @__PURE__ */ jsx(Text$1, {
|
|
158
|
-
className: label,
|
|
41
|
+
className: look.label,
|
|
159
42
|
variant: "button",
|
|
160
43
|
children
|
|
161
44
|
}) : children]
|
|
@@ -34,6 +34,10 @@ export declare const BUTTON_VARIANT_TOKENS: {
|
|
|
34
34
|
readonly surface: "border-transparent";
|
|
35
35
|
readonly label: "text-muted-foreground";
|
|
36
36
|
};
|
|
37
|
+
readonly "muted-destructive": {
|
|
38
|
+
readonly surface: "border-transparent";
|
|
39
|
+
readonly label: "text-muted-foreground";
|
|
40
|
+
};
|
|
37
41
|
readonly outline: {
|
|
38
42
|
readonly surface: "border-input bg-popover";
|
|
39
43
|
readonly label: "text-foreground";
|
|
@@ -43,9 +47,19 @@ export declare const BUTTON_VARIANT_TOKENS: {
|
|
|
43
47
|
readonly label: "text-secondary-foreground";
|
|
44
48
|
};
|
|
45
49
|
};
|
|
46
|
-
|
|
50
|
+
/** A variant painted from its shared tokens above. */
|
|
51
|
+
export type ButtonTokenVariant = keyof typeof BUTTON_VARIANT_TOKENS;
|
|
52
|
+
/**
|
|
53
|
+
* `plain`: a click target whose look is its content (a card, a thumbnail, a
|
|
54
|
+
* cell, a passage). It has no tokens: neither renderer paints a box, border,
|
|
55
|
+
* type or press motion for it, and each keeps everything else its button
|
|
56
|
+
* owns (the disabled disposition, and on the web the tooltip and the focus
|
|
57
|
+
* ring). A `size` does not apply to it.
|
|
58
|
+
*/
|
|
59
|
+
export declare const BUTTON_PLAIN_VARIANT = "plain";
|
|
60
|
+
export type ButtonVariant = ButtonTokenVariant | typeof BUTTON_PLAIN_VARIANT;
|
|
47
61
|
/** Every size a button comes in, on every platform. */
|
|
48
|
-
export declare const BUTTON_SIZES: readonly ["chip", "default", "icon", "icon-lg", "icon-sm", "icon-xl", "icon-xs", "lg", "row", "sm", "xl", "xs"];
|
|
62
|
+
export declare const BUTTON_SIZES: readonly ["chip", "default", "icon", "icon-lg", "icon-sm", "icon-xl", "icon-2xs", "icon-xs", "inline", "lg", "row", "sm", "xl", "xs"];
|
|
49
63
|
export type ButtonSize = (typeof BUTTON_SIZES)[number];
|
|
50
64
|
/**
|
|
51
65
|
* The props every button renderer accepts, with one meaning. A renderer adds
|
|
@@ -60,6 +74,14 @@ export type ButtonContract = {
|
|
|
60
74
|
disabled?: boolean;
|
|
61
75
|
/** A glyph before the label. */
|
|
62
76
|
icon?: ReactNode;
|
|
77
|
+
/**
|
|
78
|
+
* The current choice in a set (a picked option, the active filter tab, the
|
|
79
|
+
* open item of a list): shows the variant's selected treatment, which is
|
|
80
|
+
* distinct from its hover or press. The accessibility state stays the
|
|
81
|
+
* caller's, because which one is right depends on the widget the button is
|
|
82
|
+
* part of.
|
|
83
|
+
*/
|
|
84
|
+
selected?: boolean | undefined;
|
|
63
85
|
children?: ReactNode;
|
|
64
86
|
};
|
|
65
87
|
//#endregion
|
|
@@ -33,6 +33,10 @@ const BUTTON_VARIANT_TOKENS = {
|
|
|
33
33
|
surface: "border-transparent",
|
|
34
34
|
label: "text-muted-foreground"
|
|
35
35
|
},
|
|
36
|
+
"muted-destructive": {
|
|
37
|
+
surface: "border-transparent",
|
|
38
|
+
label: "text-muted-foreground"
|
|
39
|
+
},
|
|
36
40
|
outline: {
|
|
37
41
|
surface: "border-input bg-popover",
|
|
38
42
|
label: "text-foreground"
|
|
@@ -42,6 +46,14 @@ const BUTTON_VARIANT_TOKENS = {
|
|
|
42
46
|
label: "text-secondary-foreground"
|
|
43
47
|
}
|
|
44
48
|
};
|
|
49
|
+
/**
|
|
50
|
+
* `plain`: a click target whose look is its content (a card, a thumbnail, a
|
|
51
|
+
* cell, a passage). It has no tokens: neither renderer paints a box, border,
|
|
52
|
+
* type or press motion for it, and each keeps everything else its button
|
|
53
|
+
* owns (the disabled disposition, and on the web the tooltip and the focus
|
|
54
|
+
* ring). A `size` does not apply to it.
|
|
55
|
+
*/
|
|
56
|
+
const BUTTON_PLAIN_VARIANT = "plain";
|
|
45
57
|
/** Every size a button comes in, on every platform. */
|
|
46
58
|
const BUTTON_SIZES = [
|
|
47
59
|
"chip",
|
|
@@ -50,7 +62,9 @@ const BUTTON_SIZES = [
|
|
|
50
62
|
"icon-lg",
|
|
51
63
|
"icon-sm",
|
|
52
64
|
"icon-xl",
|
|
65
|
+
"icon-2xs",
|
|
53
66
|
"icon-xs",
|
|
67
|
+
"inline",
|
|
54
68
|
"lg",
|
|
55
69
|
"row",
|
|
56
70
|
"sm",
|
|
@@ -58,4 +72,4 @@ const BUTTON_SIZES = [
|
|
|
58
72
|
"xs"
|
|
59
73
|
];
|
|
60
74
|
//#endregion
|
|
61
|
-
export { BUTTON_SIZES, BUTTON_VARIANT_TOKENS };
|
|
75
|
+
export { BUTTON_PLAIN_VARIANT, BUTTON_SIZES, BUTTON_VARIANT_TOKENS };
|
|
@@ -14,7 +14,9 @@ declare const ComboboxInput: ({ className, showTrigger, showClear, startAddon, s
|
|
|
14
14
|
size?: ControlSize | number;
|
|
15
15
|
ref?: React$1.Ref<HTMLInputElement>;
|
|
16
16
|
}) => React$1.JSX.Element;
|
|
17
|
-
declare const ComboboxTrigger: ({ className, ...props }: Combobox$1.Trigger.Props
|
|
17
|
+
declare const ComboboxTrigger: ({ className, variant, ...props }: Combobox$1.Trigger.Props & {
|
|
18
|
+
variant?: "plain" | "field";
|
|
19
|
+
}) => React$1.JSX.Element;
|
|
18
20
|
declare const ComboboxPopup: ({ className, children, side, sideOffset, alignOffset, align, ...props }: Combobox$1.Popup.Props & {
|
|
19
21
|
align?: Combobox$1.Positioner.Props["align"];
|
|
20
22
|
sideOffset?: Combobox$1.Positioner.Props["sideOffset"];
|