@plexui/ui 0.13.0 → 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.
@@ -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;AAUD,MAAM,CAAC,MAAM,WAAW,GAAG,eAAuC,CAAA;AAClE,WAAW,CAAC,SAAS,GAAG,oBAAoB,CAAA","sourcesContent":["\"use client\"\n\nimport clsx from \"clsx\"\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/* Export — compound API */\n/* ------------------------------------------------------------------ */\n\ntype ButtonGroupComponent = typeof ButtonGroupRoot & {\n Separator: typeof ButtonGroupSeparator\n}\n\nexport const ButtonGroup = ButtonGroupRoot as ButtonGroupComponent\nButtonGroup.Separator = ButtonGroupSeparator\n"]}
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 */
@@ -21,40 +24,136 @@
21
24
  gap: var(--button-group-nested-gap);
22
25
  }
23
26
 
24
- /* Horizontal — default */
25
- .Root[data-orientation="horizontal"] > *:not(:first-child) {
27
+ /* Horizontal — default. Plex Button variant="outline" renders its 1px
28
+ border via an inset box-shadow on its ::before pseudo — stripping the
29
+ left `border-inline-start` does nothing. Instead we (a) overlap items
30
+ by -1px and (b) clip 1px off the leading edge of the ::before of
31
+ every non-first child, so the inset shadow of item N+1 doesn't sit
32
+ next to the inset shadow of item N (which would read as a doubled
33
+ 2px line). Focus ring lives on ::after, so clip-path on ::before
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"]) {
40
+ border-start-start-radius: 0;
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
+ ) {
47
+ margin-inline-start: -1px;
48
+ }
49
+ .Root[data-orientation="horizontal"]
50
+ > *:not(:first-child):not([data-slot="button-group-separator"]):not(
51
+ [data-slot="button-group-separator"] + *
52
+ )::before {
53
+ clip-path: inset(0 0 0 1px);
54
+ }
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"] + * {
26
64
  border-start-start-radius: 0;
27
65
  border-end-start-radius: 0;
28
- border-inline-start: 0;
29
66
  }
30
- .Root[data-orientation="horizontal"] > *:not(:last-child) {
67
+ .Root[data-orientation="horizontal"] > *:has(+ [data-slot="button-group-separator"]) {
31
68
  border-start-end-radius: 0;
32
69
  border-end-end-radius: 0;
33
70
  }
34
71
 
35
- /* Vertical */
72
+ /* Vertical — same approach, clip the top 1px of ::before on non-first */
36
73
  .Root[data-orientation="vertical"] {
37
74
  flex-direction: column;
38
75
  }
39
- .Root[data-orientation="vertical"] > *:not(:first-child) {
76
+ .Root[data-orientation="vertical"] > *:not(:first-child):not([data-slot="button-group-separator"]) {
40
77
  border-start-start-radius: 0;
41
78
  border-start-end-radius: 0;
42
- border-block-start: 0;
43
79
  }
44
- .Root[data-orientation="vertical"] > *:not(:last-child) {
80
+ .Root[data-orientation="vertical"]
81
+ > *:not(:first-child):not([data-slot="button-group-separator"]):not(
82
+ [data-slot="button-group-separator"] + *
83
+ ) {
84
+ margin-block-start: -1px;
85
+ }
86
+ .Root[data-orientation="vertical"]
87
+ > *:not(:first-child):not([data-slot="button-group-separator"]):not(
88
+ [data-slot="button-group-separator"] + *
89
+ )::before {
90
+ clip-path: inset(1px 0 0 0);
91
+ }
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"]) {
45
104
  border-end-start-radius: 0;
46
105
  border-end-end-radius: 0;
47
106
  }
48
107
  .Root > input,
49
- .Root > [data-slot="input-wrapper"] {
108
+ .Root > [data-slot="input"],
109
+ .Root > [data-slot="button-group"]:has(> [data-slot="input"]) {
50
110
  flex: 1 1 auto;
51
- }/* Separator used between items inside a ButtonGroup */.Separator {
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;
52
150
  align-self: stretch;
53
151
  margin: 0;
54
152
  flex-shrink: 0;
153
+ background: var(--button-group-separator-color);
55
154
  }
56
155
 
57
- .Separator[data-orientation="vertical"] {
156
+ .Root > .Separator[data-orientation="vertical"] {
58
157
  height: auto;
59
158
  }
60
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
- export const Input = (props) => {
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":"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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 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
- export declare const Input: (props: InputProps) => import("react/jsx-runtime").JSX.Element;
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.13.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",