@stll/ui 0.59.1 → 0.61.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/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/collapsible.d.ts +26 -0
- package/dist/components/collapsible.js +40 -0
- 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/field.d.ts +6 -0
- package/dist/components/field.js +6 -0
- package/dist/components/form.d.ts +43 -3
- package/dist/components/form.js +53 -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/search-field.js +1 -0
- package/dist/components/segmented-icon-toggle.d.ts +2 -1
- package/dist/components/segmented-icon-toggle.js +18 -19
- package/dist/components/switch.d.ts +21 -2
- package/dist/components/switch.js +44 -5
- package/dist/components/table.d.ts +16 -2
- package/dist/components/table.js +5 -4
- package/dist/components/textarea.d.ts +14 -1
- package/dist/components/textarea.js +11 -5
- package/dist/components/toggle-group.d.ts +32 -0
- package/dist/components/toggle-group.js +45 -0
- package/dist/components/toggle.d.ts +26 -0
- package/dist/components/toggle.js +26 -0
- package/dist/index.d.ts +14 -7
- package/dist/index.js +9 -3
- package/dist/kanban/column-bands.js +1 -1
- 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 +17 -1
|
@@ -1,26 +1,81 @@
|
|
|
1
1
|
import { cn } from "../lib/utils.js";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext } from "react";
|
|
3
4
|
//#region src/components/details-grid.tsx
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
children: /* @__PURE__ */ jsx("dl", {
|
|
8
|
-
className: cn("grid grid-cols-[repeat(auto-fit,minmax(min(100%,20rem),1fr))] gap-x-6 gap-y-2 font-sans @min-[64rem]:grid-cols-3", className),
|
|
9
|
-
"data-slot": "details-grid",
|
|
10
|
-
...props
|
|
11
|
-
})
|
|
12
|
-
});
|
|
13
|
-
const DetailsItem = ({ label, children, span, className, ...props }) => /* @__PURE__ */ jsxs("div", {
|
|
14
|
-
className: cn("grid min-w-0 grid-cols-[minmax(0,7rem)_minmax(0,1fr)] items-start gap-x-4 text-xs", span === "wide" && "col-span-full", className),
|
|
15
|
-
"data-slot": "details-item",
|
|
16
|
-
...props,
|
|
17
|
-
children: [/* @__PURE__ */ jsx("dt", {
|
|
18
|
-
className: "text-foreground-disabled min-w-0 font-medium tracking-wide wrap-anywhere uppercase",
|
|
19
|
-
children: label
|
|
20
|
-
}), /* @__PURE__ */ jsx("dd", {
|
|
21
|
-
className: "text-foreground-strong-muted min-w-0 wrap-anywhere",
|
|
22
|
-
children
|
|
23
|
-
})]
|
|
5
|
+
const DetailsShapeContext = createContext({
|
|
6
|
+
orientation: "horizontal",
|
|
7
|
+
variant: "sheet"
|
|
24
8
|
});
|
|
9
|
+
const LIST_CLASS_NAME = {
|
|
10
|
+
horizontal: "grid grid-cols-[auto_minmax(0,1fr)] items-center gap-x-4 gap-y-1 text-sm",
|
|
11
|
+
vertical: "flex min-w-0 flex-col gap-2 text-sm"
|
|
12
|
+
};
|
|
13
|
+
const ITEM_CLASS_NAME = {
|
|
14
|
+
list: {
|
|
15
|
+
horizontal: "col-span-full grid grid-cols-subgrid items-center",
|
|
16
|
+
vertical: "flex min-w-0 flex-col gap-2"
|
|
17
|
+
},
|
|
18
|
+
sheet: {
|
|
19
|
+
horizontal: "grid min-w-0 grid-cols-[minmax(0,7rem)_minmax(0,1fr)] items-start gap-x-4 text-xs",
|
|
20
|
+
vertical: "flex min-w-0 flex-col gap-1 text-xs"
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const LABEL_CLASS_NAME = {
|
|
24
|
+
list: {
|
|
25
|
+
horizontal: "text-muted-foreground min-w-0",
|
|
26
|
+
vertical: "font-medium"
|
|
27
|
+
},
|
|
28
|
+
sheet: {
|
|
29
|
+
horizontal: "text-foreground-disabled min-w-0 font-medium tracking-wide wrap-anywhere uppercase",
|
|
30
|
+
vertical: "text-foreground-disabled min-w-0 font-medium tracking-wide wrap-anywhere uppercase"
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const VALUE_CLASS_NAME = {
|
|
34
|
+
list: "min-w-0 wrap-anywhere",
|
|
35
|
+
sheet: "text-foreground-strong-muted min-w-0 wrap-anywhere"
|
|
36
|
+
};
|
|
37
|
+
const DetailsGrid = ({ className, orientation = "horizontal", variant = "sheet", ...props }) => {
|
|
38
|
+
const shape = {
|
|
39
|
+
orientation,
|
|
40
|
+
variant
|
|
41
|
+
};
|
|
42
|
+
if (variant === "list") return /* @__PURE__ */ jsx(DetailsShapeContext.Provider, {
|
|
43
|
+
value: shape,
|
|
44
|
+
children: /* @__PURE__ */ jsx("dl", {
|
|
45
|
+
className: cn(LIST_CLASS_NAME[orientation], className),
|
|
46
|
+
"data-orientation": orientation,
|
|
47
|
+
"data-slot": "details-grid",
|
|
48
|
+
"data-variant": variant,
|
|
49
|
+
...props
|
|
50
|
+
})
|
|
51
|
+
});
|
|
52
|
+
return /* @__PURE__ */ jsx(DetailsShapeContext.Provider, {
|
|
53
|
+
value: shape,
|
|
54
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
55
|
+
className: "@container min-w-0 [contain-intrinsic-inline-size:20rem]",
|
|
56
|
+
"data-slot": "details-grid-container",
|
|
57
|
+
children: /* @__PURE__ */ jsx("dl", {
|
|
58
|
+
className: cn("grid grid-cols-[repeat(auto-fit,minmax(min(100%,20rem),1fr))] gap-x-6 gap-y-2 font-sans @min-[64rem]:grid-cols-3", className),
|
|
59
|
+
"data-slot": "details-grid",
|
|
60
|
+
...props
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
const DetailsItem = ({ label, children, span, className, ...props }) => {
|
|
66
|
+
const { orientation, variant } = useContext(DetailsShapeContext);
|
|
67
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
68
|
+
className: cn(ITEM_CLASS_NAME[variant][orientation], variant === "sheet" && span === "wide" && "col-span-full", className),
|
|
69
|
+
"data-slot": "details-item",
|
|
70
|
+
...props,
|
|
71
|
+
children: [/* @__PURE__ */ jsx("dt", {
|
|
72
|
+
className: LABEL_CLASS_NAME[variant][orientation],
|
|
73
|
+
children: label
|
|
74
|
+
}), /* @__PURE__ */ jsx("dd", {
|
|
75
|
+
className: VALUE_CLASS_NAME[variant],
|
|
76
|
+
children
|
|
77
|
+
})]
|
|
78
|
+
});
|
|
79
|
+
};
|
|
25
80
|
//#endregion
|
|
26
81
|
export { DetailsGrid, DetailsItem };
|
|
@@ -4,6 +4,12 @@ declare const Field: ({ className, ...props }: Field$1.Root.Props) => import("re
|
|
|
4
4
|
declare const FieldLabel: ({ className, ...props }: Field$1.Label.Props) => import("react").JSX.Element;
|
|
5
5
|
declare const FieldItem: ({ className, ...props }: Field$1.Item.Props) => import("react").JSX.Element;
|
|
6
6
|
declare const FieldDescription: ({ className, ...props }: Field$1.Description.Props) => import("react").JSX.Element;
|
|
7
|
+
/**
|
|
8
|
+
* The message for an invalid field. With no `match` it shows whatever error
|
|
9
|
+
* the field has, including one the `Form` received in `errors` from the
|
|
10
|
+
* server; with a validity key (`match="valueMissing"`) it shows only for that
|
|
11
|
+
* constraint, so each rule can be worded on its own. See `Form`.
|
|
12
|
+
*/
|
|
7
13
|
declare const FieldError: ({ className, ...props }: Field$1.Error.Props) => import("react").JSX.Element;
|
|
8
14
|
declare const FieldControl: import("react").ForwardRefExoticComponent<Omit<import("@base-ui/react").FieldControlProps, "ref"> & import("react").RefAttributes<HTMLElement>>;
|
|
9
15
|
declare const FieldValidity: import("react").FC<import("@base-ui/react").FieldValidityProps>;
|
package/dist/components/field.js
CHANGED
|
@@ -23,6 +23,12 @@ const FieldDescription = ({ className, ...props }) => /* @__PURE__ */ jsx(Field$
|
|
|
23
23
|
"data-slot": "field-description",
|
|
24
24
|
...props
|
|
25
25
|
});
|
|
26
|
+
/**
|
|
27
|
+
* The message for an invalid field. With no `match` it shows whatever error
|
|
28
|
+
* the field has, including one the `Form` received in `errors` from the
|
|
29
|
+
* server; with a validity key (`match="valueMissing"`) it shows only for that
|
|
30
|
+
* constraint, so each rule can be worded on its own. See `Form`.
|
|
31
|
+
*/
|
|
26
32
|
const FieldError = ({ className, ...props }) => /* @__PURE__ */ jsx(Field$1.Error, {
|
|
27
33
|
className: cn("text-destructive-foreground text-xs", className),
|
|
28
34
|
"data-slot": "field-error",
|
|
@@ -1,8 +1,48 @@
|
|
|
1
1
|
import { Form as Form$1 } from "@base-ui/react/form";
|
|
2
|
+
import { VariantProps } from "class-variance-authority";
|
|
2
3
|
//#region src/components/form.d.ts
|
|
3
|
-
declare const
|
|
4
|
+
declare const formVariants: (props?: ({
|
|
5
|
+
density?: "compact" | "default" | "snug" | null | undefined;
|
|
6
|
+
layout?: "inline" | "stack" | null | undefined;
|
|
7
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
|
+
/**
|
|
9
|
+
* Errors a server returned for the last submit, keyed by each `Field`'s
|
|
10
|
+
* `name`: one message or several.
|
|
11
|
+
*/
|
|
12
|
+
type FormErrors = NonNullable<Form$1.Props["errors"]>;
|
|
13
|
+
type FormProps = Form$1.Props & VariantProps<typeof formVariants> & {
|
|
4
14
|
dirty?: boolean;
|
|
5
15
|
onDiscard?: (() => void) | undefined;
|
|
6
|
-
}
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* A form whose fields report their own errors.
|
|
19
|
+
*
|
|
20
|
+
* Validation, in the order a reader meets it:
|
|
21
|
+
*
|
|
22
|
+
* - **In the browser.** A `Field` reads its control's constraints
|
|
23
|
+
* (`required`, `type`, `pattern`, `min`/`max`, a `validate` function) and
|
|
24
|
+
* shows a `FieldError` for the ones that fail. `validationMode` stays
|
|
25
|
+
* Base UI's `onSubmit`: nothing is flagged while a person tabs through an
|
|
26
|
+
* empty form, the first submit marks every invalid field at once, and from
|
|
27
|
+
* then on each field re-validates as it changes. `onBlur` would flag a
|
|
28
|
+
* required field the moment focus passes it, before anyone tried to submit;
|
|
29
|
+
* pass it only where checking a value as soon as it is left is the point
|
|
30
|
+
* (an availability check, a long identifier).
|
|
31
|
+
* - **From the server.** Pass what the server rejected as `errors`, keyed by
|
|
32
|
+
* field `name` (`{ email: "Already invited" }`). The field turns invalid,
|
|
33
|
+
* its control gets `aria-invalid`, and its `FieldError` shows the message.
|
|
34
|
+
* Base UI drops a field's server error as soon as its value changes, so a
|
|
35
|
+
* correction clears the message without a round trip. Keep the errors in
|
|
36
|
+
* state and replace them on the next submit.
|
|
37
|
+
*
|
|
38
|
+
* In a `FieldError`, leave `match` unset to show whichever error the field
|
|
39
|
+
* has, the server's included; `match="valueMissing"` (or another validity
|
|
40
|
+
* key) words one constraint in the product's voice; `match` set to `true`
|
|
41
|
+
* hands visibility to the caller.
|
|
42
|
+
*
|
|
43
|
+
* `dirty` and `onDiscard` register the form with an enclosing `Dialog`'s
|
|
44
|
+
* unsaved-changes guard.
|
|
45
|
+
*/
|
|
46
|
+
declare const Form: ({ className, density, layout, dirty, onDiscard, children, ...props }: FormProps) => import("react").JSX.Element;
|
|
7
47
|
//#endregion
|
|
8
|
-
export { Form };
|
|
48
|
+
export { Form, type FormErrors, type FormProps, formVariants };
|
package/dist/components/form.js
CHANGED
|
@@ -2,10 +2,60 @@
|
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
3
|
import { DialogFormState } from "./dialog-form-state.js";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { cva } from "class-variance-authority";
|
|
5
6
|
import { Form as Form$1 } from "@base-ui/react/form";
|
|
6
7
|
//#region src/components/form.tsx
|
|
7
|
-
const
|
|
8
|
-
|
|
8
|
+
const formVariants = cva("flex w-full", {
|
|
9
|
+
defaultVariants: {
|
|
10
|
+
density: "default",
|
|
11
|
+
layout: "stack"
|
|
12
|
+
},
|
|
13
|
+
variants: {
|
|
14
|
+
density: {
|
|
15
|
+
default: "gap-4",
|
|
16
|
+
snug: "gap-3",
|
|
17
|
+
compact: "gap-2"
|
|
18
|
+
},
|
|
19
|
+
layout: {
|
|
20
|
+
stack: "flex-col",
|
|
21
|
+
inline: "flex-row items-center"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* A form whose fields report their own errors.
|
|
27
|
+
*
|
|
28
|
+
* Validation, in the order a reader meets it:
|
|
29
|
+
*
|
|
30
|
+
* - **In the browser.** A `Field` reads its control's constraints
|
|
31
|
+
* (`required`, `type`, `pattern`, `min`/`max`, a `validate` function) and
|
|
32
|
+
* shows a `FieldError` for the ones that fail. `validationMode` stays
|
|
33
|
+
* Base UI's `onSubmit`: nothing is flagged while a person tabs through an
|
|
34
|
+
* empty form, the first submit marks every invalid field at once, and from
|
|
35
|
+
* then on each field re-validates as it changes. `onBlur` would flag a
|
|
36
|
+
* required field the moment focus passes it, before anyone tried to submit;
|
|
37
|
+
* pass it only where checking a value as soon as it is left is the point
|
|
38
|
+
* (an availability check, a long identifier).
|
|
39
|
+
* - **From the server.** Pass what the server rejected as `errors`, keyed by
|
|
40
|
+
* field `name` (`{ email: "Already invited" }`). The field turns invalid,
|
|
41
|
+
* its control gets `aria-invalid`, and its `FieldError` shows the message.
|
|
42
|
+
* Base UI drops a field's server error as soon as its value changes, so a
|
|
43
|
+
* correction clears the message without a round trip. Keep the errors in
|
|
44
|
+
* state and replace them on the next submit.
|
|
45
|
+
*
|
|
46
|
+
* In a `FieldError`, leave `match` unset to show whichever error the field
|
|
47
|
+
* has, the server's included; `match="valueMissing"` (or another validity
|
|
48
|
+
* key) words one constraint in the product's voice; `match` set to `true`
|
|
49
|
+
* hands visibility to the caller.
|
|
50
|
+
*
|
|
51
|
+
* `dirty` and `onDiscard` register the form with an enclosing `Dialog`'s
|
|
52
|
+
* unsaved-changes guard.
|
|
53
|
+
*/
|
|
54
|
+
const Form = ({ className, density, layout, dirty = false, onDiscard, children, ...props }) => /* @__PURE__ */ jsxs(Form$1, {
|
|
55
|
+
className: cn(formVariants({
|
|
56
|
+
density,
|
|
57
|
+
layout
|
|
58
|
+
}), className),
|
|
9
59
|
"data-slot": "form",
|
|
10
60
|
...props,
|
|
11
61
|
children: [/* @__PURE__ */ jsx(DialogFormState, {
|
|
@@ -14,4 +64,4 @@ const Form = ({ className, dirty = false, onDiscard, children, ...props }) => /*
|
|
|
14
64
|
}), children]
|
|
15
65
|
});
|
|
16
66
|
//#endregion
|
|
17
|
-
export { Form };
|
|
67
|
+
export { Form, formVariants };
|
|
@@ -16,4 +16,13 @@ export declare const INPUT_SIZE_CLASS_NAMES: {
|
|
|
16
16
|
readonly default: undefined;
|
|
17
17
|
readonly lg: "min-h-9.5 leading-9.5 sm:min-h-8.5 sm:leading-8.5";
|
|
18
18
|
};
|
|
19
|
+
/**
|
|
20
|
+
* A field edited where it is read: an option's name in a list, a page
|
|
21
|
+
* heading, the summary under it. No box until focused, the type, leading and
|
|
22
|
+
* colour of the text around it, so the row keeps the height of its text; a
|
|
23
|
+
* focus ring marks it while editing. A slim inline inset gives the ring and
|
|
24
|
+
* the caret room, and a matching negative margin keeps the text in line with
|
|
25
|
+
* its neighbours.
|
|
26
|
+
*/
|
|
27
|
+
export declare const INLINE_FIELD_CLASS_NAME = "placeholder:text-foreground-placeholder focus-visible:ring-ring/30 -mx-1 min-h-0 w-full min-w-0 rounded-sm bg-transparent px-1 py-0 font-[inherit] text-[length:inherit] leading-[inherit] text-inherit outline-none focus-visible:ring-2";
|
|
19
28
|
//#endregion
|
|
@@ -17,5 +17,14 @@ const INPUT_SIZE_CLASS_NAMES = {
|
|
|
17
17
|
[CONTROL_SIZE.default]: void 0,
|
|
18
18
|
[CONTROL_SIZE.lg]: "min-h-9.5 leading-9.5 sm:min-h-8.5 sm:leading-8.5"
|
|
19
19
|
};
|
|
20
|
+
/**
|
|
21
|
+
* A field edited where it is read: an option's name in a list, a page
|
|
22
|
+
* heading, the summary under it. No box until focused, the type, leading and
|
|
23
|
+
* colour of the text around it, so the row keeps the height of its text; a
|
|
24
|
+
* focus ring marks it while editing. A slim inline inset gives the ring and
|
|
25
|
+
* the caret room, and a matching negative margin keeps the text in line with
|
|
26
|
+
* its neighbours.
|
|
27
|
+
*/
|
|
28
|
+
const INLINE_FIELD_CLASS_NAME = "placeholder:text-foreground-placeholder focus-visible:ring-ring/30 -mx-1 min-h-0 w-full min-w-0 rounded-sm bg-transparent px-1 py-0 font-[inherit] text-[length:inherit] leading-[inherit] text-inherit outline-none focus-visible:ring-2";
|
|
20
29
|
//#endregion
|
|
21
|
-
export { INPUT_CONTROL_CLASS_NAME, INPUT_ELEMENT_CLASS_NAME, INPUT_SIZE_CLASS_NAMES };
|
|
30
|
+
export { INLINE_FIELD_CLASS_NAME, INPUT_CONTROL_CLASS_NAME, INPUT_ELEMENT_CLASS_NAME, INPUT_SIZE_CLASS_NAMES };
|
|
@@ -7,8 +7,14 @@ type InputProps = Omit<Input$1.Props & React$1.RefAttributes<HTMLInputElement>,
|
|
|
7
7
|
size?: ControlSize | number;
|
|
8
8
|
style?: React$1.CSSProperties;
|
|
9
9
|
unstyled?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* A borderless field in a line of text: it edits a value where it is read,
|
|
12
|
+
* with no box until focused and the parent's type, leading and colour. See
|
|
13
|
+
* `INLINE_FIELD_CLASS_NAME`.
|
|
14
|
+
*/
|
|
15
|
+
inline?: boolean;
|
|
10
16
|
nativeInput?: boolean;
|
|
11
17
|
};
|
|
12
|
-
declare const Input: ({ className, size, unstyled, nativeInput, dir, onChange, ...props }: InputProps) => React$1.JSX.Element;
|
|
18
|
+
declare const Input: ({ className, size, unstyled, inline, nativeInput, dir, onChange, ...props }: InputProps) => React$1.JSX.Element;
|
|
13
19
|
//#endregion
|
|
14
20
|
export { Input, type InputProps };
|
package/dist/components/input.js
CHANGED
|
@@ -3,11 +3,11 @@ import { SearchIcon } from "../icons.js";
|
|
|
3
3
|
import { cn } from "../lib/utils.js";
|
|
4
4
|
import { CONTROL_SIZE } from "../lib/control-size.js";
|
|
5
5
|
import { isStructuredInputType, useContentDir } from "../hooks/use-content-dir.js";
|
|
6
|
-
import { INPUT_ELEMENT_CLASS_NAME, INPUT_SIZE_CLASS_NAMES } from "./input-control.js";
|
|
6
|
+
import { INLINE_FIELD_CLASS_NAME, INPUT_ELEMENT_CLASS_NAME, INPUT_SIZE_CLASS_NAMES } from "./input-control.js";
|
|
7
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
import { Input as Input$1 } from "@base-ui/react/input";
|
|
9
9
|
//#region src/components/input.tsx
|
|
10
|
-
const Input = ({ className, size = CONTROL_SIZE.default, unstyled = false, nativeInput = false, dir, onChange, ...props }) => {
|
|
10
|
+
const Input = ({ className, size = CONTROL_SIZE.default, unstyled = false, inline = false, nativeInput = false, dir, onChange, ...props }) => {
|
|
11
11
|
const controlSize = typeof size === "number" ? CONTROL_SIZE.default : size;
|
|
12
12
|
const contentDir = useContentDir({
|
|
13
13
|
dir: dir ?? (isStructuredInputType(props.type) ? "ltr" : void 0),
|
|
@@ -18,9 +18,9 @@ const Input = ({ className, size = CONTROL_SIZE.default, unstyled = false, nativ
|
|
|
18
18
|
contentDir.trackValue(event.currentTarget.value);
|
|
19
19
|
onChange?.(event);
|
|
20
20
|
};
|
|
21
|
-
const inputClassName = cn(INPUT_ELEMENT_CLASS_NAME, INPUT_SIZE_CLASS_NAMES[controlSize], unstyled && "px-0", props.type === "search" && "ps-8 [&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none [&::-webkit-search-results-button]:appearance-none [&::-webkit-search-results-decoration]:appearance-none");
|
|
21
|
+
const inputClassName = cn(inline ? INLINE_FIELD_CLASS_NAME : INPUT_ELEMENT_CLASS_NAME, !inline && INPUT_SIZE_CLASS_NAMES[controlSize], unstyled && !inline && "px-0", props.type === "search" && "ps-8 [&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none [&::-webkit-search-results-button]:appearance-none [&::-webkit-search-results-decoration]:appearance-none");
|
|
22
22
|
return /* @__PURE__ */ jsxs("span", {
|
|
23
|
-
className: cn(!unstyled && "border-input bg-background text-foreground ring-ring/24 has-autofill:bg-foreground/4 has-focus-visible:border-ring has-aria-invalid:border-destructive/36 has-focus-visible:has-aria-invalid:border-destructive/64 has-focus-visible:has-aria-invalid:ring-destructive/16 dark:bg-input/32 dark:has-autofill:bg-foreground/8 dark:has-aria-invalid:ring-destructive/24 relative inline-flex w-full rounded-lg border text-base shadow-xs/5 not-dark:bg-clip-padding before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] has-focus-visible:ring-[3px] has-disabled:opacity-64 has-[:disabled,:focus-visible,[aria-invalid]]:shadow-none sm:text-sm dark:not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)] pointer-coarse:min-h-11", className) || void 0,
|
|
23
|
+
className: cn(inline ? "relative inline-flex w-full" : !unstyled && "border-input bg-background text-foreground ring-ring/24 has-autofill:bg-foreground/4 has-focus-visible:border-ring has-aria-invalid:border-destructive/36 has-focus-visible:has-aria-invalid:border-destructive/64 has-focus-visible:has-aria-invalid:ring-destructive/16 dark:bg-input/32 dark:has-autofill:bg-foreground/8 dark:has-aria-invalid:ring-destructive/24 relative inline-flex w-full rounded-lg border text-base shadow-xs/5 not-dark:bg-clip-padding before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] has-focus-visible:ring-[3px] has-disabled:opacity-64 has-[:disabled,:focus-visible,[aria-invalid]]:shadow-none sm:text-sm dark:not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)] pointer-coarse:min-h-11", className) || void 0,
|
|
24
24
|
"data-size": size,
|
|
25
25
|
"data-slot": "input-control",
|
|
26
26
|
dir: contentDir.dir,
|
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import { useRender } from "@base-ui/react/use-render";
|
|
2
2
|
//#region src/components/label.d.ts
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* - `default`: the caption above or beside a control.
|
|
5
|
+
* - `choice`: one row of a list of choices, wrapping its radio or checkbox so
|
|
6
|
+
* the whole row picks it; it highlights on hover and rings while the
|
|
7
|
+
* control inside has keyboard focus.
|
|
8
|
+
* - `choice-card`: a bordered card wrapping its radio, for choices that carry
|
|
9
|
+
* a description; the chosen card takes the primary border.
|
|
10
|
+
* - `muted`: a quiet caption in small type, for a filter bar or a dense side
|
|
11
|
+
* form where the controls, not their names, carry the eye.
|
|
12
|
+
*
|
|
13
|
+
* The choice variants dim and stop inviting a click while their control is
|
|
14
|
+
* disabled.
|
|
15
|
+
*/
|
|
16
|
+
declare const LABEL_VARIANT_CLASS_NAME: {
|
|
17
|
+
readonly default: "text-foreground inline-flex items-center gap-2 text-base/4.5 font-medium sm:text-sm/4";
|
|
18
|
+
readonly choice: "hover:bg-muted/60 has-focus-visible:ring-ring flex min-h-11 cursor-pointer items-center gap-2 rounded-sm px-1 py-1.5 text-sm has-focus-visible:ring-2 has-disabled:cursor-not-allowed has-disabled:opacity-64";
|
|
19
|
+
readonly "choice-card": "has-checked:border-primary has-checked:bg-muted/50 flex min-h-11 cursor-pointer items-start gap-3 rounded-md border p-3 has-disabled:cursor-not-allowed has-disabled:opacity-50";
|
|
20
|
+
readonly muted: "text-muted-foreground inline-flex items-center gap-2 text-xs/4 font-medium";
|
|
21
|
+
};
|
|
22
|
+
type LabelVariant = keyof typeof LABEL_VARIANT_CLASS_NAME;
|
|
23
|
+
declare function Label({ className, render, variant, ...props }: useRender.ComponentProps<"label"> & {
|
|
24
|
+
variant?: LabelVariant;
|
|
25
|
+
}): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
4
26
|
//#endregion
|
|
5
|
-
export { Label };
|
|
27
|
+
export { Label, type LabelVariant };
|
package/dist/components/label.js
CHANGED
|
@@ -3,9 +3,28 @@ import { cn } from "../lib/utils.js";
|
|
|
3
3
|
import { mergeProps } from "@base-ui/react/merge-props";
|
|
4
4
|
import { useRender } from "@base-ui/react/use-render";
|
|
5
5
|
//#region src/components/label.tsx
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* - `default`: the caption above or beside a control.
|
|
8
|
+
* - `choice`: one row of a list of choices, wrapping its radio or checkbox so
|
|
9
|
+
* the whole row picks it; it highlights on hover and rings while the
|
|
10
|
+
* control inside has keyboard focus.
|
|
11
|
+
* - `choice-card`: a bordered card wrapping its radio, for choices that carry
|
|
12
|
+
* a description; the chosen card takes the primary border.
|
|
13
|
+
* - `muted`: a quiet caption in small type, for a filter bar or a dense side
|
|
14
|
+
* form where the controls, not their names, carry the eye.
|
|
15
|
+
*
|
|
16
|
+
* The choice variants dim and stop inviting a click while their control is
|
|
17
|
+
* disabled.
|
|
18
|
+
*/
|
|
19
|
+
const LABEL_VARIANT_CLASS_NAME = {
|
|
20
|
+
default: "text-foreground inline-flex items-center gap-2 text-base/4.5 font-medium sm:text-sm/4",
|
|
21
|
+
choice: "hover:bg-muted/60 has-focus-visible:ring-ring flex min-h-11 cursor-pointer items-center gap-2 rounded-sm px-1 py-1.5 text-sm has-focus-visible:ring-2 has-disabled:cursor-not-allowed has-disabled:opacity-64",
|
|
22
|
+
"choice-card": "has-checked:border-primary has-checked:bg-muted/50 flex min-h-11 cursor-pointer items-start gap-3 rounded-md border p-3 has-disabled:cursor-not-allowed has-disabled:opacity-50",
|
|
23
|
+
muted: "text-muted-foreground inline-flex items-center gap-2 text-xs/4 font-medium"
|
|
24
|
+
};
|
|
25
|
+
function Label({ className, render, variant = "default", ...props }) {
|
|
7
26
|
const defaultProps = {
|
|
8
|
-
className: cn(
|
|
27
|
+
className: cn(LABEL_VARIANT_CLASS_NAME[variant], className),
|
|
9
28
|
"data-slot": "label"
|
|
10
29
|
};
|
|
11
30
|
return useRender({
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import { ControlSize } from "../lib/control-size.js";
|
|
2
2
|
import { NumberField } from "@base-ui/react/number-field";
|
|
3
|
+
import { ReactNode } from "react";
|
|
3
4
|
//#region src/components/number-input.d.ts
|
|
4
5
|
type NumberInputProps = Omit<NumberField.Root.Props, "children" | "className" | "render"> & {
|
|
5
6
|
className?: string;
|
|
6
7
|
inputClassName?: string;
|
|
7
|
-
inputProps?: Omit<NumberField.Input.Props, "className" | "defaultValue" | "id" | "onChange" | "type" | "value">;
|
|
8
|
+
inputProps?: Omit<NumberField.Input.Props, "className" | "defaultValue" | "id" | "onChange" | "render" | "type" | "value">;
|
|
8
9
|
size?: ControlSize;
|
|
10
|
+
/**
|
|
11
|
+
* The unit the value is in ("pt", "%"), shown muted at the end of the
|
|
12
|
+
* control so the number reads with its unit while the typed text stays a
|
|
13
|
+
* bare number.
|
|
14
|
+
*/
|
|
15
|
+
suffix?: ReactNode;
|
|
9
16
|
};
|
|
10
17
|
/**
|
|
11
18
|
* A numeric control that keeps the user's editable text separate from its
|
|
12
19
|
* canonical numeric value. Parsed changes are reported through `onValueChange`;
|
|
13
20
|
* formatting and clamping happen only at Base UI's commit boundaries.
|
|
14
21
|
*/
|
|
15
|
-
declare const NumberInput: ({ className, inputClassName, inputProps, dir, size, ...props }: NumberInputProps) => import("react").JSX.Element;
|
|
22
|
+
declare const NumberInput: ({ className, inputClassName, inputProps, dir, size, suffix, ...props }: NumberInputProps) => import("react").JSX.Element;
|
|
16
23
|
//#endregion
|
|
17
24
|
export { NumberInput, type NumberInputProps };
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
3
|
import { CONTROL_SIZE } from "../lib/control-size.js";
|
|
4
4
|
import { INPUT_CONTROL_CLASS_NAME, INPUT_ELEMENT_CLASS_NAME, INPUT_SIZE_CLASS_NAMES } from "./input-control.js";
|
|
5
|
-
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { NumberField } from "@base-ui/react/number-field";
|
|
7
7
|
//#region src/components/number-input.tsx
|
|
8
8
|
/**
|
|
@@ -10,18 +10,30 @@ import { NumberField } from "@base-ui/react/number-field";
|
|
|
10
10
|
* canonical numeric value. Parsed changes are reported through `onValueChange`;
|
|
11
11
|
* formatting and clamping happen only at Base UI's commit boundaries.
|
|
12
12
|
*/
|
|
13
|
-
const NumberInput = ({ className, inputClassName, inputProps, dir = "ltr", size = CONTROL_SIZE.default, ...props }) => /* @__PURE__ */
|
|
13
|
+
const NumberInput = ({ className, inputClassName, inputProps, dir = "ltr", size = CONTROL_SIZE.default, suffix, ...props }) => /* @__PURE__ */ jsxs(NumberField.Root, {
|
|
14
14
|
...props,
|
|
15
15
|
className: cn(INPUT_CONTROL_CLASS_NAME, className),
|
|
16
16
|
"data-size": size,
|
|
17
17
|
"data-slot": "number-input-control",
|
|
18
18
|
dir,
|
|
19
19
|
render: void 0,
|
|
20
|
-
children: /* @__PURE__ */ jsx(NumberField.Input, {
|
|
20
|
+
children: [/* @__PURE__ */ jsx(NumberField.Input, {
|
|
21
21
|
...inputProps,
|
|
22
|
-
className: cn(INPUT_ELEMENT_CLASS_NAME, INPUT_SIZE_CLASS_NAMES[size], inputClassName),
|
|
23
|
-
"data-slot": "number-input"
|
|
24
|
-
|
|
22
|
+
className: cn(INPUT_ELEMENT_CLASS_NAME, INPUT_SIZE_CLASS_NAMES[size], "tabular-nums", suffix !== void 0 && "pe-1", inputClassName),
|
|
23
|
+
"data-slot": "number-input",
|
|
24
|
+
render: (inputElementProps, state) => /* @__PURE__ */ jsx("input", {
|
|
25
|
+
...inputElementProps,
|
|
26
|
+
"aria-valuemax": props.max,
|
|
27
|
+
"aria-valuemin": props.min,
|
|
28
|
+
"aria-valuenow": state.value ?? void 0,
|
|
29
|
+
dir,
|
|
30
|
+
role: "spinbutton"
|
|
31
|
+
})
|
|
32
|
+
}), suffix === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
33
|
+
className: "text-muted-foreground pointer-events-none flex shrink-0 items-center pe-2.5 text-xs",
|
|
34
|
+
"data-slot": "number-input-suffix",
|
|
35
|
+
children: suffix
|
|
36
|
+
})]
|
|
25
37
|
});
|
|
26
38
|
//#endregion
|
|
27
39
|
export { NumberInput };
|
|
@@ -16,7 +16,8 @@ type SegmentedIconToggleProps<T extends string> = {
|
|
|
16
16
|
};
|
|
17
17
|
/** Compact segmented control of icon buttons (e.g. table row wrap, list
|
|
18
18
|
* density). One shared shell so every density/mode switch looks and behaves
|
|
19
|
-
* the same across the app.
|
|
19
|
+
* the same across the app. A `ToggleGroup` of icon items that always holds
|
|
20
|
+
* one value: clicking the chosen option keeps it. */
|
|
20
21
|
export declare const SegmentedIconToggle: <T extends string>({ value, options, onChange, size }: SegmentedIconToggleProps<T>) => import("react").JSX.Element;
|
|
21
22
|
//#endregion
|
|
22
23
|
export {};
|
|
@@ -1,31 +1,30 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ToggleGroup, ToggleGroupItem } from "./toggle-group.js";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
5
|
//#region src/components/segmented-icon-toggle.tsx
|
|
7
6
|
/** Compact segmented control of icon buttons (e.g. table row wrap, list
|
|
8
7
|
* density). One shared shell so every density/mode switch looks and behaves
|
|
9
|
-
* the same across the app.
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
* the same across the app. A `ToggleGroup` of icon items that always holds
|
|
9
|
+
* one value: clicking the chosen option keeps it. */
|
|
10
|
+
const SegmentedIconToggle = ({ value, options, onChange, size = "compact" }) => /* @__PURE__ */ jsx(ToggleGroup, {
|
|
11
|
+
className: cn("rounded-md", size === "touch" ? "h-7 [@media(any-pointer:coarse)]:h-12" : "h-7"),
|
|
12
12
|
"data-slot": "segmented-icon-toggle",
|
|
13
|
+
onValueChange: (next) => {
|
|
14
|
+
const [chosen] = next;
|
|
15
|
+
if (chosen !== void 0 && chosen !== value) onChange(chosen);
|
|
16
|
+
},
|
|
17
|
+
value: [value],
|
|
13
18
|
children: options.map((option) => {
|
|
14
19
|
const Icon = option.icon;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"data-control-value": option.value,
|
|
22
|
-
onClick: () => onChange(option.value),
|
|
23
|
-
size: "icon-xs",
|
|
24
|
-
type: "button",
|
|
25
|
-
variant: "ghost"
|
|
26
|
-
}),
|
|
20
|
+
return /* @__PURE__ */ jsx(ToggleGroupItem, {
|
|
21
|
+
"aria-label": option.label,
|
|
22
|
+
className: cn("h-6 w-7 rounded-[4px] sm:h-6", size === "touch" && "[@media(any-pointer:coarse)]:h-11 [@media(any-pointer:coarse)]:w-11"),
|
|
23
|
+
"data-control-value": option.value,
|
|
24
|
+
size: "icon-xs",
|
|
25
|
+
value: option.value,
|
|
27
26
|
children: /* @__PURE__ */ jsx(Icon, { className: "size-3.5" })
|
|
28
|
-
}
|
|
27
|
+
}, option.value);
|
|
29
28
|
})
|
|
30
29
|
});
|
|
31
30
|
//#endregion
|
|
@@ -1,5 +1,24 @@
|
|
|
1
|
+
import * as React$1 from "react";
|
|
1
2
|
import { Switch as Switch$1 } from "@base-ui/react/switch";
|
|
2
3
|
//#region src/components/switch.d.ts
|
|
3
|
-
|
|
4
|
+
/**
|
|
5
|
+
* `default` is a setting's own control; `sm` sits in a dense row or a menu
|
|
6
|
+
* beside small type.
|
|
7
|
+
*/
|
|
8
|
+
type SwitchSize = "sm" | "default";
|
|
9
|
+
type SwitchProps = Switch$1.Root.Props & {
|
|
10
|
+
size?: SwitchSize | undefined;
|
|
11
|
+
};
|
|
12
|
+
declare const Switch: ({ className, size, ...props }: SwitchProps) => React$1.JSX.Element;
|
|
13
|
+
/**
|
|
14
|
+
* A setting row: its label and an optional description at the start, the
|
|
15
|
+
* switch at the end. The whole row is the label, so a click anywhere on it
|
|
16
|
+
* flips the switch, and the row is at least 44px tall for touch. The
|
|
17
|
+
* description is linked to the switch, so it is read after the name.
|
|
18
|
+
*/
|
|
19
|
+
declare const SwitchField: ({ className, description, label, size, ...props }: SwitchProps & {
|
|
20
|
+
label: React$1.ReactNode;
|
|
21
|
+
description?: React$1.ReactNode;
|
|
22
|
+
}) => React$1.JSX.Element;
|
|
4
23
|
//#endregion
|
|
5
|
-
export { Switch };
|
|
24
|
+
export { Switch, SwitchField, type SwitchProps, type SwitchSize };
|
|
@@ -1,16 +1,55 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
|
-
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { useId } from "react";
|
|
4
5
|
import { Switch as Switch$1 } from "@base-ui/react/switch";
|
|
5
6
|
//#region src/components/switch.tsx
|
|
6
|
-
const
|
|
7
|
-
|
|
7
|
+
const SWITCH_ROOT_SIZE_CLASS_NAME = {
|
|
8
|
+
default: "h-5 w-9",
|
|
9
|
+
sm: "h-4 w-7"
|
|
10
|
+
};
|
|
11
|
+
const SWITCH_THUMB_SIZE_CLASS_NAME = {
|
|
12
|
+
default: "size-4 ltr:data-checked:translate-x-4.5 ltr:data-unchecked:translate-x-0.5 rtl:data-checked:-translate-x-4.5 rtl:data-unchecked:-translate-x-0.5",
|
|
13
|
+
sm: "size-3 ltr:data-checked:translate-x-3.5 ltr:data-unchecked:translate-x-0.5 rtl:data-checked:-translate-x-3.5 rtl:data-unchecked:-translate-x-0.5"
|
|
14
|
+
};
|
|
15
|
+
const Switch = ({ className, size = "default", ...props }) => /* @__PURE__ */ jsx(Switch$1.Root, {
|
|
16
|
+
className: cn("focus-visible:ring-ring focus-visible:ring-offset-background data-checked:bg-primary data-unchecked:bg-input relative inline-flex shrink-0 items-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-offset-1 data-disabled:opacity-64", SWITCH_ROOT_SIZE_CLASS_NAME[size], className),
|
|
17
|
+
"data-size": size,
|
|
8
18
|
"data-slot": "switch",
|
|
9
19
|
...props,
|
|
10
20
|
children: /* @__PURE__ */ jsx(Switch$1.Thumb, {
|
|
11
|
-
className: "bg-background pointer-events-none block
|
|
21
|
+
className: cn("bg-background pointer-events-none block rounded-full shadow-xs transition-transform", SWITCH_THUMB_SIZE_CLASS_NAME[size]),
|
|
12
22
|
"data-slot": "switch-thumb"
|
|
13
23
|
})
|
|
14
24
|
});
|
|
25
|
+
/**
|
|
26
|
+
* A setting row: its label and an optional description at the start, the
|
|
27
|
+
* switch at the end. The whole row is the label, so a click anywhere on it
|
|
28
|
+
* flips the switch, and the row is at least 44px tall for touch. The
|
|
29
|
+
* description is linked to the switch, so it is read after the name.
|
|
30
|
+
*/
|
|
31
|
+
const SwitchField = ({ className, description, label, size, ...props }) => {
|
|
32
|
+
const descriptionId = useId();
|
|
33
|
+
return /* @__PURE__ */ jsxs("label", {
|
|
34
|
+
className: cn("flex min-h-11 cursor-pointer items-center justify-between gap-4 py-2 has-data-disabled:cursor-not-allowed", className),
|
|
35
|
+
"data-slot": "switch-field",
|
|
36
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
37
|
+
className: "flex min-w-0 flex-col gap-0.5 in-[label:has([data-disabled])]:opacity-64",
|
|
38
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
39
|
+
className: "text-foreground text-sm font-medium",
|
|
40
|
+
children: label
|
|
41
|
+
}), description === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
42
|
+
className: "text-muted-foreground text-xs",
|
|
43
|
+
"data-slot": "switch-field-description",
|
|
44
|
+
id: descriptionId,
|
|
45
|
+
children: description
|
|
46
|
+
})]
|
|
47
|
+
}), /* @__PURE__ */ jsx(Switch, {
|
|
48
|
+
"aria-describedby": description === void 0 ? void 0 : descriptionId,
|
|
49
|
+
size,
|
|
50
|
+
...props
|
|
51
|
+
})]
|
|
52
|
+
});
|
|
53
|
+
};
|
|
15
54
|
//#endregion
|
|
16
|
-
export { Switch };
|
|
55
|
+
export { Switch, SwitchField };
|