@vipengele/react-ui 0.0.0 → 0.1.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/LICENSE +21 -0
- package/README.md +463 -3
- package/dist/Avatar/Avatar.d.ts +21 -0
- package/dist/Avatar/Avatar.d.ts.map +1 -0
- package/dist/Avatar/Avatar.stylesheet.d.ts +17 -0
- package/dist/Avatar/Avatar.stylesheet.d.ts.map +1 -0
- package/dist/Button/Button.d.ts +39 -0
- package/dist/Button/Button.d.ts.map +1 -0
- package/dist/Button/Button.stylesheet.d.ts +17 -0
- package/dist/Button/Button.stylesheet.d.ts.map +1 -0
- package/dist/ButtonGroup/ButtonGroup.d.ts +16 -0
- package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -0
- package/dist/ButtonGroup/ButtonGroup.stylesheet.d.ts +12 -0
- package/dist/ButtonGroup/ButtonGroup.stylesheet.d.ts.map +1 -0
- package/dist/Card/Card.d.ts +36 -0
- package/dist/Card/Card.d.ts.map +1 -0
- package/dist/Card/Card.stylesheet.d.ts +17 -0
- package/dist/Card/Card.stylesheet.d.ts.map +1 -0
- package/dist/Dropdown/Dropdown.d.ts +205 -0
- package/dist/Dropdown/Dropdown.d.ts.map +1 -0
- package/dist/Dropdown/Dropdown.stylesheet.d.ts +24 -0
- package/dist/Dropdown/Dropdown.stylesheet.d.ts.map +1 -0
- package/dist/FieldSet/FieldSet.d.ts +22 -0
- package/dist/FieldSet/FieldSet.d.ts.map +1 -0
- package/dist/FieldSet/FieldSet.stylesheet.d.ts +34 -0
- package/dist/FieldSet/FieldSet.stylesheet.d.ts.map +1 -0
- package/dist/FieldShell/FieldShell.d.ts +42 -0
- package/dist/FieldShell/FieldShell.d.ts.map +1 -0
- package/dist/FieldShell/FieldShell.stylesheet.d.ts +32 -0
- package/dist/FieldShell/FieldShell.stylesheet.d.ts.map +1 -0
- package/dist/FormField/FormField.d.ts +45 -0
- package/dist/FormField/FormField.d.ts.map +1 -0
- package/dist/FormField/FormField.stylesheet.d.ts +24 -0
- package/dist/FormField/FormField.stylesheet.d.ts.map +1 -0
- package/dist/PasswordInput/PasswordInput.d.ts +15 -0
- package/dist/PasswordInput/PasswordInput.d.ts.map +1 -0
- package/dist/PasswordInput/PasswordInput.stylesheet.d.ts +24 -0
- package/dist/PasswordInput/PasswordInput.stylesheet.d.ts.map +1 -0
- package/dist/Popover/Popover.d.ts +47 -0
- package/dist/Popover/Popover.d.ts.map +1 -0
- package/dist/Popover/Popover.stylesheet.d.ts +21 -0
- package/dist/Popover/Popover.stylesheet.d.ts.map +1 -0
- package/dist/Progress/Progress.d.ts +18 -0
- package/dist/Progress/Progress.d.ts.map +1 -0
- package/dist/Progress/Progress.stylesheet.d.ts +18 -0
- package/dist/Progress/Progress.stylesheet.d.ts.map +1 -0
- package/dist/RadioButton/RadioButton.d.ts +18 -0
- package/dist/RadioButton/RadioButton.d.ts.map +1 -0
- package/dist/RadioButton/RadioButton.stylesheet.d.ts +15 -0
- package/dist/RadioButton/RadioButton.stylesheet.d.ts.map +1 -0
- package/dist/RadioGroup/RadioGroup.d.ts +40 -0
- package/dist/RadioGroup/RadioGroup.d.ts.map +1 -0
- package/dist/RadioGroup/RadioGroup.stylesheet.d.ts +11 -0
- package/dist/RadioGroup/RadioGroup.stylesheet.d.ts.map +1 -0
- package/dist/Skeleton/Skeleton.d.ts +18 -0
- package/dist/Skeleton/Skeleton.d.ts.map +1 -0
- package/dist/Skeleton/Skeleton.stylesheet.d.ts +25 -0
- package/dist/Skeleton/Skeleton.stylesheet.d.ts.map +1 -0
- package/dist/Slider/Slider.d.ts +12 -0
- package/dist/Slider/Slider.d.ts.map +1 -0
- package/dist/Slider/Slider.stylesheet.d.ts +18 -0
- package/dist/Slider/Slider.stylesheet.d.ts.map +1 -0
- package/dist/Spinner/Spinner.d.ts +19 -0
- package/dist/Spinner/Spinner.d.ts.map +1 -0
- package/dist/Spinner/Spinner.stylesheet.d.ts +14 -0
- package/dist/Spinner/Spinner.stylesheet.d.ts.map +1 -0
- package/dist/Tabs/Tabs.d.ts +46 -0
- package/dist/Tabs/Tabs.d.ts.map +1 -0
- package/dist/Tabs/Tabs.stylesheet.d.ts +15 -0
- package/dist/Tabs/Tabs.stylesheet.d.ts.map +1 -0
- package/dist/TextField/TextField.d.ts +24 -0
- package/dist/TextField/TextField.d.ts.map +1 -0
- package/dist/TextField/TextField.stylesheet.d.ts +29 -0
- package/dist/TextField/TextField.stylesheet.d.ts.map +1 -0
- package/dist/Toggle/Toggle.d.ts +12 -0
- package/dist/Toggle/Toggle.d.ts.map +1 -0
- package/dist/Toggle/Toggle.stylesheet.d.ts +16 -0
- package/dist/Toggle/Toggle.stylesheet.d.ts.map +1 -0
- package/dist/Tooltip/Tooltip.d.ts +34 -0
- package/dist/Tooltip/Tooltip.d.ts.map +1 -0
- package/dist/Tooltip/Tooltip.stylesheet.d.ts +22 -0
- package/dist/Tooltip/Tooltip.stylesheet.d.ts.map +1 -0
- package/dist/Typography/Typography.d.ts +28 -0
- package/dist/Typography/Typography.d.ts.map +1 -0
- package/dist/Typography/Typography.stylesheet.d.ts +16 -0
- package/dist/Typography/Typography.stylesheet.d.ts.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2947 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/listbox.stylesheet.d.ts +24 -0
- package/dist/internal/listbox.stylesheet.d.ts.map +1 -0
- package/dist/internal/useListboxKeyboard.d.ts +90 -0
- package/dist/internal/useListboxKeyboard.d.ts.map +1 -0
- package/package.json +50 -5
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<ButtonGroup>`'s own styles, injected as an inline `<style>` rather than a `.css` import so
|
|
3
|
+
* the package can stay `"sideEffects": false` (same approach as `Button`'s and `Typography`'s
|
|
4
|
+
* stylesheets).
|
|
5
|
+
*
|
|
6
|
+
* `ButtonGroup` never touches its `<Button>` children — no `cloneElement`, no context — so the
|
|
7
|
+
* attached look is entirely a set of descendant/sibling selectors here, targeting `.vpg-
|
|
8
|
+
* button` directly. That makes `Button`'s rendered class name a contract: renaming it without a
|
|
9
|
+
* matching change here silently breaks the segmented styling with no type error to catch it.
|
|
10
|
+
*/
|
|
11
|
+
export declare const buttonGroupStylesheet = "\n.vpg-button-group {\n display: inline-flex;\n}\n\n.vpg-button-group-horizontal {\n flex-direction: row;\n}\n\n.vpg-button-group-vertical {\n flex-direction: column;\n}\n\n/* Adjacent buttons overlap their shared border by one pixel rather than doubling it, and only\n the outer corners of the group stay rounded. */\n.vpg-button-group-horizontal > .vpg-button {\n border-radius: 0;\n}\n\n.vpg-button-group-horizontal > .vpg-button:first-child {\n border-top-left-radius: var(--vpg-radius);\n border-bottom-left-radius: var(--vpg-radius);\n}\n\n.vpg-button-group-horizontal > .vpg-button:last-child {\n border-top-right-radius: var(--vpg-radius);\n border-bottom-right-radius: var(--vpg-radius);\n}\n\n.vpg-button-group-horizontal > .vpg-button + .vpg-button {\n margin-left: -1px;\n}\n\n.vpg-button-group-vertical > .vpg-button {\n border-radius: 0;\n}\n\n.vpg-button-group-vertical > .vpg-button:first-child {\n border-top-left-radius: var(--vpg-radius);\n border-top-right-radius: var(--vpg-radius);\n}\n\n.vpg-button-group-vertical > .vpg-button:last-child {\n border-bottom-left-radius: var(--vpg-radius);\n border-bottom-right-radius: var(--vpg-radius);\n}\n\n.vpg-button-group-vertical > .vpg-button + .vpg-button {\n margin-top: -1px;\n}\n\n/* A focused or hovered button's border should read as whole rather than being clipped under\n its neighbour's overlap. */\n.vpg-button-group > .vpg-button:focus-visible,\n.vpg-button-group > .vpg-button:hover {\n position: relative;\n z-index: 1;\n}\n";
|
|
12
|
+
//# sourceMappingURL=ButtonGroup.stylesheet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ButtonGroup.stylesheet.d.ts","sourceRoot":"","sources":["../../src/ButtonGroup/ButtonGroup.stylesheet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,w/CA0DjC,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type HTMLAttributes, type MouseEvent, type ReactNode } from "react";
|
|
2
|
+
export interface CardHeaderProps extends HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
children?: ReactNode;
|
|
4
|
+
}
|
|
5
|
+
export interface CardContentProps extends HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
export interface CardFooterProps extends HTMLAttributes<HTMLDivElement> {
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
declare function CardHeader({ className, children, ...rest }: CardHeaderProps): import("react").JSX.Element;
|
|
12
|
+
declare function CardContent({ className, children, ...rest }: CardContentProps): import("react").JSX.Element;
|
|
13
|
+
declare function CardFooter({ className, children, ...rest }: CardFooterProps): import("react").JSX.Element;
|
|
14
|
+
export interface CardProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
15
|
+
/** Any mix of `Card.Header`, `Card.Content`, and `Card.Footer`, in any order — `Card.Content`
|
|
16
|
+
* is required, and at most one of each is allowed. Anything else throws at render. */
|
|
17
|
+
children: ReactNode;
|
|
18
|
+
/** Makes the card itself the interactive element: `role="button"`, focusable, `Enter`/`Space`
|
|
19
|
+
* activate it. Renders as a `<div>` rather than a `<button>` because a `<button>`'s content
|
|
20
|
+
* model forbids interactive content, and `Card.Footer`'s canonical content is a `<Button>`. */
|
|
21
|
+
onClick?: (event: MouseEvent<HTMLDivElement>) => void;
|
|
22
|
+
}
|
|
23
|
+
declare function CardImpl({ className, children, onClick, onKeyDown, ...rest }: CardProps): import("react").JSX.Element;
|
|
24
|
+
type CardComponent = typeof CardImpl & {
|
|
25
|
+
Header: typeof CardHeader;
|
|
26
|
+
Content: typeof CardContent;
|
|
27
|
+
Footer: typeof CardFooter;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* A structured content surface: `Card.Header`, `Card.Content` (required), and `Card.Footer`, in
|
|
31
|
+
* any order in JSX — layout is CSS-driven (`order`), not JSX-order-dependent. Passing `onClick`
|
|
32
|
+
* makes the whole card an interactive element.
|
|
33
|
+
*/
|
|
34
|
+
export declare const Card: CardComponent;
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=Card.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Card.d.ts","sourceRoot":"","sources":["../../src/Card/Card.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,cAAc,EAAsC,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAG3H,MAAM,WAAW,eAAgB,SAAQ,cAAc,CAAC,cAAc,CAAC;IACrE,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,MAAM,WAAW,gBAAiB,SAAQ,cAAc,CAAC,cAAc,CAAC;IACtE,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,MAAM,WAAW,eAAgB,SAAQ,cAAc,CAAC,cAAc,CAAC;IACrE,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,iBAAS,UAAU,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,eAAe,+BAOpE;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,gBAAgB,+BAOtE;AAED,iBAAS,UAAU,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,eAAe,+BAOpE;AAED,MAAM,WAAW,SAAU,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC;IACjF;0FACsF;IACtF,QAAQ,EAAE,SAAS,CAAC;IACpB;;mGAE+F;IAC/F,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,cAAc,CAAC,KAAK,IAAI,CAAC;CACvD;AA+DD,iBAAS,QAAQ,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,SAAS,+BAyDhF;AAED,KAAK,aAAa,GAAG,OAAO,QAAQ,GAAG;IACrC,MAAM,EAAE,OAAO,UAAU,CAAC;IAC1B,OAAO,EAAE,OAAO,WAAW,CAAC;IAC5B,MAAM,EAAE,OAAO,UAAU,CAAC;CAC3B,CAAC;AAEF;;;;GAIG;AAIH,eAAO,MAAM,IAAI,EAIX,aAAa,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<Card>`'s own styles, injected as an inline `<style>` rather than a `.css` import so the
|
|
3
|
+
* package can stay `"sideEffects": false` (same approach as `Button`'s and `Avatar`'s
|
|
4
|
+
* stylesheets).
|
|
5
|
+
*
|
|
6
|
+
* Every `--vpg-*` property is *read* here through `var()` and never assigned inline by the
|
|
7
|
+
* component: an inline style declaration always wins over a stylesheet rule for the same
|
|
8
|
+
* property on the same element, so an inline `--vpg-surface-raised` would permanently
|
|
9
|
+
* shadow the dark-mode reassignment in `@vipengele/react-tokens`'s base stylesheet and this card would
|
|
10
|
+
* stop adapting to colour mode.
|
|
11
|
+
*
|
|
12
|
+
* `.vpg-card` lays its children out as a column and orders them by CSS `order` rather than
|
|
13
|
+
* by DOM position, so `Card.Header`/`Card.Content`/`Card.Footer` render header-above-content-
|
|
14
|
+
* above-footer regardless of the order a consumer writes them in JSX.
|
|
15
|
+
*/
|
|
16
|
+
export declare const cardStylesheet = "\n.vpg-card {\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n background-color: var(--vpg-surface-raised);\n border: 1px solid var(--vpg-border);\n border-radius: var(--vpg-radius);\n color: var(--vpg-ink);\n}\n\n.vpg-card-interactive {\n cursor: pointer;\n text-align: left;\n}\n\n.vpg-card-interactive:hover {\n background-color: var(--vpg-surface-hover);\n}\n\n.vpg-card-interactive:active {\n background-color: var(--vpg-surface-press);\n}\n\n.vpg-card-interactive:focus-visible {\n /* Offset rather than inset so the ring stays legible against a same-coloured surface. */\n outline: var(--vpg-focus-ring-width) solid var(--vpg-accent-ring);\n outline-offset: var(--vpg-focus-ring-offset);\n}\n\n.vpg-card-header {\n order: 1;\n padding: 1rem 1rem 0;\n}\n\n.vpg-card-content {\n order: 2;\n padding: 1rem;\n}\n\n.vpg-card-footer {\n order: 3;\n padding: 0 1rem 1rem;\n display: flex;\n align-items: center;\n gap: 0.5rem;\n}\n";
|
|
17
|
+
//# sourceMappingURL=Card.stylesheet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Card.stylesheet.d.ts","sourceRoot":"","sources":["../../src/Card/Card.stylesheet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,q9BA+C1B,CAAC"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { type IconComponent } from "@vipengele/react-icons";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export interface DropdownOptionProps {
|
|
4
|
+
/** This option's identity. A selection carrying the same string is this option, whatever label
|
|
5
|
+
* the selection itself carries. */
|
|
6
|
+
value: string;
|
|
7
|
+
/** The option's text. A plain string rather than children: it is also what the trigger shows
|
|
8
|
+
* for the current selection, what a chip shows in `multiple` mode, and what type-ahead matches
|
|
9
|
+
* a keystroke against. */
|
|
10
|
+
label: string;
|
|
11
|
+
/** Rendered before the label, and — for the selected option in single-select — in the trigger
|
|
12
|
+
* too. */
|
|
13
|
+
icon?: IconComponent;
|
|
14
|
+
/** Skipped by arrow-key and type-ahead traversal, and not selectable by click or `Enter`. */
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface DropdownGroupProps {
|
|
18
|
+
/** The heading drawn above the group's options, and the group's accessible name. */
|
|
19
|
+
label: string;
|
|
20
|
+
/** `Dropdown.Option` children, and nothing else: a group heads one run of options rather than a
|
|
21
|
+
* tree, so a `Dropdown.Group` among them throws. Falsy children are skipped, as directly under
|
|
22
|
+
* `Dropdown`. */
|
|
23
|
+
children?: ReactNode;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A selection, as `Dropdown` takes it and hands it back. The `value` string is the identity: a
|
|
27
|
+
* consumer that re-creates this object on every render keeps its selection, because nothing in
|
|
28
|
+
* `Dropdown` compares object references.
|
|
29
|
+
*
|
|
30
|
+
* The `label` and `icon` are what renders when no `Dropdown.Option` child carries this `value` —
|
|
31
|
+
* a matching option's own label and icon win over them.
|
|
32
|
+
*/
|
|
33
|
+
export interface DropdownValue {
|
|
34
|
+
value: string;
|
|
35
|
+
label: string;
|
|
36
|
+
icon?: IconComponent;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* One search result from `loadOptions`, in async mode — a plain data object rather than
|
|
40
|
+
* `Dropdown.Option` JSX, since a result that hasn't come back from the API yet has no element for
|
|
41
|
+
* a consumer to have declared. `Dropdown` renders each one as a `Dropdown.Option` itself; the
|
|
42
|
+
* fields are exactly that component's own props, so a declared and a loaded list mean the same
|
|
43
|
+
* thing per entry.
|
|
44
|
+
*/
|
|
45
|
+
export interface DropdownAsyncOption {
|
|
46
|
+
value: string;
|
|
47
|
+
label: string;
|
|
48
|
+
icon?: IconComponent;
|
|
49
|
+
disabled?: boolean;
|
|
50
|
+
/** The heading this result belongs under. Results carrying the same string are one group,
|
|
51
|
+
* however far apart they arrive in the array; the groups stand in the order their first result
|
|
52
|
+
* arrives, after every result carrying no group at all. */
|
|
53
|
+
group?: string;
|
|
54
|
+
}
|
|
55
|
+
declare function DropdownOption({ value, label, icon: OptionIcon, disabled }: DropdownOptionProps): import("react").JSX.Element | null;
|
|
56
|
+
/**
|
|
57
|
+
* A heading and the options standing under it. The heading is not an option: it carries no place
|
|
58
|
+
* in the flat list the arrow keys, `Home`/`End` and the highlight travel, so the index of every
|
|
59
|
+
* option is the index it would hold with no group around it at all.
|
|
60
|
+
*
|
|
61
|
+
* The separator is the group's own leading edge, drawn for every group but the first one showing —
|
|
62
|
+
* which puts a line between each pair of groups and none at either end of the list, without the
|
|
63
|
+
* consumer declaring one.
|
|
64
|
+
*/
|
|
65
|
+
declare function DropdownGroup({ label, children }: DropdownGroupProps): import("react").JSX.Element | null;
|
|
66
|
+
interface DropdownBaseProps {
|
|
67
|
+
/**
|
|
68
|
+
* `Dropdown.Option` children, directly beneath `Dropdown` — there is no list layer, since the
|
|
69
|
+
* listbox's positioning is `Dropdown`'s own business. Falsy children (what
|
|
70
|
+
* `condition && <Dropdown.Option />` produces) are skipped; anything else throws at render.
|
|
71
|
+
*
|
|
72
|
+
* Ignored when `loadOptions` is provided — the two are alternate option sources, not
|
|
73
|
+
* combinable, since an async result has no consumer-declared element to fall back to.
|
|
74
|
+
*/
|
|
75
|
+
children?: ReactNode;
|
|
76
|
+
/**
|
|
77
|
+
* Switches `Dropdown` into async mode: instead of filtering `children`, it calls this with the
|
|
78
|
+
* current query (debounced by `debounceMs`) and renders whatever it resolves to. A rejection is
|
|
79
|
+
* not the consumer's to catch — it surfaces as `errorMessage` in the listbox. Filtering is the
|
|
80
|
+
* API's job in this mode; results are rendered as returned, unfiltered again client-side.
|
|
81
|
+
*/
|
|
82
|
+
loadOptions?: (query: string) => Promise<DropdownAsyncOption[]>;
|
|
83
|
+
/** How long to wait, after the query stops changing, before calling `loadOptions`. Only reads
|
|
84
|
+
* in async mode. */
|
|
85
|
+
debounceMs?: number;
|
|
86
|
+
/** Shown, non-interactively, in the listbox while `loadOptions` is pending. */
|
|
87
|
+
loadingMessage?: string;
|
|
88
|
+
/** Shown, non-interactively, in the listbox when `loadOptions` rejects. */
|
|
89
|
+
errorMessage?: string;
|
|
90
|
+
/** Shown in the trigger while nothing is selected. */
|
|
91
|
+
placeholder?: string;
|
|
92
|
+
/** Whether the listbox opens with a search row above it, filtering the options as the consumer
|
|
93
|
+
* types. Off, the trigger keeps real focus and a keystroke jumps the highlight to the next
|
|
94
|
+
* matching label instead. */
|
|
95
|
+
searchable?: boolean;
|
|
96
|
+
/** The search input's hint, and its accessible name — the input is a `role="combobox"` of its
|
|
97
|
+
* own, and this is the only text naming it. */
|
|
98
|
+
searchPlaceholder?: string;
|
|
99
|
+
/** Whether `multiple`'s chips wrap onto further rows, growing the field downwards, instead of
|
|
100
|
+
* keeping to one row with an indicator standing for the chips that do not fit. Only reads in
|
|
101
|
+
* `multiple` mode; a wrapping field measures nothing and observes nothing. */
|
|
102
|
+
wrapChips?: boolean;
|
|
103
|
+
/** Whether the field offers a "Clear selection" button emptying the whole selection at once.
|
|
104
|
+
* The button is in the shell's trailing slot and shows only while something is selected, so an
|
|
105
|
+
* empty field carries no control with nothing to do. */
|
|
106
|
+
clearable?: boolean;
|
|
107
|
+
/** Composed onto the root wrapper. */
|
|
108
|
+
className?: string;
|
|
109
|
+
/** Lands on the trigger, not the wrapper — `FormField` clones it on, and it is the trigger that
|
|
110
|
+
* a `<label>` and a role query have to resolve to. */
|
|
111
|
+
id?: string;
|
|
112
|
+
"aria-label"?: string;
|
|
113
|
+
"aria-labelledby"?: string;
|
|
114
|
+
"aria-describedby"?: string;
|
|
115
|
+
"aria-invalid"?: boolean;
|
|
116
|
+
}
|
|
117
|
+
export interface DropdownSingleProps extends DropdownBaseProps {
|
|
118
|
+
multiple?: false;
|
|
119
|
+
/** Makes the selection controlled; pair it with `onChange`. `null` selects nothing. */
|
|
120
|
+
value?: DropdownValue | null;
|
|
121
|
+
/** The initially selected value when the selection is uncontrolled. */
|
|
122
|
+
defaultValue?: DropdownValue | null;
|
|
123
|
+
/** Reports the option that was picked, or `null` for a selection that has been emptied — the
|
|
124
|
+
* same `DropdownValue | null` that `value` and `defaultValue` take, so a controlled consumer
|
|
125
|
+
* hands straight back what it is given. */
|
|
126
|
+
onChange?: (value: DropdownValue | null) => void;
|
|
127
|
+
}
|
|
128
|
+
export interface DropdownMultipleProps extends DropdownBaseProps {
|
|
129
|
+
multiple: true;
|
|
130
|
+
/** Makes the selection controlled; pair it with `onChange`. */
|
|
131
|
+
value?: DropdownValue[];
|
|
132
|
+
/** The initially selected values when the selection is uncontrolled. */
|
|
133
|
+
defaultValue?: DropdownValue[];
|
|
134
|
+
onChange?: (value: DropdownValue[]) => void;
|
|
135
|
+
}
|
|
136
|
+
export type DropdownProps = DropdownSingleProps | DropdownMultipleProps;
|
|
137
|
+
declare function DropdownImpl(props: DropdownProps): import("react").JSX.Element;
|
|
138
|
+
type DropdownComponent = typeof DropdownImpl & {
|
|
139
|
+
Option: typeof DropdownOption;
|
|
140
|
+
Group: typeof DropdownGroup;
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* A select-only combobox: a trigger showing the current selection, and a floating listbox of
|
|
144
|
+
* `Dropdown.Option` children.
|
|
145
|
+
*
|
|
146
|
+
* A `Dropdown.Group label` heads a run of those options, and an async result carries its heading
|
|
147
|
+
* as a `group` string instead. A group is drawn over the same flat list of options — its heading
|
|
148
|
+
* takes no index, so the arrow keys, `Home`/`End` and the wrap at either end reach exactly the
|
|
149
|
+
* options they reach with no group declared. Each group is a `role="group"` named by its heading,
|
|
150
|
+
* with a separator drawn between one group and the next; a group the query leaves no option in
|
|
151
|
+
* renders nothing at all.
|
|
152
|
+
*
|
|
153
|
+
* The trigger is a `<div role="combobox" tabIndex={0}>` rather than a `<button>` — only
|
|
154
|
+
* `combobox`, `textbox`, `listbox`, `group`, `application` and the composite-derived roles may
|
|
155
|
+
* carry `aria-activedescendant`, and the highlighted option is tracked virtually through exactly
|
|
156
|
+
* that attribute rather than by moving real DOM focus into the listbox (see
|
|
157
|
+
* `docs/adr/0004-aria-activedescendant-for-dropdown-and-autocomplete.md`).
|
|
158
|
+
*
|
|
159
|
+
* `searchable` (default `true`) opens the listbox under a search row — a magnifier and an input,
|
|
160
|
+
* then a divider — that filters the options by a case-insensitive substring of their labels, and
|
|
161
|
+
* says so when the query matches none. The input is a `role="combobox"` of its own holding the
|
|
162
|
+
* live navigation state, while the trigger keeps the accessible name and description; a non-modal
|
|
163
|
+
* `FloatingFocusManager` puts real focus in the input and returns it to the trigger as the panel
|
|
164
|
+
* closes. Every keystroke belongs to that search: a character typed on the closed trigger opens
|
|
165
|
+
* the panel and seeds the query with it, a pick clears the query, `multiple`'s `Backspace` with no
|
|
166
|
+
* character to delete removes the last selection, and the query clears as the panel closes.
|
|
167
|
+
* `searchable={false}` leaves real focus on the trigger throughout, with a keystroke there jumping
|
|
168
|
+
* the highlight to the next matching label instead.
|
|
169
|
+
*
|
|
170
|
+
* Selection is a `DropdownValue` object — `{ value, label, icon? }` — controlled through
|
|
171
|
+
* `value`/`onChange` or left to `Dropdown` itself, seeded by `defaultValue`. `multiple` switches
|
|
172
|
+
* both to arrays and gives each option a checkbox and each selected value a removable chip beside
|
|
173
|
+
* the trigger; selecting in `multiple` mode toggles the option and leaves the listbox open.
|
|
174
|
+
*
|
|
175
|
+
* `clearable` adds a "Clear selection" button to the field's trailing slot while anything is
|
|
176
|
+
* selected, reporting `null` in single mode and `[]` in `multiple`. Pressing it empties the
|
|
177
|
+
* selection without opening the listbox and leaves focus on the trigger.
|
|
178
|
+
*
|
|
179
|
+
* Those chips keep to one row. Which of them fit is measured against the width the field has, and
|
|
180
|
+
* re-measured before paint whenever that width changes; the rest give way to an indicator reading
|
|
181
|
+
* "and N more" that names them in a tooltip on hover, and a single chip wider than the field shows
|
|
182
|
+
* alone with its label ellipsised. The indicator takes no tab stop: a selection it stands for is
|
|
183
|
+
* removed by unchecking it in the listbox. The trigger is described by the whole selection,
|
|
184
|
+
* merged with whatever `aria-describedby` it is given rather than written over it. `wrapChips`
|
|
185
|
+
* switches the measurement off and wraps the chips onto further rows instead, growing the field
|
|
186
|
+
* downwards.
|
|
187
|
+
*
|
|
188
|
+
* Passing `loadOptions` switches to async mode: `children` goes unread, and `Dropdown` calls
|
|
189
|
+
* `loadOptions(query)` itself (debounced by `debounceMs`, default 300ms) and renders whatever it
|
|
190
|
+
* resolves to, showing `loadingMessage` while pending and `errorMessage` on a rejection. Results
|
|
191
|
+
* are rendered as returned — filtering the query is the API's job in this mode, not `Dropdown`'s.
|
|
192
|
+
* A response the query has moved past is discarded rather than applied — a slow earlier search
|
|
193
|
+
* resolving after a faster later one, and equally one resolving while the next query is still
|
|
194
|
+
* settling. The search is keyed off the query alone, so `loadOptions` may be an inline arrow with
|
|
195
|
+
* a fresh identity on every render. A selection carries its own label, so the trigger and a chip
|
|
196
|
+
* render it with nothing fetched and no option child to match against.
|
|
197
|
+
*
|
|
198
|
+
* The listbox portals into the nearest ancestor `.vpg-root` — the subtree `ThemeProvider`
|
|
199
|
+
* establishes — rather than `document.body`, so it keeps every `--vpg-*` value. With no
|
|
200
|
+
* `.vpg-root` ancestor it renders inline beside the trigger instead, positioned identically
|
|
201
|
+
* but inheriting whatever theme surrounds it.
|
|
202
|
+
*/
|
|
203
|
+
export declare const Dropdown: DropdownComponent;
|
|
204
|
+
export {};
|
|
205
|
+
//# sourceMappingURL=Dropdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Dropdown.d.ts","sourceRoot":"","sources":["../../src/Dropdown/Dropdown.tsx"],"names":[],"mappings":"AACA,OAAO,EAAsB,KAAK,aAAa,EAAa,MAAM,wBAAwB,CAAC;AAC3F,OAAO,EAML,KAAK,SAAS,EAOf,MAAM,OAAO,CAAC;AAQf,MAAM,WAAW,mBAAmB;IAClC;uCACmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd;;8BAE0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd;cACU;IACV,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,oFAAoF;IACpF,KAAK,EAAE,MAAM,CAAC;IACd;;qBAEiB;IACjB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,aAAa,CAAC;CACtB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;+DAE2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA6CD,iBAAS,cAAc,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,QAAgB,EAAE,EAAE,mBAAmB,sCA0ChG;AAED;;;;;;;;GAQG;AACH,iBAAS,aAAa,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,kBAAkB,sCAuB7D;AAED,UAAU,iBAAiB;IACzB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAAC;IAChE;wBACoB;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;iCAE6B;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;mDAC+C;IAC/C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;kFAE8E;IAC9E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;4DAEwD;IACxD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;0DACsD;IACtD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAoB,SAAQ,iBAAiB;IAC5D,QAAQ,CAAC,EAAE,KAAK,CAAC;IACjB,uFAAuF;IACvF,KAAK,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC7B,uEAAuE;IACvE,YAAY,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IACpC;;+CAE2C;IAC3C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,KAAK,IAAI,CAAC;CAClD;AAED,MAAM,WAAW,qBAAsB,SAAQ,iBAAiB;IAC9D,QAAQ,EAAE,IAAI,CAAC;IACf,+DAA+D;IAC/D,KAAK,CAAC,EAAE,aAAa,EAAE,CAAC;IACxB,wEAAwE;IACxE,YAAY,CAAC,EAAE,aAAa,EAAE,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,IAAI,CAAC;CAC7C;AAED,MAAM,MAAM,aAAa,GAAG,mBAAmB,GAAG,qBAAqB,CAAC;AAkMxE,iBAAS,YAAY,CAAC,KAAK,EAAE,aAAa,+BAypBzC;AAED,KAAK,iBAAiB,GAAG,OAAO,YAAY,GAAG;IAC7C,MAAM,EAAE,OAAO,cAAc,CAAC;IAC9B,KAAK,EAAE,OAAO,aAAa,CAAC;CAC7B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AAIH,eAAO,MAAM,QAAQ,EAGf,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<Dropdown>`'s own styles — the wrapper, the `role="combobox"` trigger and the clear button in
|
|
3
|
+
* the field's trailing slot. The field's box is
|
|
4
|
+
* `FieldShell`'s, composed with the extra class `.vpg-dropdown-control`; the floating listbox,
|
|
5
|
+
* its options, the chip row and its chips are styled by the shared
|
|
6
|
+
* `internal/listbox.stylesheet.ts`, which every combobox-shaped component in this package injects.
|
|
7
|
+
*
|
|
8
|
+
* Injected as an inline `<style>` rather than a `.css` import so the package can stay
|
|
9
|
+
* `"sideEffects": false` (same approach as `Card`'s and `Popover`'s stylesheets).
|
|
10
|
+
*
|
|
11
|
+
* Every `--vpg-*` property is *read* here through `var()` and never assigned inline by the
|
|
12
|
+
* component: an inline style declaration always wins over a stylesheet rule for the same property
|
|
13
|
+
* on the same element, so an inline `--vpg-ink` would permanently shadow the dark-mode
|
|
14
|
+
* reassignment in `@vipengele/react-tokens`'s base stylesheet and the trigger would stop adapting to
|
|
15
|
+
* colour mode.
|
|
16
|
+
*
|
|
17
|
+
* Nothing here draws the field's chrome. The border, fill, corner radius, height, horizontal
|
|
18
|
+
* padding, focus ring, danger border and hover fill are the shell's, read off the trigger as its
|
|
19
|
+
* direct child; a second copy of any of them here draws a box inside the field's box, or a ring
|
|
20
|
+
* inside its ring (ADR-0011). The trigger carries `outline: none` for the same reason — the shell
|
|
21
|
+
* draws the focus ring, and the trigger's native outline would sit inside it.
|
|
22
|
+
*/
|
|
23
|
+
export declare const dropdownStylesheet = "\n/* A block box the width of its container, like `TextField`: the field never widens as options\n are selected and never narrows below its container either. `box-sizing: border-box` keeps\n that width inclusive of the field's own border and padding.\n\n `min-width: 0` is what makes that hold in a flex or grid container. The initial `auto`\n resolves there to the automatic minimum size \u2014 the root's min-content, which a row of chips\n and a long trigger label make wide \u2014 and that floor outranks `width: 100%`, so the field\n pushes its track open and overflows whatever width the container was given. */\n.vpg-dropdown {\n display: block;\n box-sizing: border-box;\n width: 100%;\n min-width: 0;\n color: var(--vpg-ink);\n font-family: var(--vpg-font-sans);\n}\n\n/* `align-self: stretch` gives the trigger the field's full height, so a click anywhere across it\n opens the listbox; a percentage height would collapse, because the shell's height is a floor\n rather than a definite length. */\n.vpg-dropdown-trigger {\n display: flex;\n align-items: center;\n align-self: stretch;\n gap: var(--vpg-space-2);\n outline: none;\n color: inherit;\n font-size: var(--vpg-font-size-sm);\n line-height: 1.5;\n cursor: pointer;\n user-select: none;\n}\n\n/* The shell lets every centre element shrink to nothing, which beside a row of chips would leave\n the trigger \u2014 the only thing that opens the listbox \u2014 with no width at all. A square of the\n field's own height keeps it a target and keeps room for the chevron. Qualified by both classes\n the shell element carries, so it outranks the shell's own `min-width: 0` whichever stylesheet\n the page injects first. */\n.vpg-field-shell.vpg-dropdown-control > .vpg-dropdown-trigger {\n min-width: var(--vpg-size-md);\n}\n\n.vpg-dropdown-trigger-icon,\n.vpg-dropdown-chevron {\n flex: none;\n}\n\n/* Matches `.vpg-listbox-option-icon`: the same icon at the same size in the trigger as in\n the list it came from, rather than lucide's own 24px default. */\n.vpg-dropdown-trigger-icon {\n width: var(--vpg-icon-md);\n height: var(--vpg-icon-md);\n}\n\n/* `margin-left: auto` is what sets the chevron against the field's trailing edge. A `multiple`\n trigger holding a selection contains the chevron and nothing else, so no sibling claims the\n trigger's free space and the chevron would otherwise sit at its leading edge, floating\n mid-field. The margin is inert wherever a `flex: 1` sibling has already absorbed that space \u2014\n single-select's value and either mode's placeholder \u2014 since there is none left for it to take. */\n.vpg-dropdown-chevron {\n margin-left: auto;\n color: var(--vpg-ink-muted);\n}\n\n/* Takes the trigger's free space, so the chevron's auto margin resolves to nothing and the label\n keeps the field's leading edge. */\n.vpg-dropdown-value {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.vpg-dropdown-placeholder {\n flex: 1;\n color: var(--vpg-ink-subtle);\n}\n\n/* The clear button stands in the shell's trailing slot, a subtree the shell's `> ` state rules do\n not reach into \u2014 so its focus ring is its own, and the field takes none from it. It carries no\n `opacity` either: the shell already dims for a disabled control, and a second fade here reads\n as more faded than the field around it. */\n.vpg-dropdown-clear {\n display: flex;\n align-items: center;\n justify-content: center;\n flex: none;\n appearance: none;\n padding: var(--vpg-space-1);\n background: none;\n border: none;\n border-radius: var(--vpg-radius-full);\n color: var(--vpg-ink-muted);\n cursor: pointer;\n transition: background-color var(--vpg-duration-fast) var(--vpg-ease-standard),\n color var(--vpg-duration-fast) var(--vpg-ease-standard);\n}\n\n.vpg-dropdown-clear-icon {\n flex: none;\n width: var(--vpg-icon-sm);\n height: var(--vpg-icon-sm);\n}\n\n.vpg-dropdown-clear:hover {\n background-color: var(--vpg-accent-wash);\n color: var(--vpg-ink);\n}\n\n.vpg-dropdown-clear:focus-visible {\n outline: var(--vpg-focus-ring-width) solid var(--vpg-accent-ring);\n outline-offset: var(--vpg-focus-ring-offset);\n}\n\n/* Names every selection to a screen reader while taking no space in the field, which is the only\n route to the chips the row has no width for: those are out of the flow altogether. */\n.vpg-dropdown-selection-description {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n";
|
|
24
|
+
//# sourceMappingURL=Dropdown.stylesheet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Dropdown.stylesheet.d.ts","sourceRoot":"","sources":["../../src/Dropdown/Dropdown.stylesheet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,kBAAkB,oiJAgI9B,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { FieldsetHTMLAttributes, ReactNode } from "react";
|
|
2
|
+
export interface FieldSetProps extends Omit<FieldsetHTMLAttributes<HTMLFieldSetElement>, "children"> {
|
|
3
|
+
/** The group's accessible name, rendered in a native `<legend>` — a `<fieldset>` derives its
|
|
4
|
+
* own accessible name from this automatically, with no id/aria wiring required. */
|
|
5
|
+
legend: ReactNode;
|
|
6
|
+
/** Grouped content, laid out with spacing between each child — typically one or more
|
|
7
|
+
* `FormField`s, but not restricted to them. `FieldSet` carries no form-state logic of its
|
|
8
|
+
* own. */
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A native `<fieldset>` + `<legend>` pair with spacing between `children`, for grouping related
|
|
13
|
+
* controls. Purely a layout wrapper: `disabled` forwards straight to the native `<fieldset>`,
|
|
14
|
+
* which natively disables every descendant form control without `FieldSet` doing anything
|
|
15
|
+
* itself.
|
|
16
|
+
*
|
|
17
|
+
* A `<legend>` names its own `<fieldset>` automatically, but that does not extend to a
|
|
18
|
+
* `role="radiogroup"` element nested inside it — `RadioGroup` carries its own `aria-label` for
|
|
19
|
+
* that reason.
|
|
20
|
+
*/
|
|
21
|
+
export declare function FieldSet({ legend, children, className, ...rest }: FieldSetProps): import("react").JSX.Element;
|
|
22
|
+
//# sourceMappingURL=FieldSet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldSet.d.ts","sourceRoot":"","sources":["../../src/FieldSet/FieldSet.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAG/D,MAAM,WAAW,aAAc,SAAQ,IAAI,CAAC,sBAAsB,CAAC,mBAAmB,CAAC,EAAE,UAAU,CAAC;IAClG;uFACmF;IACnF,MAAM,EAAE,SAAS,CAAC;IAClB;;cAEU;IACV,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,aAAa,+BAgB/E"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<FieldSet>`'s own styles, injected as an inline `<style>` rather than a `.css` import so the
|
|
3
|
+
* package can stay `"sideEffects": false` (same approach as `FormField`'s and `Card`'s
|
|
4
|
+
* stylesheets).
|
|
5
|
+
*
|
|
6
|
+
* Every `--vpg-*` property is *read* here through `var()` and never assigned inline by the
|
|
7
|
+
* component: an inline style declaration always wins over a stylesheet rule for the same
|
|
8
|
+
* property on the same element, so an inline theme property would permanently shadow
|
|
9
|
+
* `@vipengele/react-tokens`'s dark-mode reassignment and this fieldset would stop adapting to colour
|
|
10
|
+
* mode.
|
|
11
|
+
*
|
|
12
|
+
* Reads the same raised-surface tokens as `Card` — `--vpg-surface-raised`,
|
|
13
|
+
* `--vpg-border` — so a group of fields reads as the same material as a card, rather than as
|
|
14
|
+
* bare unstyled markup. `FieldSet` is an outer container (a field sits inside it, never the other
|
|
15
|
+
* way around), so per ADR-0012 its corner takes the outer step of the radius ladder,
|
|
16
|
+
* `--vpg-radius-lg`, not the inner step a field itself takes. The legend takes the same
|
|
17
|
+
* type-scale step as `FormField`'s label.
|
|
18
|
+
*
|
|
19
|
+
* A native `<legend>` inside a bordered `<fieldset>` is not a normal flow child: the browser lifts
|
|
20
|
+
* it out of the box's padding and straddles it across the border-block-start, cutting a notch the
|
|
21
|
+
* border does not paint through. That notch — not the surface, border or radius — is what reads as
|
|
22
|
+
* unstyled browser chrome unless the legend settles into it deliberately: no margin fighting the
|
|
23
|
+
* browser's own placement, and no padding on the legend either — the browser already anchors the
|
|
24
|
+
* rendered legend's inline-start edge at the fieldset's own padding edge, the same edge every
|
|
25
|
+
* child's content starts from, so a legend with `padding: 0` sets its text flush with the labels
|
|
26
|
+
* beneath it. Padding on the legend only pushes its text past that edge, out of line with them.
|
|
27
|
+
* The fieldset itself stays a plain block box (not flex) so the browser's own
|
|
28
|
+
* `max(padding-top, legend-block-size)` rule is the only thing sizing the gap above the first
|
|
29
|
+
* child; a flex `gap` would additionally count the legend as a flex item and double that space.
|
|
30
|
+
* The gap between the remaining children comes from a sibling-combinator margin instead of `gap`,
|
|
31
|
+
* since `gap` cannot skip the pair the legend is one half of.
|
|
32
|
+
*/
|
|
33
|
+
export declare const fieldSetStylesheet = "\n.vpg-fieldset {\n box-sizing: border-box;\n margin: 0;\n padding: var(--vpg-space-5);\n background-color: var(--vpg-surface-raised);\n border: 1px solid var(--vpg-border);\n border-radius: var(--vpg-radius-lg);\n}\n\n.vpg-fieldset:disabled {\n opacity: 0.55;\n}\n\n.vpg-fieldset > *:not(.vpg-fieldset-legend) ~ *:not(.vpg-fieldset-legend) {\n margin-top: var(--vpg-space-5);\n}\n\n.vpg-fieldset-legend {\n padding: 0;\n margin: 0;\n font-size: var(--vpg-font-size-sm);\n font-weight: 600;\n color: var(--vpg-ink);\n}\n";
|
|
34
|
+
//# sourceMappingURL=FieldSet.stylesheet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldSet.stylesheet.d.ts","sourceRoot":"","sources":["../../src/FieldSet/FieldSet.stylesheet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,kBAAkB,0hBAyB9B,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode, Ref } from "react";
|
|
2
|
+
export interface FieldShellProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
3
|
+
/**
|
|
4
|
+
* A ref to `.vpg-field-shell`, the bordered box itself. A floating surface anchored to the
|
|
5
|
+
* field — a listbox, a popover — positions against the whole field rather than the control
|
|
6
|
+
* inside it, so the ref lands on the element whose border the surface has to align with and
|
|
7
|
+
* whose width it has to match. `HTMLAttributes` carries no `ref`, so the prop is declared here.
|
|
8
|
+
*/
|
|
9
|
+
ref?: Ref<HTMLDivElement>;
|
|
10
|
+
/**
|
|
11
|
+
* An adornment rendered in `.vpg-field-shell-leading`, before the centre — an icon, a
|
|
12
|
+
* prefix, a currency symbol. Absent, the slot element is not rendered at all, so a shell with
|
|
13
|
+
* no adornment holds no empty wrapper.
|
|
14
|
+
*/
|
|
15
|
+
leading?: ReactNode;
|
|
16
|
+
/**
|
|
17
|
+
* An adornment rendered in `.vpg-field-shell-trailing`, after the centre — a unit, a
|
|
18
|
+
* spinner, an interactive button. A disabled button here is the adornment's own state and
|
|
19
|
+
* leaves the field undimmed; only the control dims the shell.
|
|
20
|
+
*/
|
|
21
|
+
trailing?: ReactNode;
|
|
22
|
+
/**
|
|
23
|
+
* The control the shell decorates, and any sibling that belongs inside the field's boundary —
|
|
24
|
+
* a chip row beside a trigger, for instance. The shell renders no field element of its own and
|
|
25
|
+
* needs no knowledge of what it wraps.
|
|
26
|
+
*/
|
|
27
|
+
children: ReactNode;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The chrome of a text-entry control: a bordered, rounded, surface-filled box that takes a focus
|
|
31
|
+
* ring, an accent border while the control's listbox is open (`aria-expanded="true"`), a danger
|
|
32
|
+
* border when the control is invalid and a dimmed treatment when it is disabled.
|
|
33
|
+
* It owns the box, its states, its height, its horizontal padding and its width; the control it
|
|
34
|
+
* wraps keeps its own element, class name, `className` and prop spread (ADR-0011).
|
|
35
|
+
*
|
|
36
|
+
* The DOM this renders is part of the component's API — `.vpg-field-shell` is the bordered
|
|
37
|
+
* box, and each slot is a child of it, the leading one before the centre and the trailing one
|
|
38
|
+
* after. Consumers style around those class names, so the shape is a commitment; the selectors
|
|
39
|
+
* that read focus, openness, invalidity and disabledness out of the control are internal mechanics.
|
|
40
|
+
*/
|
|
41
|
+
export declare function FieldShell({ leading, trailing, className, children, ref, ...rest }: FieldShellProps): import("react").JSX.Element;
|
|
42
|
+
//# sourceMappingURL=FieldShell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldShell.d.ts","sourceRoot":"","sources":["../../src/FieldShell/FieldShell.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAG5D,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC;IACvF;;;;;OAKG;IACH,GAAG,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IAC1B;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;;OAIG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,EAAE,eAAe,+BAwBnG"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<FieldShell>`'s own styles — the bordered box a text-entry control composes, its two adornment
|
|
3
|
+
* slots, and the states it reads out of the control it wraps.
|
|
4
|
+
*
|
|
5
|
+
* Injected as an inline `<style>` rather than a `.css` import so the package can stay
|
|
6
|
+
* `"sideEffects": false` (same approach as `TextField`'s and `Dropdown`'s stylesheets).
|
|
7
|
+
*
|
|
8
|
+
* Every `--vpg-*` property is *read* here through `var()` and never assigned inline by the
|
|
9
|
+
* component: an inline style declaration always wins over a stylesheet rule for the same property
|
|
10
|
+
* on the same element, so an inline `--vpg-surface` would permanently shadow the dark-mode
|
|
11
|
+
* reassignment in `@vipengele/react-tokens`'s base stylesheet and the shell would stop adapting to colour
|
|
12
|
+
* mode.
|
|
13
|
+
*
|
|
14
|
+
* Every measurement is a scale step: the minimum height is the size scale's default control step,
|
|
15
|
+
* the horizontal padding and the gap between the slots and the centre are spacing steps, and the
|
|
16
|
+
* corner is the radius ladder's inner step, which is the step a field takes (ADR-0012). A literal
|
|
17
|
+
* here is one more measurement agreeing with nothing, which is the drift `FieldShell` exists to
|
|
18
|
+
* end (ADR-0011).
|
|
19
|
+
*
|
|
20
|
+
* The step is a floor rather than a fixed height: a single-line control sits at exactly the step,
|
|
21
|
+
* and a centre that wraps onto a second line — a row of chips beside a trigger — grows the box
|
|
22
|
+
* instead of overflowing it.
|
|
23
|
+
*
|
|
24
|
+
* The state rules match `> ` — a direct child of the shell — so each reads the wrapped control and
|
|
25
|
+
* nothing deeper. An adornment slot holds an arbitrary subtree that can carry its own interactive
|
|
26
|
+
* elements, a reveal or clear button among them, and an unscoped `:has(:disabled)`,
|
|
27
|
+
* `:has([aria-invalid="true"])` or `:has([aria-expanded="true"])` treats one of those as the
|
|
28
|
+
* field's own state: the whole field dims because a button beside the control is off, or takes the
|
|
29
|
+
* accent border because a menu beside the control is open.
|
|
30
|
+
*/
|
|
31
|
+
export declare const fieldShellStylesheet = "\n.vpg-field-shell {\n display: flex;\n align-items: center;\n gap: var(--vpg-space-2);\n box-sizing: border-box;\n width: 100%;\n min-height: var(--vpg-size-md);\n padding: 0 var(--vpg-space-3);\n background-color: var(--vpg-surface);\n border: 1px solid var(--vpg-border-strong);\n border-radius: var(--vpg-radius-sm);\n color: var(--vpg-ink);\n transition: border-color var(--vpg-duration-fast) var(--vpg-ease-standard),\n box-shadow var(--vpg-duration-fast) var(--vpg-ease-standard),\n opacity var(--vpg-duration-fast) var(--vpg-ease-standard),\n background-color var(--vpg-duration-fast) var(--vpg-ease-standard);\n}\n\n/* The centre is whatever the caller passes as children \u2014 one control, or a pair of siblings such\n as a chip row beside a trigger. `min-width: 0` is what stops a flex item propagating its\n content's intrinsic width, so a wide centre shrinks inside the field rather than growing the\n field past what contains it. */\n.vpg-field-shell > *:not(.vpg-field-shell-leading, .vpg-field-shell-trailing) {\n flex: 0 1 auto;\n min-width: 0;\n}\n\n/* The centre's free space goes to its last element, and only to that one. A centre of one control\n is that element, so a lone `<input>` fills the field. A centre of several \u2014 a chip row, then a\n trigger \u2014 sizes every earlier element to its content and leaves the remainder to the trigger,\n where the caret goes; splitting the space evenly instead would stretch the chip row to half the\n field whatever it holds.\n\n `of` counts among the centre elements only, so the rule finds the last one whether or not a\n trailing slot follows it. */\n.vpg-field-shell > *:nth-last-child(1 of :not(.vpg-field-shell-leading, .vpg-field-shell-trailing)) {\n flex: 1;\n}\n\n.vpg-field-shell-leading,\n.vpg-field-shell-trailing {\n display: flex;\n align-items: center;\n flex: none;\n color: var(--vpg-ink-muted);\n}\n\n.vpg-field-shell:has(> :focus-visible) {\n border-color: var(--vpg-accent);\n box-shadow: 0 0 0 var(--vpg-focus-ring-width) var(--vpg-accent-ring);\n}\n\n/* The field whose control has its listbox open takes the accent border, so a pointer-opened\n combobox \u2014 which `:focus-visible` does not match \u2014 still reads as the field the listbox belongs\n to. Border only: the ring is keyboard focus's, and an open listbox does not claim it. The\n `:not()` hands an invalid field to the danger border outright, so which of the two wins never\n depends on the order these rules sit in or on the order stylesheets are injected. */\n.vpg-field-shell:has(> [aria-expanded=\"true\"]):not(:has(> [aria-invalid=\"true\"])) {\n border-color: var(--vpg-accent);\n}\n\n.vpg-field-shell:has(> [aria-invalid=\"true\"]) {\n border-color: var(--vpg-danger);\n}\n\n.vpg-field-shell:has(> [aria-invalid=\"true\"]):has(> :focus-visible) {\n box-shadow: 0 0 0 var(--vpg-focus-ring-width) var(--vpg-danger-ring);\n}\n\n.vpg-field-shell:has(> :disabled) {\n cursor: not-allowed;\n opacity: 0.55;\n}\n\n.vpg-field-shell:hover:not(:has(> :disabled)) {\n background-color: var(--vpg-surface-hover);\n}\n";
|
|
32
|
+
//# sourceMappingURL=FieldShell.stylesheet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldShell.stylesheet.d.ts","sourceRoot":"","sources":["../../src/FieldShell/FieldShell.stylesheet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,oBAAoB,ikGA8EhC,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode } from "react";
|
|
2
|
+
/** The subset of ARIA/labelling attributes `FormField` clones onto its single child. Every
|
|
3
|
+
* focusable control this package ships (native inputs, `Toggle`, `RadioButton`, and
|
|
4
|
+
* `Dropdown`'s trigger div) accepts all four as plain optional props. */
|
|
5
|
+
interface CloneableControlProps {
|
|
6
|
+
id?: string;
|
|
7
|
+
"aria-describedby"?: string;
|
|
8
|
+
"aria-invalid"?: boolean;
|
|
9
|
+
"aria-labelledby"?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface FormFieldProps {
|
|
12
|
+
/** The field's accessible name, rendered in a `<label>` and referenced from the control via
|
|
13
|
+
* `aria-labelledby` (applied unconditionally — see `children`'s doc comment). */
|
|
14
|
+
label: ReactNode;
|
|
15
|
+
/** Supplementary guidance, rendered below the control and wired into `aria-describedby`. */
|
|
16
|
+
hint?: ReactNode;
|
|
17
|
+
/** A validation message. Rendered below the control (after `hint`, when both are present) and
|
|
18
|
+
* wired into `aria-describedby`; its presence also sets `aria-invalid` on the control. */
|
|
19
|
+
error?: ReactNode;
|
|
20
|
+
/**
|
|
21
|
+
* Exactly one focusable control — a native input, `Toggle`, `RadioButton`, or `Dropdown`'s
|
|
22
|
+
* trigger. Not a group-shaped component like
|
|
23
|
+
* `RadioGroup`, which gets its accessible name from its own `aria-label` rather than a
|
|
24
|
+
* wrapping `FormField`.
|
|
25
|
+
*
|
|
26
|
+
* `FormField` clones `id`, `aria-describedby`, `aria-invalid`, and `aria-labelledby` onto this
|
|
27
|
+
* element. `aria-labelledby` is applied unconditionally, regardless of what element the child
|
|
28
|
+
* renders as: a `<label htmlFor>` only associates with labelable elements (`input`/`select`/
|
|
29
|
+
* `textarea`/`button`/`meter`/`output`/`progress`), so a non-labelable trigger (Dropdown's
|
|
30
|
+
* `<div role="combobox">`) would otherwise get no accessible name at all. Anything that isn't a
|
|
31
|
+
* single valid element — text, an array, a `Fragment`, `null`/`undefined` — throws, since there
|
|
32
|
+
* is no single node to attach the label and description to.
|
|
33
|
+
*/
|
|
34
|
+
children: ReactElement<CloneableControlProps>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Labels a single focusable control and wires up its description/validation state via
|
|
38
|
+
* `cloneElement` — the narrow labelling-attribute carve-out in
|
|
39
|
+
* `.agents/rules/wrap-trigger-never-clone.md`, not the span-wrap pattern: there is no
|
|
40
|
+
* floating-UI trigger here, just ARIA attributes that must land on the actual focusable element
|
|
41
|
+
* rather than an ancestor a screen reader never focuses.
|
|
42
|
+
*/
|
|
43
|
+
export declare function FormField({ label, hint, error, children }: FormFieldProps): import("react").JSX.Element;
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=FormField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormField.d.ts","sourceRoot":"","sources":["../../src/FormField/FormField.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA0C,KAAK,YAAY,EAAE,KAAK,SAAS,EAAS,MAAM,OAAO,CAAC;AAGzG;;yEAEyE;AACzE,UAAU,qBAAqB;IAC7B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B;qFACiF;IACjF,KAAK,EAAE,SAAS,CAAC;IACjB,4FAA4F;IAC5F,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;8FAC0F;IAC1F,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;;;;;;;;;;;OAaG;IACH,QAAQ,EAAE,YAAY,CAAC,qBAAqB,CAAC,CAAC;CAC/C;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,cAAc,+BAwDzE"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<FormField>`'s own styles, injected as an inline `<style>` rather than a `.css` import so the
|
|
3
|
+
* package can stay `"sideEffects": false` (same approach as `Card`'s and `Toggle`'s
|
|
4
|
+
* stylesheets).
|
|
5
|
+
*
|
|
6
|
+
* Every `--vpg-*` property is *read* here through `var()` and never assigned inline by the
|
|
7
|
+
* component: an inline style declaration always wins over a stylesheet rule for the same
|
|
8
|
+
* property on the same element, so an inline theme property would permanently shadow
|
|
9
|
+
* `@vipengele/react-tokens`'s dark-mode reassignment and this field would stop adapting to colour mode.
|
|
10
|
+
*
|
|
11
|
+
* The label, help and error text take steps of the type scale, and the error its colour from the
|
|
12
|
+
* danger family — the same mode-resolved red `Button`'s danger variant and an invalid field's
|
|
13
|
+
* border read.
|
|
14
|
+
*
|
|
15
|
+
* The vertical rhythm is two spacing steps, not one, because the label-to-control relationship and
|
|
16
|
+
* the control-to-hint relationship read differently: `--vpg-space-2` gives the label room to
|
|
17
|
+
* read as a heading for the control beneath it, while `--vpg-space-1` keeps a hint or error
|
|
18
|
+
* pinned tight under the control it describes. Both are tighter than the gap `FieldSet` puts
|
|
19
|
+
* between sibling fields (`--vpg-space-5`), so a column of fields reads as grouped units
|
|
20
|
+
* rather than an undifferentiated stack. `gap` cannot express this — it is uniform across every
|
|
21
|
+
* pair of flex children — so the rhythm is margins on the label and on the hint/error text instead.
|
|
22
|
+
*/
|
|
23
|
+
export declare const formFieldStylesheet = "\n.vpg-form-field {\n display: flex;\n flex-direction: column;\n}\n\n.vpg-form-field-label {\n margin: 0 0 var(--vpg-space-2);\n font-size: var(--vpg-font-size-sm);\n font-weight: 500;\n color: var(--vpg-ink);\n}\n\n.vpg-form-field-hint {\n margin-top: var(--vpg-space-1);\n font-size: var(--vpg-font-size-xs);\n color: var(--vpg-ink-muted);\n}\n\n.vpg-form-field-error {\n margin-top: var(--vpg-space-1);\n font-size: var(--vpg-font-size-xs);\n color: var(--vpg-danger);\n}\n";
|
|
24
|
+
//# sourceMappingURL=FormField.stylesheet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormField.stylesheet.d.ts","sourceRoot":"","sources":["../../src/FormField/FormField.stylesheet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,mBAAmB,6eAwB/B,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type TextFieldProps } from "../TextField/TextField.js";
|
|
2
|
+
export type PasswordInputProps = Omit<TextFieldProps, "type" | "trailing">;
|
|
3
|
+
/**
|
|
4
|
+
* A `TextField` whose native `type` toggles between `"password"` and `"text"` from a reveal
|
|
5
|
+
* button in the shell's trailing slot, so a consumer can check what they typed without retyping
|
|
6
|
+
* it. Composes `TextField` rather than `FieldShell` directly, reusing the one definition of "an
|
|
7
|
+
* input inside a shell" this package has (ADR-0011).
|
|
8
|
+
*
|
|
9
|
+
* Presentational only: no validation, no strength meter, no required indicator. The reveal
|
|
10
|
+
* button is a real `<button type="button">`, so it is reachable by keyboard and never removed
|
|
11
|
+
* from the accessibility tree, and toggling it never moves focus — the click handler only flips
|
|
12
|
+
* state, so a keyboard user stays on the button across a toggle.
|
|
13
|
+
*/
|
|
14
|
+
export declare function PasswordInput({ className, disabled, ...rest }: PasswordInputProps): import("react").JSX.Element;
|
|
15
|
+
//# sourceMappingURL=PasswordInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PasswordInput.d.ts","sourceRoot":"","sources":["../../src/PasswordInput/PasswordInput.tsx"],"names":[],"mappings":"AACA,OAAO,EAAa,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAG3E,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,cAAc,EAAE,MAAM,GAAG,UAAU,CAAC,CAAC;AAE3E;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,kBAAkB,+BAiCjF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<PasswordInput>`'s own styles, injected as an inline `<style>` rather than a `.css` import so
|
|
3
|
+
* the package can stay `"sideEffects": false` (same approach as every other component's
|
|
4
|
+
* stylesheet). This styles only the reveal button; the field's chrome and the input's own
|
|
5
|
+
* typography come from `FieldShell` and `TextField`, both already injected by the `TextField`
|
|
6
|
+
* this component composes.
|
|
7
|
+
*
|
|
8
|
+
* Every `--vpg-*` property is *read* here through `var()` and never assigned inline by the
|
|
9
|
+
* component: an inline style declaration always wins over a stylesheet rule for the same
|
|
10
|
+
* property on the same element, so an inline `--vpg-accent-ring` would permanently shadow
|
|
11
|
+
* the dark-mode reassignment in `@vipengele/react-tokens`'s base stylesheet and the button would stop
|
|
12
|
+
* adapting to colour mode.
|
|
13
|
+
*
|
|
14
|
+
* The button sits in `FieldShell`'s trailing slot, a direct child of the shell whose own state
|
|
15
|
+
* selectors read `> :focus-visible` and `> :disabled` off the shell's direct children only — the
|
|
16
|
+
* slot span, not the button inside it. So the button needs its own focus-visible ring; neither
|
|
17
|
+
* that ring nor the pointer cursor is inherited from the shell. Dimming is: `PasswordInput`
|
|
18
|
+
* disables the button exactly when it disables the input, so the shell's own `opacity` on
|
|
19
|
+
* `.vpg-field-shell` already fades the button along with the rest of the field, and this
|
|
20
|
+
* rule carries no `opacity` of its own — one would compound onto the shell's and read as more
|
|
21
|
+
* faded than the field around it.
|
|
22
|
+
*/
|
|
23
|
+
export declare const passwordInputStylesheet = "\n.vpg-password-input-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n border: none;\n border-radius: var(--vpg-radius-sm);\n padding: 0 var(--vpg-space-1);\n margin: 0;\n background: none;\n color: var(--vpg-ink-muted);\n font-family: inherit;\n font-size: var(--vpg-font-size-xs);\n line-height: 1;\n cursor: pointer;\n transition: color var(--vpg-duration-fast) var(--vpg-ease-standard);\n}\n\n.vpg-password-input-toggle:hover:not(:disabled) {\n color: var(--vpg-ink);\n}\n\n.vpg-password-input-toggle:focus-visible {\n outline: var(--vpg-focus-ring-width) solid var(--vpg-accent-ring);\n outline-offset: var(--vpg-focus-ring-offset);\n}\n\n.vpg-password-input-toggle:disabled {\n cursor: not-allowed;\n}\n";
|
|
24
|
+
//# sourceMappingURL=PasswordInput.stylesheet.d.ts.map
|