@plexui/ui 0.13.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/components/ButtonGroup/ButtonGroup.js +6 -0
- package/dist/es/components/ButtonGroup/ButtonGroup.js.map +1 -1
- package/dist/es/components/ButtonGroup/ButtonGroup.module.css +97 -11
- package/dist/es/components/Input/Input.js +5 -2
- package/dist/es/components/Input/Input.js.map +1 -1
- package/dist/es/components/Input/Input.module.css +87 -2
- package/dist/es/styles/variables-components.css +6 -0
- package/dist/types/components/ButtonGroup/ButtonGroup.d.ts +12 -0
- package/dist/types/components/Input/Input.d.ts +17 -2
- package/package.json +4 -1
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import clsx from "clsx";
|
|
4
|
+
import { Slot } from "radix-ui";
|
|
4
5
|
import {} from "react";
|
|
5
6
|
import { Separator } from "../Separator";
|
|
6
7
|
import s from "./ButtonGroup.module.css";
|
|
7
8
|
const ButtonGroupRoot = ({ orientation = "horizontal", className, ref, ...restProps }) => (_jsx("div", { ref: ref, role: "group", "data-slot": "button-group", "data-orientation": orientation, className: clsx(s.Root, className), ...restProps }));
|
|
8
9
|
const ButtonGroupSeparator = ({ className, orientation, ...restProps }) => (_jsx(Separator, { "data-slot": "button-group-separator", orientation: orientation ?? "vertical", className: clsx(s.Separator, className), ...restProps }));
|
|
10
|
+
const ButtonGroupText = ({ asChild = false, className, ref, ...restProps }) => {
|
|
11
|
+
const Comp = asChild ? Slot.Root : "div";
|
|
12
|
+
return (_jsx(Comp, { ref: ref, "data-slot": "button-group-text", className: clsx(s.Text, className), ...restProps }));
|
|
13
|
+
};
|
|
9
14
|
export const ButtonGroup = ButtonGroupRoot;
|
|
10
15
|
ButtonGroup.Separator = ButtonGroupSeparator;
|
|
16
|
+
ButtonGroup.Text = ButtonGroupText;
|
|
11
17
|
//# sourceMappingURL=ButtonGroup.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ButtonGroup.js","sourceRoot":"","sources":["../../../../src/components/ButtonGroup/ButtonGroup.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAsD,MAAM,OAAO,CAAA;AAC1E,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAkBxC,MAAM,eAAe,GAAG,CAAC,EACvB,WAAW,GAAG,YAAY,EAC1B,SAAS,EACT,GAAG,EACH,GAAG,SAAS,EACK,EAAE,EAAE,CAAC,CACtB,cACE,GAAG,EAAE,GAAG,EACR,IAAI,EAAC,OAAO,eACF,cAAc,sBACN,WAAW,EAC7B,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,KAC9B,SAAS,GACb,CACH,CAAA;AAiBD,MAAM,oBAAoB,GAAG,CAAC,EAC5B,SAAS,EACT,WAAW,EACX,GAAG,SAAS,EACc,EAAE,EAAE,CAAC,CAC/B,KAAC,SAAS,iBACE,wBAAwB,EAClC,WAAW,EAAE,WAAW,IAAI,UAAU,EACtC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,KACnC,SAAS,GACb,CACH,CAAA;
|
|
1
|
+
{"version":3,"file":"ButtonGroup.js","sourceRoot":"","sources":["../../../../src/components/ButtonGroup/ButtonGroup.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAA;AAC/B,OAAO,EAAsD,MAAM,OAAO,CAAA;AAC1E,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAkBxC,MAAM,eAAe,GAAG,CAAC,EACvB,WAAW,GAAG,YAAY,EAC1B,SAAS,EACT,GAAG,EACH,GAAG,SAAS,EACK,EAAE,EAAE,CAAC,CACtB,cACE,GAAG,EAAE,GAAG,EACR,IAAI,EAAC,OAAO,eACF,cAAc,sBACN,WAAW,EAC7B,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,KAC9B,SAAS,GACb,CACH,CAAA;AAiBD,MAAM,oBAAoB,GAAG,CAAC,EAC5B,SAAS,EACT,WAAW,EACX,GAAG,SAAS,EACc,EAAE,EAAE,CAAC,CAC/B,KAAC,SAAS,iBACE,wBAAwB,EAClC,WAAW,EAAE,WAAW,IAAI,UAAU,EACtC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,KACnC,SAAS,GACb,CACH,CAAA;AAiBD,MAAM,eAAe,GAAG,CAAC,EACvB,OAAO,GAAG,KAAK,EACf,SAAS,EACT,GAAG,EACH,GAAG,SAAS,EACS,EAAE,EAAE;IACzB,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAA;IACxC,OAAO,CACL,KAAC,IAAI,IACH,GAAG,EAAE,GAAG,eACE,mBAAmB,EAC7B,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,KAC9B,SAAS,GACb,CACH,CAAA;AACH,CAAC,CAAA;AAWD,MAAM,CAAC,MAAM,WAAW,GAAG,eAAuC,CAAA;AAClE,WAAW,CAAC,SAAS,GAAG,oBAAoB,CAAA;AAC5C,WAAW,CAAC,IAAI,GAAG,eAAe,CAAA","sourcesContent":["\"use client\"\n\nimport clsx from \"clsx\"\nimport { Slot } from \"radix-ui\"\nimport { type ComponentProps, type HTMLAttributes, type Ref } from \"react\"\nimport { Separator } from \"../Separator\"\nimport s from \"./ButtonGroup.module.css\"\n\n/* ------------------------------------------------------------------ */\n/* Root */\n/* ------------------------------------------------------------------ */\n\nexport type ButtonGroupOrientation = \"horizontal\" | \"vertical\"\n\nexport type ButtonGroupProps = HTMLAttributes<HTMLDivElement> & {\n /**\n * Direction of the joined buttons.\n * @default \"horizontal\"\n */\n orientation?: ButtonGroupOrientation\n className?: string\n ref?: Ref<HTMLDivElement>\n}\n\nconst ButtonGroupRoot = ({\n orientation = \"horizontal\",\n className,\n ref,\n ...restProps\n}: ButtonGroupProps) => (\n <div\n ref={ref}\n role=\"group\"\n data-slot=\"button-group\"\n data-orientation={orientation}\n className={clsx(s.Root, className)}\n {...restProps}\n />\n)\n\n/* ------------------------------------------------------------------ */\n/* Separator — thin divider between items */\n/* ------------------------------------------------------------------ */\n\nexport type ButtonGroupSeparatorProps = Omit<\n ComponentProps<typeof Separator>,\n \"orientation\"\n> & {\n /**\n * When omitted, flips to match the surrounding ButtonGroup axis.\n * Pass explicitly to override (rare).\n */\n orientation?: ButtonGroupOrientation\n}\n\nconst ButtonGroupSeparator = ({\n className,\n orientation,\n ...restProps\n}: ButtonGroupSeparatorProps) => (\n <Separator\n data-slot=\"button-group-separator\"\n orientation={orientation ?? \"vertical\"}\n className={clsx(s.Separator, className)}\n {...restProps}\n />\n)\n\n/* ------------------------------------------------------------------ */\n/* Text — muted label/pill sharing the group's joined geometry */\n/* ------------------------------------------------------------------ */\n\nexport type ButtonGroupTextProps = HTMLAttributes<HTMLDivElement> & {\n /**\n * Swap the rendered element for a child (label, span, etc.) while\n * keeping the group's visual treatment.\n * @default false\n */\n asChild?: boolean\n className?: string\n ref?: Ref<HTMLDivElement>\n}\n\nconst ButtonGroupText = ({\n asChild = false,\n className,\n ref,\n ...restProps\n}: ButtonGroupTextProps) => {\n const Comp = asChild ? Slot.Root : \"div\"\n return (\n <Comp\n ref={ref}\n data-slot=\"button-group-text\"\n className={clsx(s.Text, className)}\n {...restProps}\n />\n )\n}\n\n/* ------------------------------------------------------------------ */\n/* Export — compound API */\n/* ------------------------------------------------------------------ */\n\ntype ButtonGroupComponent = typeof ButtonGroupRoot & {\n Separator: typeof ButtonGroupSeparator\n Text: typeof ButtonGroupText\n}\n\nexport const ButtonGroup = ButtonGroupRoot as ButtonGroupComponent\nButtonGroup.Separator = ButtonGroupSeparator\nButtonGroup.Text = ButtonGroupText\n"]}
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
width: -moz-fit-content;
|
|
5
5
|
width: fit-content;
|
|
6
6
|
|
|
7
|
-
/* Inputs inside a ButtonGroup share the row and flex to fill space
|
|
7
|
+
/* Inputs inside a ButtonGroup share the row and flex to fill space —
|
|
8
|
+
the rule targets raw <input>, the Plex Input Container (data-slot),
|
|
9
|
+
and a nested sub-ButtonGroup that wraps an Input (e.g. Input Group
|
|
10
|
+
pattern: [leading action] [Input + inline addon]). */
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
/* Let focused item sit on top of neighbors so the ring isn't clipped */
|
|
@@ -28,16 +31,40 @@
|
|
|
28
31
|
every non-first child, so the inset shadow of item N+1 doesn't sit
|
|
29
32
|
next to the inset shadow of item N (which would read as a doubled
|
|
30
33
|
2px line). Focus ring lives on ::after, so clip-path on ::before
|
|
31
|
-
leaves the focus ring intact.
|
|
32
|
-
|
|
34
|
+
leaves the focus ring intact.
|
|
35
|
+
|
|
36
|
+
Separators are excluded — they need to render visibly BETWEEN items,
|
|
37
|
+
not be overlapped into oblivion. Adjacent items to a separator keep
|
|
38
|
+
their edge radii stripped, but skip the -1px/-clip trickery. */
|
|
39
|
+
.Root[data-orientation="horizontal"] > *:not(:first-child):not([data-slot="button-group-separator"]) {
|
|
33
40
|
border-start-start-radius: 0;
|
|
34
41
|
border-end-start-radius: 0;
|
|
42
|
+
}
|
|
43
|
+
.Root[data-orientation="horizontal"]
|
|
44
|
+
> *:not(:first-child):not([data-slot="button-group-separator"]):not(
|
|
45
|
+
[data-slot="button-group-separator"] + *
|
|
46
|
+
) {
|
|
35
47
|
margin-inline-start: -1px;
|
|
36
48
|
}
|
|
37
|
-
.Root[data-orientation="horizontal"]
|
|
49
|
+
.Root[data-orientation="horizontal"]
|
|
50
|
+
> *:not(:first-child):not([data-slot="button-group-separator"]):not(
|
|
51
|
+
[data-slot="button-group-separator"] + *
|
|
52
|
+
)::before {
|
|
38
53
|
clip-path: inset(0 0 0 1px);
|
|
39
54
|
}
|
|
40
|
-
.Root[data-orientation="horizontal"]
|
|
55
|
+
.Root[data-orientation="horizontal"]
|
|
56
|
+
> *:not(:last-child):not([data-slot="button-group-separator"]):not(
|
|
57
|
+
:has(+ [data-slot="button-group-separator"])
|
|
58
|
+
) {
|
|
59
|
+
border-start-end-radius: 0;
|
|
60
|
+
border-end-end-radius: 0;
|
|
61
|
+
}
|
|
62
|
+
/* Strip the edge radius on items sitting directly next to a separator */
|
|
63
|
+
.Root[data-orientation="horizontal"] > [data-slot="button-group-separator"] + * {
|
|
64
|
+
border-start-start-radius: 0;
|
|
65
|
+
border-end-start-radius: 0;
|
|
66
|
+
}
|
|
67
|
+
.Root[data-orientation="horizontal"] > *:has(+ [data-slot="button-group-separator"]) {
|
|
41
68
|
border-start-end-radius: 0;
|
|
42
69
|
border-end-end-radius: 0;
|
|
43
70
|
}
|
|
@@ -46,28 +73,87 @@
|
|
|
46
73
|
.Root[data-orientation="vertical"] {
|
|
47
74
|
flex-direction: column;
|
|
48
75
|
}
|
|
49
|
-
.Root[data-orientation="vertical"] > *:not(:first-child) {
|
|
76
|
+
.Root[data-orientation="vertical"] > *:not(:first-child):not([data-slot="button-group-separator"]) {
|
|
50
77
|
border-start-start-radius: 0;
|
|
51
78
|
border-start-end-radius: 0;
|
|
79
|
+
}
|
|
80
|
+
.Root[data-orientation="vertical"]
|
|
81
|
+
> *:not(:first-child):not([data-slot="button-group-separator"]):not(
|
|
82
|
+
[data-slot="button-group-separator"] + *
|
|
83
|
+
) {
|
|
52
84
|
margin-block-start: -1px;
|
|
53
85
|
}
|
|
54
|
-
.Root[data-orientation="vertical"]
|
|
86
|
+
.Root[data-orientation="vertical"]
|
|
87
|
+
> *:not(:first-child):not([data-slot="button-group-separator"]):not(
|
|
88
|
+
[data-slot="button-group-separator"] + *
|
|
89
|
+
)::before {
|
|
55
90
|
clip-path: inset(1px 0 0 0);
|
|
56
91
|
}
|
|
57
|
-
.Root[data-orientation="vertical"]
|
|
92
|
+
.Root[data-orientation="vertical"]
|
|
93
|
+
> *:not(:last-child):not([data-slot="button-group-separator"]):not(
|
|
94
|
+
:has(+ [data-slot="button-group-separator"])
|
|
95
|
+
) {
|
|
96
|
+
border-end-start-radius: 0;
|
|
97
|
+
border-end-end-radius: 0;
|
|
98
|
+
}
|
|
99
|
+
.Root[data-orientation="vertical"] > [data-slot="button-group-separator"] + * {
|
|
100
|
+
border-start-start-radius: 0;
|
|
101
|
+
border-start-end-radius: 0;
|
|
102
|
+
}
|
|
103
|
+
.Root[data-orientation="vertical"] > *:has(+ [data-slot="button-group-separator"]) {
|
|
58
104
|
border-end-start-radius: 0;
|
|
59
105
|
border-end-end-radius: 0;
|
|
60
106
|
}
|
|
61
107
|
.Root > input,
|
|
62
|
-
.Root > [data-slot="input
|
|
108
|
+
.Root > [data-slot="input"],
|
|
109
|
+
.Root > [data-slot="button-group"]:has(> [data-slot="input"]) {
|
|
63
110
|
flex: 1 1 auto;
|
|
64
|
-
}/*
|
|
111
|
+
}/* Text — a muted label pill that slots into a ButtonGroup alongside
|
|
112
|
+
Buttons. Uses an inset box-shadow border (same trick Button uses for
|
|
113
|
+
variant="outline") so the group's edge-clipping rules (::before
|
|
114
|
+
clip-path) also work here and collapse the doubled 1px edge. */.Text {
|
|
115
|
+
position: relative;
|
|
116
|
+
display: inline-flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
gap: var(--spacing-2);
|
|
119
|
+
padding-inline: var(--spacing-4);
|
|
120
|
+
align-self: stretch;
|
|
121
|
+
background: var(--button-group-text-background);
|
|
122
|
+
color: var(--button-group-text-color);
|
|
123
|
+
font-size: var(--button-group-text-font-size);
|
|
124
|
+
font-weight: var(--button-group-text-font-weight);
|
|
125
|
+
font-family: var(--font-family-text);
|
|
126
|
+
line-height: 1;
|
|
127
|
+
white-space: nowrap;
|
|
128
|
+
border-radius: var(--radius-md);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.Text::before {
|
|
132
|
+
content: "";
|
|
133
|
+
position: absolute;
|
|
134
|
+
inset: 0;
|
|
135
|
+
border-radius: inherit;
|
|
136
|
+
box-shadow: 0 0 0 1px var(--button-group-text-border-color) inset;
|
|
137
|
+
pointer-events: none;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.Text svg {
|
|
141
|
+
width: 1em;
|
|
142
|
+
height: 1em;
|
|
143
|
+
flex-shrink: 0;
|
|
144
|
+
}/* Separator used between items inside a ButtonGroup.
|
|
145
|
+
Sits above the neighbor buttons (z-index) so the 1px line isn't
|
|
146
|
+
visually swallowed by the adjacent box-shadow borders. Parent-scoped
|
|
147
|
+
selector beats the base Separator class so the stronger color wins. */.Root > .Separator {
|
|
148
|
+
position: relative;
|
|
149
|
+
z-index: 1;
|
|
65
150
|
align-self: stretch;
|
|
66
151
|
margin: 0;
|
|
67
152
|
flex-shrink: 0;
|
|
153
|
+
background: var(--button-group-separator-color);
|
|
68
154
|
}
|
|
69
155
|
|
|
70
|
-
.Separator[data-orientation="vertical"] {
|
|
156
|
+
.Root > .Separator[data-orientation="vertical"] {
|
|
71
157
|
height: auto;
|
|
72
158
|
}
|
|
73
159
|
}
|
|
@@ -6,7 +6,7 @@ import { mergeRefs } from "react-merge-refs";
|
|
|
6
6
|
import {} from "../../types";
|
|
7
7
|
import { X } from "../Icon";
|
|
8
8
|
import s from "./Input.module.css";
|
|
9
|
-
|
|
9
|
+
const InputRoot = (props) => {
|
|
10
10
|
const inputRef = useRef(null);
|
|
11
11
|
const inputIdSuffix = useId();
|
|
12
12
|
// Prevent 1Password from appearing: 1Password won't display if it thinks this field is for searching
|
|
@@ -81,7 +81,7 @@ export const Input = (props) => {
|
|
|
81
81
|
onAutofill?.();
|
|
82
82
|
}
|
|
83
83
|
};
|
|
84
|
-
return (_jsxs("div", { className: clsx(s.Container, className), "data-variant": variant, "data-size": size, "data-gutter-size": gutterSize, "data-focused": focused, "data-disabled": disabled ? "" : undefined, "data-readonly": readOnly ? "" : undefined, "data-invalid": invalid ? "" : undefined, "data-pill": pill ? "" : undefined, "data-optically-align": opticallyAlign, "data-has-start-adornment": StartAdornment ? "" : undefined, "data-has-end-adornment": EndAdornment || showClearButton ? "" : undefined, onMouseDown: handleMouseDown, children: [StartAdornment, _jsx("input", { ...restProps, ref: mergeRefs([ref, inputRef]), id: id || (allowAutofillExtensions ? undefined : onePasswordPreventionId), className: s.Input, type: type, name: name, value: value, defaultValue: defaultValue, readOnly: readOnly, disabled: disabled, onFocus: (evt) => {
|
|
84
|
+
return (_jsxs("div", { className: clsx(s.Container, className), "data-slot": "input", "data-variant": variant, "data-size": size, "data-gutter-size": gutterSize, "data-focused": focused, "data-disabled": disabled ? "" : undefined, "data-readonly": readOnly ? "" : undefined, "data-invalid": invalid ? "" : undefined, "data-pill": pill ? "" : undefined, "data-optically-align": opticallyAlign, "data-has-start-adornment": StartAdornment ? "" : undefined, "data-has-end-adornment": EndAdornment || showClearButton ? "" : undefined, onMouseDown: handleMouseDown, children: [StartAdornment, _jsx("input", { ...restProps, ref: mergeRefs([ref, inputRef]), id: id || (allowAutofillExtensions ? undefined : onePasswordPreventionId), className: s.Input, type: type, name: name, value: value, defaultValue: defaultValue, readOnly: readOnly, disabled: disabled, onFocus: (evt) => {
|
|
85
85
|
setFocused(true);
|
|
86
86
|
onFocus?.(evt);
|
|
87
87
|
}, onBlur: (evt) => {
|
|
@@ -89,4 +89,7 @@ export const Input = (props) => {
|
|
|
89
89
|
onBlur?.(evt);
|
|
90
90
|
}, onAnimationStart: handleAnimationStart, "data-lpignore": allowAutofillExtensions ? undefined : true, "data-1p-ignore": allowAutofillExtensions ? undefined : true }), showClearButton && (_jsx("button", { type: "button", "aria-label": "Clear input", className: s.Clear, onClick: onClear, children: _jsx(X, {}) })), EndAdornment] }));
|
|
91
91
|
};
|
|
92
|
+
const InputAdornmentButton = ({ className, type = "button", color, ref, ...restProps }) => (_jsx("button", { ref: ref, type: type, className: clsx(s.AdornmentButton, className), "data-color": color, ...restProps }));
|
|
93
|
+
export const Input = InputRoot;
|
|
94
|
+
Input.AdornmentButton = InputAdornmentButton;
|
|
92
95
|
//# sourceMappingURL=Input.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Input.js","sourceRoot":"","sources":["../../../../src/components/Input/Input.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\"\n\nimport clsx from \"clsx\"\nimport { useEffect, useId, useRef, useState } from \"react\"\nimport { mergeRefs } from \"react-merge-refs\"\nimport { type ControlSize, type Sizes, type Variants } from \"../../types\"\nimport { X } from \"../Icon\"\nimport s from \"./Input.module.css\"\n\nexport type InputProps = {\n /**\n * Visual style of the input\n * @default outline\n */\n variant?: Variants<\"outline\" | \"soft\">\n /**\n * Controls the size of the input\n *\n * | 3xs | 2xs | xs | sm | md | lg | xl | 2xl | 3xl |\n * | ------- | ------- | ------- | ------- | ------- | ------- | ------- | ------- | ------- |\n * | `22px` | `24px` | `26px` | `28px` | `32px` | `36px` | `40px` | `44px` | `48px` |\n *\n * @default md\n */\n size?: ControlSize\n /**\n * Controls gutter on the edges of the input, defaults to value from `size`.\n *\n * | 2xs | xs | sm | md | lg | xl |\n * | ------ | ------ | ------ | ------ | ------ | ------ |\n * | `6px` | `8px` | `10px` | `12px` | `14px` | `16px` |\n */\n gutterSize?: Sizes<\"2xs\" | \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\">\n /**\n * Disables the select visually and from interactions\n * @default false\n */\n disabled?: boolean\n /**\n * Mark the input as invalid\n * @default false\n */\n invalid?: boolean\n /**\n * Allow autofill extensions to appear in the input\n * @default false\n */\n allowAutofillExtensions?: boolean\n /**\n * Select all contents of the input when mounted.\n * @default false\n */\n autoSelect?: boolean\n /** Callback invoked when the input is autofilled by the browser */\n onAutofill?: () => void\n /** Content rendered at the start of the input */\n startAdornment?: React.ReactNode\n /** Content rendered at the end of the input */\n endAdornment?: React.ReactNode\n /**\n * Callback invoked when the clear button is clicked.\n * When provided, displays a clear button that appears when the input has a value.\n */\n onClear?: () => void\n /**\n * Determines if the button should be a fully rounded pill shape\n * @default false\n */\n pill?: boolean\n /**\n * Applies a negative margin using the current gutter to optically align the input\n * with surrounding content.\n */\n opticallyAlign?: \"start\" | \"end\"\n /** Ref for the input */\n ref?: React.Ref<HTMLInputElement | null>\n} & Omit<React.InputHTMLAttributes<HTMLInputElement>, \"disabled\" | \"size\">\n\nexport const Input = (props: InputProps) => {\n const inputRef = useRef<HTMLInputElement | null>(null)\n const inputIdSuffix = useId()\n // Prevent 1Password from appearing: 1Password won't display if it thinks this field is for searching\n // Allow user to override this for connecting to labels.\n const onePasswordPreventionId = `search-ui-input-${inputIdSuffix}`\n const {\n id,\n name,\n type = \"text\",\n variant = \"outline\",\n size = \"md\",\n gutterSize,\n className,\n autoComplete,\n disabled = false,\n readOnly = false,\n invalid = false,\n // Default to `true` when type=\"password\" or presence of `name`\n allowAutofillExtensions = type === \"password\" || !!name,\n onFocus,\n onBlur,\n onAnimationStart,\n onAutofill,\n autoSelect,\n startAdornment: StartAdornment,\n endAdornment: EndAdornment,\n onClear,\n pill,\n opticallyAlign,\n ref,\n value,\n defaultValue,\n ...restProps\n } = props\n\n // Determine if clear button should be shown\n const hasValue = value !== undefined ? !!value : !!defaultValue\n const showClearButton = !!onClear && hasValue && !disabled && !readOnly\n // Redirect clicks on the container and adornments\n const handleMouseDown = (evt: React.MouseEvent<HTMLDivElement>) => {\n const input = inputRef.current\n // Bail out if the target is not an element or the textarea is not found\n if (!evt.target || !(evt.target instanceof Element) || !input) {\n return\n }\n // Bail out if the target is inside the textarea\n if (input.contains(evt.target)) {\n return\n }\n // Bail out if the target is a button\n if (evt.target.closest(\"button, [type='button'], [role='button'], [role='menuitem']\")) {\n return\n }\n\n evt.preventDefault()\n // If the textarea is not focused, focus it\n if (document.activeElement !== input) {\n input.focus()\n }\n\n const { left, top } = input.getBoundingClientRect()\n const { clientX, clientY } = evt\n // \"Before\" the input: top/left\n const isBefore = clientY < top || clientX < left\n\n // If this is the first click, clear the selection.\n if (evt.detail === 1) {\n if (isBefore) {\n input.setSelectionRange(0, 0)\n } else {\n const length = input.value.length\n input.setSelectionRange(length, length)\n }\n }\n // If this is the second click, select the first or last word\n else if (evt.detail === 2) {\n // Split the textarea value into words and non-word characters\n const words = input.value.match(/\\w+|[^\\w\\s]/g) || []\n const selectedWord = isBefore ? words.at(0) : words.at(-1)\n if (selectedWord) {\n const wordIndex = isBefore\n ? input.value.indexOf(selectedWord)\n : input.value.lastIndexOf(selectedWord)\n input.setSelectionRange(wordIndex, wordIndex + selectedWord.length)\n }\n }\n // If this is the third click or greater, select the entire input\n else {\n input.select()\n }\n }\n\n const [focused, setFocused] = useState<boolean>(false)\n\n useEffect(() => {\n if (autoSelect) {\n inputRef.current?.select()\n }\n }, [autoSelect])\n\n const handleAnimationStart = (evt: React.AnimationEvent<HTMLInputElement>) => {\n onAnimationStart?.(evt)\n if (evt.animationName === \"native-autofill-in\") {\n onAutofill?.()\n }\n }\n\n return (\n <div\n className={clsx(s.Container, className)}\n data-variant={variant}\n data-size={size}\n data-gutter-size={gutterSize}\n data-focused={focused}\n data-disabled={disabled ? \"\" : undefined}\n data-readonly={readOnly ? \"\" : undefined}\n data-invalid={invalid ? \"\" : undefined}\n data-pill={pill ? \"\" : undefined}\n data-optically-align={opticallyAlign}\n data-has-start-adornment={StartAdornment ? \"\" : undefined}\n data-has-end-adornment={EndAdornment || showClearButton ? \"\" : undefined}\n onMouseDown={handleMouseDown}\n >\n {StartAdornment}\n <input\n {...restProps}\n ref={mergeRefs([ref, inputRef])}\n id={id || (allowAutofillExtensions ? undefined : onePasswordPreventionId)}\n className={s.Input}\n type={type}\n name={name}\n value={value}\n defaultValue={defaultValue}\n readOnly={readOnly}\n disabled={disabled}\n onFocus={(evt) => {\n setFocused(true)\n onFocus?.(evt)\n }}\n onBlur={(evt) => {\n setFocused(false)\n onBlur?.(evt)\n }}\n onAnimationStart={handleAnimationStart}\n // Prevent LastPass and 1Password from appearing\n data-lpignore={allowAutofillExtensions ? undefined : true}\n data-1p-ignore={allowAutofillExtensions ? undefined : true}\n />\n {showClearButton && (\n <button type=\"button\" aria-label=\"Clear input\" className={s.Clear} onClick={onClear}>\n <X />\n </button>\n )}\n {EndAdornment}\n </div>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"Input.js","sourceRoot":"","sources":["../../../../src/components/Input/Input.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,EAAmE,MAAM,aAAa,CAAA;AAC7F,OAAO,EAAE,CAAC,EAAE,MAAM,SAAS,CAAA;AAC3B,OAAO,CAAC,MAAM,oBAAoB,CAAA;AAuElC,MAAM,SAAS,GAAG,CAAC,KAAiB,EAAE,EAAE;IACtC,MAAM,QAAQ,GAAG,MAAM,CAA0B,IAAI,CAAC,CAAA;IACtD,MAAM,aAAa,GAAG,KAAK,EAAE,CAAA;IAC7B,qGAAqG;IACrG,wDAAwD;IACxD,MAAM,uBAAuB,GAAG,mBAAmB,aAAa,EAAE,CAAA;IAClE,MAAM,EACJ,EAAE,EACF,IAAI,EACJ,IAAI,GAAG,MAAM,EACb,OAAO,GAAG,SAAS,EACnB,IAAI,GAAG,IAAI,EACX,UAAU,EACV,SAAS,EACT,YAAY,EACZ,QAAQ,GAAG,KAAK,EAChB,QAAQ,GAAG,KAAK,EAChB,OAAO,GAAG,KAAK;IACf,+DAA+D;IAC/D,uBAAuB,GAAG,IAAI,KAAK,UAAU,IAAI,CAAC,CAAC,IAAI,EACvD,OAAO,EACP,MAAM,EACN,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,cAAc,EAAE,cAAc,EAC9B,YAAY,EAAE,YAAY,EAC1B,OAAO,EACP,IAAI,EACJ,cAAc,EACd,GAAG,EACH,KAAK,EACL,YAAY,EACZ,GAAG,SAAS,EACb,GAAG,KAAK,CAAA;IAET,4CAA4C;IAC5C,MAAM,QAAQ,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAA;IAC/D,MAAM,eAAe,GAAG,CAAC,CAAC,OAAO,IAAI,QAAQ,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAA;IACvE,kDAAkD;IAClD,MAAM,eAAe,GAAG,CAAC,GAAqC,EAAE,EAAE;QAChE,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAA;QAC9B,wEAAwE;QACxE,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,YAAY,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9D,OAAM;QACR,CAAC;QACD,gDAAgD;QAChD,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/B,OAAM;QACR,CAAC;QACD,qCAAqC;QACrC,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,6DAA6D,CAAC,EAAE,CAAC;YACtF,OAAM;QACR,CAAC;QAED,GAAG,CAAC,cAAc,EAAE,CAAA;QACpB,2CAA2C;QAC3C,IAAI,QAAQ,CAAC,aAAa,KAAK,KAAK,EAAE,CAAC;YACrC,KAAK,CAAC,KAAK,EAAE,CAAA;QACf,CAAC;QAED,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC,qBAAqB,EAAE,CAAA;QACnD,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,GAAG,CAAA;QAChC,+BAA+B;QAC/B,MAAM,QAAQ,GAAG,OAAO,GAAG,GAAG,IAAI,OAAO,GAAG,IAAI,CAAA;QAEhD,mDAAmD;QACnD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrB,IAAI,QAAQ,EAAE,CAAC;gBACb,KAAK,CAAC,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;YAC/B,CAAC;iBAAM,CAAC;gBACN,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAA;gBACjC,KAAK,CAAC,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;YACzC,CAAC;QACH,CAAC;QACD,6DAA6D;aACxD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,8DAA8D;YAC9D,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,EAAE,CAAA;YACrD,MAAM,YAAY,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;YAC1D,IAAI,YAAY,EAAE,CAAC;gBACjB,MAAM,SAAS,GAAG,QAAQ;oBACxB,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC;oBACnC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,YAAY,CAAC,CAAA;gBACzC,KAAK,CAAC,iBAAiB,CAAC,SAAS,EAAE,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,CAAA;YACrE,CAAC;QACH,CAAC;QACD,iEAAiE;aAC5D,CAAC;YACJ,KAAK,CAAC,MAAM,EAAE,CAAA;QAChB,CAAC;IACH,CAAC,CAAA;IAED,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAU,KAAK,CAAC,CAAA;IAEtD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,UAAU,EAAE,CAAC;YACf,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAA;QAC5B,CAAC;IACH,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;IAEhB,MAAM,oBAAoB,GAAG,CAAC,GAA2C,EAAE,EAAE;QAC3E,gBAAgB,EAAE,CAAC,GAAG,CAAC,CAAA;QACvB,IAAI,GAAG,CAAC,aAAa,KAAK,oBAAoB,EAAE,CAAC;YAC/C,UAAU,EAAE,EAAE,CAAA;QAChB,CAAC;IACH,CAAC,CAAA;IAED,OAAO,CACL,eACE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,eAC7B,OAAO,kBACH,OAAO,eACV,IAAI,sBACG,UAAU,kBACd,OAAO,mBACN,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,mBACzB,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,kBAC1B,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,eAC3B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,0BACV,cAAc,8BACV,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,4BACjC,YAAY,IAAI,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,EACxE,WAAW,EAAE,eAAe,aAE3B,cAAc,EACf,mBACM,SAAS,EACb,GAAG,EAAE,SAAS,CAAC,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,EAC/B,EAAE,EAAE,EAAE,IAAI,CAAC,uBAAuB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,uBAAuB,CAAC,EACzE,SAAS,EAAE,CAAC,CAAC,KAAK,EAClB,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,KAAK,EACZ,YAAY,EAAE,YAAY,EAC1B,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;oBACf,UAAU,CAAC,IAAI,CAAC,CAAA;oBAChB,OAAO,EAAE,CAAC,GAAG,CAAC,CAAA;gBAChB,CAAC,EACD,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE;oBACd,UAAU,CAAC,KAAK,CAAC,CAAA;oBACjB,MAAM,EAAE,CAAC,GAAG,CAAC,CAAA;gBACf,CAAC,EACD,gBAAgB,EAAE,oBAAoB,mBAEvB,uBAAuB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,oBACzC,uBAAuB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,GAC1D,EACD,eAAe,IAAI,CAClB,iBAAQ,IAAI,EAAC,QAAQ,gBAAY,aAAa,EAAC,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,YACjF,KAAC,CAAC,KAAG,GACE,CACV,EACA,YAAY,IACT,CACP,CAAA;AACH,CAAC,CAAA;AAsBD,MAAM,oBAAoB,GAAG,CAAC,EAC5B,SAAS,EACT,IAAI,GAAG,QAAQ,EACf,KAAK,EACL,GAAG,EACH,GAAG,SAAS,EACc,EAAE,EAAE,CAAC,CAC/B,iBACE,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,eAAe,EAAE,SAAS,CAAC,gBACjC,KAAK,KACb,SAAS,GACb,CACH,CAAA;AAMD,MAAM,CAAC,MAAM,KAAK,GAAG,SAA2B,CAAA;AAChD,KAAK,CAAC,eAAe,GAAG,oBAAoB,CAAA","sourcesContent":["\"use client\"\n\nimport clsx from \"clsx\"\nimport { useEffect, useId, useRef, useState } from \"react\"\nimport { mergeRefs } from \"react-merge-refs\"\nimport { type ControlSize, type SemanticColor, type Sizes, type Variants } from \"../../types\"\nimport { X } from \"../Icon\"\nimport s from \"./Input.module.css\"\n\nexport type InputProps = {\n /**\n * Visual style of the input\n * @default outline\n */\n variant?: Variants<\"outline\" | \"soft\">\n /**\n * Controls the size of the input\n *\n * | 3xs | 2xs | xs | sm | md | lg | xl | 2xl | 3xl |\n * | ------- | ------- | ------- | ------- | ------- | ------- | ------- | ------- | ------- |\n * | `22px` | `24px` | `26px` | `28px` | `32px` | `36px` | `40px` | `44px` | `48px` |\n *\n * @default md\n */\n size?: ControlSize\n /**\n * Controls gutter on the edges of the input, defaults to value from `size`.\n *\n * | 2xs | xs | sm | md | lg | xl |\n * | ------ | ------ | ------ | ------ | ------ | ------ |\n * | `6px` | `8px` | `10px` | `12px` | `14px` | `16px` |\n */\n gutterSize?: Sizes<\"2xs\" | \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\">\n /**\n * Disables the select visually and from interactions\n * @default false\n */\n disabled?: boolean\n /**\n * Mark the input as invalid\n * @default false\n */\n invalid?: boolean\n /**\n * Allow autofill extensions to appear in the input\n * @default false\n */\n allowAutofillExtensions?: boolean\n /**\n * Select all contents of the input when mounted.\n * @default false\n */\n autoSelect?: boolean\n /** Callback invoked when the input is autofilled by the browser */\n onAutofill?: () => void\n /** Content rendered at the start of the input */\n startAdornment?: React.ReactNode\n /** Content rendered at the end of the input */\n endAdornment?: React.ReactNode\n /**\n * Callback invoked when the clear button is clicked.\n * When provided, displays a clear button that appears when the input has a value.\n */\n onClear?: () => void\n /**\n * Determines if the button should be a fully rounded pill shape\n * @default false\n */\n pill?: boolean\n /**\n * Applies a negative margin using the current gutter to optically align the input\n * with surrounding content.\n */\n opticallyAlign?: \"start\" | \"end\"\n /** Ref for the input */\n ref?: React.Ref<HTMLInputElement | null>\n} & Omit<React.InputHTMLAttributes<HTMLInputElement>, \"disabled\" | \"size\">\n\nconst InputRoot = (props: InputProps) => {\n const inputRef = useRef<HTMLInputElement | null>(null)\n const inputIdSuffix = useId()\n // Prevent 1Password from appearing: 1Password won't display if it thinks this field is for searching\n // Allow user to override this for connecting to labels.\n const onePasswordPreventionId = `search-ui-input-${inputIdSuffix}`\n const {\n id,\n name,\n type = \"text\",\n variant = \"outline\",\n size = \"md\",\n gutterSize,\n className,\n autoComplete,\n disabled = false,\n readOnly = false,\n invalid = false,\n // Default to `true` when type=\"password\" or presence of `name`\n allowAutofillExtensions = type === \"password\" || !!name,\n onFocus,\n onBlur,\n onAnimationStart,\n onAutofill,\n autoSelect,\n startAdornment: StartAdornment,\n endAdornment: EndAdornment,\n onClear,\n pill,\n opticallyAlign,\n ref,\n value,\n defaultValue,\n ...restProps\n } = props\n\n // Determine if clear button should be shown\n const hasValue = value !== undefined ? !!value : !!defaultValue\n const showClearButton = !!onClear && hasValue && !disabled && !readOnly\n // Redirect clicks on the container and adornments\n const handleMouseDown = (evt: React.MouseEvent<HTMLDivElement>) => {\n const input = inputRef.current\n // Bail out if the target is not an element or the textarea is not found\n if (!evt.target || !(evt.target instanceof Element) || !input) {\n return\n }\n // Bail out if the target is inside the textarea\n if (input.contains(evt.target)) {\n return\n }\n // Bail out if the target is a button\n if (evt.target.closest(\"button, [type='button'], [role='button'], [role='menuitem']\")) {\n return\n }\n\n evt.preventDefault()\n // If the textarea is not focused, focus it\n if (document.activeElement !== input) {\n input.focus()\n }\n\n const { left, top } = input.getBoundingClientRect()\n const { clientX, clientY } = evt\n // \"Before\" the input: top/left\n const isBefore = clientY < top || clientX < left\n\n // If this is the first click, clear the selection.\n if (evt.detail === 1) {\n if (isBefore) {\n input.setSelectionRange(0, 0)\n } else {\n const length = input.value.length\n input.setSelectionRange(length, length)\n }\n }\n // If this is the second click, select the first or last word\n else if (evt.detail === 2) {\n // Split the textarea value into words and non-word characters\n const words = input.value.match(/\\w+|[^\\w\\s]/g) || []\n const selectedWord = isBefore ? words.at(0) : words.at(-1)\n if (selectedWord) {\n const wordIndex = isBefore\n ? input.value.indexOf(selectedWord)\n : input.value.lastIndexOf(selectedWord)\n input.setSelectionRange(wordIndex, wordIndex + selectedWord.length)\n }\n }\n // If this is the third click or greater, select the entire input\n else {\n input.select()\n }\n }\n\n const [focused, setFocused] = useState<boolean>(false)\n\n useEffect(() => {\n if (autoSelect) {\n inputRef.current?.select()\n }\n }, [autoSelect])\n\n const handleAnimationStart = (evt: React.AnimationEvent<HTMLInputElement>) => {\n onAnimationStart?.(evt)\n if (evt.animationName === \"native-autofill-in\") {\n onAutofill?.()\n }\n }\n\n return (\n <div\n className={clsx(s.Container, className)}\n data-slot=\"input\"\n data-variant={variant}\n data-size={size}\n data-gutter-size={gutterSize}\n data-focused={focused}\n data-disabled={disabled ? \"\" : undefined}\n data-readonly={readOnly ? \"\" : undefined}\n data-invalid={invalid ? \"\" : undefined}\n data-pill={pill ? \"\" : undefined}\n data-optically-align={opticallyAlign}\n data-has-start-adornment={StartAdornment ? \"\" : undefined}\n data-has-end-adornment={EndAdornment || showClearButton ? \"\" : undefined}\n onMouseDown={handleMouseDown}\n >\n {StartAdornment}\n <input\n {...restProps}\n ref={mergeRefs([ref, inputRef])}\n id={id || (allowAutofillExtensions ? undefined : onePasswordPreventionId)}\n className={s.Input}\n type={type}\n name={name}\n value={value}\n defaultValue={defaultValue}\n readOnly={readOnly}\n disabled={disabled}\n onFocus={(evt) => {\n setFocused(true)\n onFocus?.(evt)\n }}\n onBlur={(evt) => {\n setFocused(false)\n onBlur?.(evt)\n }}\n onAnimationStart={handleAnimationStart}\n // Prevent LastPass and 1Password from appearing\n data-lpignore={allowAutofillExtensions ? undefined : true}\n data-1p-ignore={allowAutofillExtensions ? undefined : true}\n />\n {showClearButton && (\n <button type=\"button\" aria-label=\"Clear input\" className={s.Clear} onClick={onClear}>\n <X />\n </button>\n )}\n {EndAdornment}\n </div>\n )\n}\n\n/* ------------------------------------------------------------------ */\n/* AdornmentButton — canonical inline action button for Input slots */\n/* (mic toggle, visibility toggle, copy, etc.). Sizes itself off the */\n/* parent Input via --input-clear-size / --input-clear-pill-offset */\n/* so it stays visually consistent with the built-in clear button. */\n/* ------------------------------------------------------------------ */\n\nexport type InputAdornmentButtonProps = Omit<\n React.ButtonHTMLAttributes<HTMLButtonElement>,\n \"ref\" | \"color\"\n> & {\n ref?: React.Ref<HTMLButtonElement>\n /**\n * Applies a semantic color tint to the pressed (`aria-pressed=\"true\"`) state\n * — the same palette Button accepts. Default `aria-pressed` keeps the\n * neutral primary-soft tint.\n */\n color?: SemanticColor\n}\n\nconst InputAdornmentButton = ({\n className,\n type = \"button\",\n color,\n ref,\n ...restProps\n}: InputAdornmentButtonProps) => (\n <button\n ref={ref}\n type={type}\n className={clsx(s.AdornmentButton, className)}\n data-color={color}\n {...restProps}\n />\n)\n\ntype InputComponent = typeof InputRoot & {\n AdornmentButton: typeof InputAdornmentButton\n}\n\nexport const Input = InputRoot as InputComponent\nInput.AdornmentButton = InputAdornmentButton\n"]}
|
|
@@ -234,11 +234,21 @@
|
|
|
234
234
|
|
|
235
235
|
/* =============================================
|
|
236
236
|
Disabled
|
|
237
|
+
---------------------------------------------
|
|
238
|
+
Fade the input text and the built-in Clear button, but keep the
|
|
239
|
+
Container's chrome (border, focus ring) and any AdornmentButton
|
|
240
|
+
at full opacity — AdornmentButtons often double as toggles that
|
|
241
|
+
remain interactive while the input is disabled (voice-mode switch,
|
|
242
|
+
edit-in-place, etc.).
|
|
237
243
|
============================================= */
|
|
238
244
|
.Container[data-disabled] {
|
|
239
245
|
cursor: not-allowed;
|
|
240
|
-
opacity: 0.5;
|
|
241
246
|
}
|
|
247
|
+
|
|
248
|
+
.Container[data-disabled] > .Input,
|
|
249
|
+
.Container[data-disabled] > .Clear {
|
|
250
|
+
opacity: 0.5;
|
|
251
|
+
}
|
|
242
252
|
/*
|
|
243
253
|
* When adornments are present, gutter is applied to the container instead of the input.
|
|
244
254
|
*/
|
|
@@ -269,7 +279,7 @@
|
|
|
269
279
|
margin-left: var(--input-adornment-offset, -2px);
|
|
270
280
|
}
|
|
271
281
|
|
|
272
|
-
.Container > :last-child:not(input, .Clear) {
|
|
282
|
+
.Container > :last-child:not(input, .Clear, .AdornmentButton) {
|
|
273
283
|
margin-right: var(--input-adornment-offset, -2px);
|
|
274
284
|
}.Input {
|
|
275
285
|
display: block;
|
|
@@ -410,6 +420,81 @@
|
|
|
410
420
|
.Clear > svg {
|
|
411
421
|
width: var(--input-clear-icon-size);
|
|
412
422
|
height: var(--input-clear-icon-size);
|
|
423
|
+
}/* AdornmentButton — public sibling of .Clear for in-input action buttons
|
|
424
|
+
(mic toggle, copy, send, etc.). Sized from the Input's own size token:
|
|
425
|
+
input height minus one spacing step each side leaves ~4px of breathing
|
|
426
|
+
room top + bottom, so a md (32px) input gets a 24px pill, an lg (36px)
|
|
427
|
+
gets 28px, etc. Radius is --radius-full so a pill input stays
|
|
428
|
+
concentric with the button. Icon fills half the button. */.AdornmentButton {
|
|
429
|
+
display: flex;
|
|
430
|
+
align-items: center;
|
|
431
|
+
justify-content: center;
|
|
432
|
+
flex-shrink: 0;
|
|
433
|
+
width: calc(var(--input-size) - calc(var(--spacing) * 2));
|
|
434
|
+
height: calc(var(--input-size) - calc(var(--spacing) * 2));
|
|
435
|
+
padding: 0;
|
|
436
|
+
border: 0;
|
|
437
|
+
border-radius: var(--radius-full);
|
|
438
|
+
/* Concentric with the input edge — leave a spacing(1) = 4px breathing
|
|
439
|
+
gap between the button's outer edge and the input's inner edge.
|
|
440
|
+
For a non-pill input that's `4 - gutter`; a pill input scales the
|
|
441
|
+
gutter by --control-gutter-pill-scaling. */
|
|
442
|
+
margin-right: calc(calc(var(--spacing) * 1) - var(--input-gutter));
|
|
443
|
+
background-color: transparent;
|
|
444
|
+
color: var(--color-text-tertiary);
|
|
445
|
+
cursor: pointer;
|
|
446
|
+
transition-duration: var(--transition-duration-basic);
|
|
447
|
+
transition-property: background-color, color;
|
|
448
|
+
transition-timing-function: var(--transition-ease-basic);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.AdornmentButton:hover {
|
|
452
|
+
background-color: var(--color-background-secondary-soft-alpha);
|
|
453
|
+
color: var(--color-text);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.AdornmentButton[aria-pressed="true"] {
|
|
457
|
+
background-color: var(--color-background-primary-soft);
|
|
458
|
+
color: var(--color-text);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.AdornmentButton[data-color="caution"][aria-pressed="true"] {
|
|
462
|
+
background-color: var(--color-background-caution-soft);
|
|
463
|
+
color: var(--color-text-caution-soft);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.AdornmentButton[data-color="danger"][aria-pressed="true"] {
|
|
467
|
+
background-color: var(--color-background-danger-soft);
|
|
468
|
+
color: var(--color-text-danger-soft);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.AdornmentButton[data-color="success"][aria-pressed="true"] {
|
|
472
|
+
background-color: var(--color-background-success-soft);
|
|
473
|
+
color: var(--color-text-success-soft);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.AdornmentButton[data-color="info"][aria-pressed="true"] {
|
|
477
|
+
background-color: var(--color-background-info-soft);
|
|
478
|
+
color: var(--color-text-info-soft);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.AdornmentButton[data-color="discovery"][aria-pressed="true"] {
|
|
482
|
+
background-color: var(--color-background-discovery-soft);
|
|
483
|
+
color: var(--color-text-discovery-soft);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.AdornmentButton:disabled {
|
|
487
|
+
cursor: not-allowed;
|
|
488
|
+
opacity: 0.5;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
:where(.Container[data-pill]) .AdornmentButton {
|
|
492
|
+
margin-right: calc(calc(var(--spacing) * 1) - var(--input-gutter) * var(--control-gutter-pill-scaling));
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.AdornmentButton > svg {
|
|
496
|
+
width: calc(var(--input-size) / 2);
|
|
497
|
+
height: calc(var(--input-size) / 2);
|
|
413
498
|
}@keyframes native-autofill-in {
|
|
414
499
|
0% {
|
|
415
500
|
opacity: 1;
|
|
@@ -131,6 +131,12 @@
|
|
|
131
131
|
ButtonGroup
|
|
132
132
|
============================================= */
|
|
133
133
|
--button-group-nested-gap: calc(var(--spacing) * 2);
|
|
134
|
+
--button-group-separator-color: var(--color-border-strong, var(--color-border));
|
|
135
|
+
--button-group-text-background: var(--color-background-secondary-soft);
|
|
136
|
+
--button-group-text-color: var(--color-text-primary);
|
|
137
|
+
--button-group-text-border-color: var(--color-border-secondary-outline, var(--color-border));
|
|
138
|
+
--button-group-text-font-size: var(--font-text-sm-size);
|
|
139
|
+
--button-group-text-font-weight: var(--font-weight-medium);
|
|
134
140
|
|
|
135
141
|
/* =============================================
|
|
136
142
|
Accordion
|
|
@@ -19,8 +19,20 @@ export type ButtonGroupSeparatorProps = Omit<ComponentProps<typeof Separator>, "
|
|
|
19
19
|
orientation?: ButtonGroupOrientation;
|
|
20
20
|
};
|
|
21
21
|
declare const ButtonGroupSeparator: ({ className, orientation, ...restProps }: ButtonGroupSeparatorProps) => import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export type ButtonGroupTextProps = HTMLAttributes<HTMLDivElement> & {
|
|
23
|
+
/**
|
|
24
|
+
* Swap the rendered element for a child (label, span, etc.) while
|
|
25
|
+
* keeping the group's visual treatment.
|
|
26
|
+
* @default false
|
|
27
|
+
*/
|
|
28
|
+
asChild?: boolean;
|
|
29
|
+
className?: string;
|
|
30
|
+
ref?: Ref<HTMLDivElement>;
|
|
31
|
+
};
|
|
32
|
+
declare const ButtonGroupText: ({ asChild, className, ref, ...restProps }: ButtonGroupTextProps) => import("react/jsx-runtime").JSX.Element;
|
|
22
33
|
type ButtonGroupComponent = typeof ButtonGroupRoot & {
|
|
23
34
|
Separator: typeof ButtonGroupSeparator;
|
|
35
|
+
Text: typeof ButtonGroupText;
|
|
24
36
|
};
|
|
25
37
|
export declare const ButtonGroup: ButtonGroupComponent;
|
|
26
38
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ControlSize, type Sizes, type Variants } from "../../types";
|
|
1
|
+
import { type ControlSize, type SemanticColor, type Sizes, type Variants } from "../../types";
|
|
2
2
|
export type InputProps = {
|
|
3
3
|
/**
|
|
4
4
|
* Visual style of the input
|
|
@@ -67,4 +67,19 @@ export type InputProps = {
|
|
|
67
67
|
/** Ref for the input */
|
|
68
68
|
ref?: React.Ref<HTMLInputElement | null>;
|
|
69
69
|
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, "disabled" | "size">;
|
|
70
|
-
|
|
70
|
+
declare const InputRoot: (props: InputProps) => import("react/jsx-runtime").JSX.Element;
|
|
71
|
+
export type InputAdornmentButtonProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "ref" | "color"> & {
|
|
72
|
+
ref?: React.Ref<HTMLButtonElement>;
|
|
73
|
+
/**
|
|
74
|
+
* Applies a semantic color tint to the pressed (`aria-pressed="true"`) state
|
|
75
|
+
* — the same palette Button accepts. Default `aria-pressed` keeps the
|
|
76
|
+
* neutral primary-soft tint.
|
|
77
|
+
*/
|
|
78
|
+
color?: SemanticColor;
|
|
79
|
+
};
|
|
80
|
+
declare const InputAdornmentButton: ({ className, type, color, ref, ...restProps }: InputAdornmentButtonProps) => import("react/jsx-runtime").JSX.Element;
|
|
81
|
+
type InputComponent = typeof InputRoot & {
|
|
82
|
+
AdornmentButton: typeof InputAdornmentButton;
|
|
83
|
+
};
|
|
84
|
+
export declare const Input: InputComponent;
|
|
85
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plexui/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Modern design system for building high-quality applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -70,6 +70,9 @@
|
|
|
70
70
|
"build:clean": "rm -rf dist",
|
|
71
71
|
"build:js": "tsc -p tsconfig.build.json",
|
|
72
72
|
"build:css": "node ./scripts/build-css.mjs",
|
|
73
|
+
"dev:js": "tsc -p tsconfig.build.json --watch --preserveWatchOutput",
|
|
74
|
+
"dev:css": "node ./scripts/build-css.mjs --watch",
|
|
75
|
+
"dev": "npm run build:clean && npm run build:js && npm run build:css && npm run build:assets && (npm run dev:js & npm run dev:css & wait)",
|
|
73
76
|
"build:assets": "node -e \"const fs=require('fs'),p=require('path');function c(s,d){fs.readdirSync(s,{withFileTypes:true}).forEach(e=>{const sp=p.join(s,e.name),dp=p.join(d,e.name);if(e.isDirectory()){c(sp,dp)}else if(e.name.endsWith('.d.ts')){fs.mkdirSync(d,{recursive:true});fs.copyFileSync(sp,dp)}})}c('src','dist/types')\"",
|
|
74
77
|
"build": "npm run build:clean && npm run build:js && npm run build:css && npm run build:assets",
|
|
75
78
|
"prepublishOnly": "npm run build",
|