beez-ui 2.3.1 → 2.5.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,3 +1,17 @@
1
- import * as React from "react";
2
- declare function Input({ className, type, ...props }: React.ComponentProps<"input">): React.JSX.Element;
1
+ /**
2
+ * Arc's Input (https://uiarc.dev, MIT License, see THIRD_PARTY_NOTICES.md): a single-line field on Arc's tokens whose
3
+ * focus answers with the border colour alone. With a `label`, `description` or `error` it renders Arc's labelled field
4
+ * with animated helper and error rows; without them it renders the bare control, for input groups, tables and forms
5
+ * that place their own label.
6
+ */
7
+ import type { ComponentProps } from "react";
8
+ import type { FieldCopyProps } from "./field.js";
9
+ export type InputProps = ComponentProps<"input"> & FieldCopyProps;
10
+ /**
11
+ * A single-line text value, controlled or uncontrolled like a native input.
12
+ * @param inputProps - Optional field copy plus every native input attribute, including `ref`, `name` and `type`.
13
+ * @returns The labelled field when `label`, `description` or `error` is passed (even as `undefined`), otherwise the
14
+ * bare control.
15
+ */
16
+ declare function Input(inputProps: InputProps): import("react").JSX.Element;
3
17
  export { Input };
@@ -1,17 +1,124 @@
1
- import * as React from "react";
1
+ "use client";
2
+ import { c as _c } from "react/compiler-runtime";
2
3
  import { Input as InputPrimitive } from "@base-ui/react/input";
3
4
  import { MotionSlot } from "../motion/motion-slot.js";
4
5
  import { cn } from "../lib/utils.js";
6
+ import { Field, usesFieldCopy, useFieldAria } from "./field.js";
5
7
  import { jsx as _jsx } from "react/jsx-runtime";
6
- function Input({ className, type, ...props }) {
7
- return /* @__PURE__ */ _jsx(MotionSlot, {
8
- kind: "field",
9
- children: /* @__PURE__ */ _jsx(InputPrimitive, {
10
- type,
11
- "data-slot": "input",
12
- className: cn("h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-[color,background-color,border-color,box-shadow] outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", className),
8
+ /**
9
+ * Arc's control recipe. Text stays at 16px on small screens so iOS does not zoom on focus, hover only answers on
10
+ * hover-capable fine pointers, and the field never changes size on focus or error.
11
+ */
12
+ const inputClassName = "min-h-(--control-height-md) w-full min-w-0 rounded-(--radius-control) border border-border-strong bg-surface px-(--space-3) font-sans text-base leading-(--leading-body) tracking-(--tracking-body) text-foreground outline-none transition-[color,background-color,border-color] duration-(--duration-fast) ease-(--ease-standard) motion-reduce:transition-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-text-muted hover-fine:not-disabled:not-aria-invalid:border-foreground focus-visible:border-foreground aria-invalid:border-danger disabled:cursor-not-allowed disabled:bg-surface-muted disabled:opacity-50 md:text-sm";
13
+ /**
14
+ * A single-line text value, controlled or uncontrolled like a native input.
15
+ * @param inputProps - Optional field copy plus every native input attribute, including `ref`, `name` and `type`.
16
+ * @returns The labelled field when `label`, `description` or `error` is passed (even as `undefined`), otherwise the
17
+ * bare control.
18
+ */
19
+ function Input(inputProps) {
20
+ const $ = _c(28);
21
+ let className;
22
+ let description;
23
+ let error;
24
+ let id;
25
+ let label;
26
+ let props;
27
+ let type;
28
+ if ($[0] !== inputProps) {
29
+ ({className, type, label, description, error, id, ...props} = inputProps);
30
+ $[0] = inputProps;
31
+ $[1] = className;
32
+ $[2] = description;
33
+ $[3] = error;
34
+ $[4] = id;
35
+ $[5] = label;
36
+ $[6] = props;
37
+ $[7] = type;
38
+ } else {
39
+ className = $[1];
40
+ description = $[2];
41
+ error = $[3];
42
+ id = $[4];
43
+ label = $[5];
44
+ props = $[6];
45
+ type = $[7];
46
+ }
47
+ let t0;
48
+ if ($[8] !== description || $[9] !== error || $[10] !== id || $[11] !== props) {
49
+ t0 = {
50
+ id,
51
+ description,
52
+ error,
13
53
  ...props
14
- })
15
- });
54
+ };
55
+ $[8] = description;
56
+ $[9] = error;
57
+ $[10] = id;
58
+ $[11] = props;
59
+ $[12] = t0;
60
+ } else {
61
+ t0 = $[12];
62
+ }
63
+ const aria = useFieldAria(t0);
64
+ const isField = usesFieldCopy(inputProps);
65
+ let t1;
66
+ if ($[13] !== className) {
67
+ t1 = cn(inputClassName, className);
68
+ $[13] = className;
69
+ $[14] = t1;
70
+ } else {
71
+ t1 = $[14];
72
+ }
73
+ const t2 = isField ? aria.controlId : id;
74
+ const t3 = aria["aria-describedby"];
75
+ const t4 = aria["aria-invalid"];
76
+ let t5;
77
+ if ($[15] !== props || $[16] !== t1 || $[17] !== t2 || $[18] !== t3 || $[19] !== t4 || $[20] !== type) {
78
+ t5 = /* @__PURE__ */ _jsx(MotionSlot, {
79
+ kind: "field",
80
+ children: /* @__PURE__ */ _jsx(InputPrimitive, {
81
+ type,
82
+ "data-slot": "input",
83
+ className: t1,
84
+ ...props,
85
+ id: t2,
86
+ "aria-describedby": t3,
87
+ "aria-invalid": t4
88
+ })
89
+ });
90
+ $[15] = props;
91
+ $[16] = t1;
92
+ $[17] = t2;
93
+ $[18] = t3;
94
+ $[19] = t4;
95
+ $[20] = type;
96
+ $[21] = t5;
97
+ } else {
98
+ t5 = $[21];
99
+ }
100
+ const control = t5;
101
+ if (!isField) {
102
+ return control;
103
+ }
104
+ let t6;
105
+ if ($[22] !== aria || $[23] !== control || $[24] !== description || $[25] !== error || $[26] !== label) {
106
+ t6 = /* @__PURE__ */ _jsx(Field, {
107
+ label,
108
+ description,
109
+ error,
110
+ aria,
111
+ children: control
112
+ });
113
+ $[22] = aria;
114
+ $[23] = control;
115
+ $[24] = description;
116
+ $[25] = error;
117
+ $[26] = label;
118
+ $[27] = t6;
119
+ } else {
120
+ t6 = $[27];
121
+ }
122
+ return t6;
16
123
  }
17
124
  export { Input };
@@ -0,0 +1,14 @@
1
+ import type { Ref } from "react";
2
+ export type MorphTextProps = {
3
+ text: string;
4
+ reduceMotion: boolean;
5
+ /** Row of glyphs, for a caller that springs its slot to the row's width with `useMorphWidth`. */
6
+ ref?: Ref<HTMLSpanElement>;
7
+ };
8
+ /**
9
+ * Morphs one label into the next: kept letters glide into place and new ones rise in from a soft blur. The row
10
+ * keeps its natural width; callers spring the slot around it.
11
+ * @param props - Label text, whether the user prefers reduced motion and an optional ref to the row.
12
+ * @returns The row of animated letters. Callers hide it from assistive tech and name their control otherwise.
13
+ */
14
+ export declare function MorphText({ text, reduceMotion, ref }: MorphTextProps): import("react").JSX.Element;
@@ -0,0 +1,210 @@
1
+ "use client";
2
+ import { c as _c } from "react/compiler-runtime";
3
+ /**
4
+ * Letter-by-letter label morph adapted from Arc (https://uiarc.dev), Copyright (c) 2026 Elia Kuratli, MIT License.
5
+ * The full license text ships in THIRD_PARTY_NOTICES.md. Internal: shared by `ActionButton` and `SplitButton`.
6
+ */
7
+ import { useState } from "react";
8
+ import { AnimatePresence, motion } from "motion/react";
9
+ import { MORPH_TEXT_GLYPH_OFFSET_PX, MORPH_TEXT_MAX_GLYPH_DELAY_S, MORPH_TEXT_MIN_SHARED_RUN } from "../constants/morph-text.js";
10
+ import { ARC_BLUR, ARC_DURATION, ARC_EASE, ARC_SPRING_MORPH, ARC_STAGGER } from "../motion/arc-tokens.js";
11
+ import { jsx as _jsx } from "react/jsx-runtime";
12
+ const restState = {
13
+ opacity: 1,
14
+ y: 0,
15
+ filter: "blur(0px)"
16
+ };
17
+ /** Letters rise in from a soft blur and lift away a little faster. */
18
+ const glyphEnter = {
19
+ opacity: 0,
20
+ y: MORPH_TEXT_GLYPH_OFFSET_PX.enter,
21
+ filter: `blur(${ARC_BLUR.soft}px)`
22
+ };
23
+ const glyphExit = {
24
+ opacity: 0,
25
+ y: MORPH_TEXT_GLYPH_OFFSET_PX.exit,
26
+ filter: `blur(${ARC_BLUR.subtle}px)`,
27
+ transition: {
28
+ duration: ARC_DURATION.fast,
29
+ ease: [...ARC_EASE.standard]
30
+ }
31
+ };
32
+ const reducedTransition = { duration: ARC_DURATION.instant };
33
+ const reducedEnter = {
34
+ ...restState,
35
+ opacity: 0
36
+ };
37
+ const reducedExit = {
38
+ opacity: 0,
39
+ transition: reducedTransition
40
+ };
41
+ /** Grapheme segmenter created once; absent on runtimes without `Intl.Segmenter`. */
42
+ const graphemeSegmenter = typeof Intl.Segmenter === "function" ? new Intl.Segmenter(undefined, { granularity: "grapheme" }) : undefined;
43
+ /**
44
+ * Splits text into user-perceived characters, so emoji sequences, flags and combining accents stay in one glyph.
45
+ * Falls back to code points when `Intl.Segmenter` is unavailable.
46
+ * @param text - Text to split.
47
+ * @returns Graphemes in reading order.
48
+ */
49
+ function splitGraphemes(text) {
50
+ if (!graphemeSegmenter) return [...text];
51
+ return Array.from(graphemeSegmenter.segment(text), (segment) => segment.segment);
52
+ }
53
+ /**
54
+ * Turns characters into glyphs of one generation.
55
+ * @param characters - Characters to wrap.
56
+ * @param generation - Label generation, part of every glyph id.
57
+ * @returns Glyphs in reading order.
58
+ */
59
+ function toGlyphs(characters, generation) {
60
+ return characters.map((character, order) => ({
61
+ id: `${generation}:${order}`,
62
+ character,
63
+ order
64
+ }));
65
+ }
66
+ /**
67
+ * Splits a label into glyphs, keeping the identity of the shared leading and trailing characters of the previous
68
+ * label, so `Copy page` → `Copied` only replaces the changed letters.
69
+ * @param text - Current label.
70
+ * @returns Glyphs of the label.
71
+ */
72
+ function useGlyphs(text) {
73
+ const $ = _c(10);
74
+ let t0;
75
+ if ($[0] !== text) {
76
+ t0 = () => ({
77
+ text,
78
+ generation: 0,
79
+ glyphs: toGlyphs(splitGraphemes(text), 0)
80
+ });
81
+ $[0] = text;
82
+ $[1] = t0;
83
+ } else {
84
+ t0 = $[1];
85
+ }
86
+ const [glyphState, setGlyphState] = useState(t0);
87
+ if (glyphState.text === text) {
88
+ return glyphState.glyphs;
89
+ }
90
+ let t1;
91
+ if ($[2] !== glyphState.text) {
92
+ t1 = splitGraphemes(glyphState.text);
93
+ $[2] = glyphState.text;
94
+ $[3] = t1;
95
+ } else {
96
+ t1 = $[3];
97
+ }
98
+ const previous = t1;
99
+ let generation;
100
+ let t2;
101
+ if ($[4] !== glyphState.generation || $[5] !== glyphState.glyphs || $[6] !== previous || $[7] !== text) {
102
+ const next = splitGraphemes(text);
103
+ let sharedStart = 0;
104
+ let sharedEnd = 0;
105
+ while (sharedStart < previous.length && sharedStart < next.length && previous[sharedStart] === next[sharedStart]) {
106
+ sharedStart++;
107
+ }
108
+ while (sharedEnd < previous.length - sharedStart && sharedEnd < next.length - sharedStart && previous[previous.length - 1 - sharedEnd] === next[next.length - 1 - sharedEnd]) {
109
+ sharedEnd++;
110
+ }
111
+ if (sharedStart < MORPH_TEXT_MIN_SHARED_RUN) {
112
+ sharedStart = 0;
113
+ }
114
+ if (sharedEnd < MORPH_TEXT_MIN_SHARED_RUN) {
115
+ sharedEnd = 0;
116
+ }
117
+ generation = glyphState.generation + 1;
118
+ t2 = [
119
+ ...glyphState.glyphs.slice(0, sharedStart),
120
+ ...toGlyphs(next.slice(sharedStart, next.length - sharedEnd), generation),
121
+ ...glyphState.glyphs.slice(glyphState.glyphs.length - sharedEnd)
122
+ ];
123
+ $[4] = glyphState.generation;
124
+ $[5] = glyphState.glyphs;
125
+ $[6] = previous;
126
+ $[7] = text;
127
+ $[8] = generation;
128
+ $[9] = t2;
129
+ } else {
130
+ generation = $[8];
131
+ t2 = $[9];
132
+ }
133
+ const glyphs = t2;
134
+ setGlyphState({
135
+ text,
136
+ generation,
137
+ glyphs
138
+ });
139
+ return glyphs;
140
+ }
141
+ /**
142
+ * Morphs one label into the next: kept letters glide into place and new ones rise in from a soft blur. The row
143
+ * keeps its natural width; callers spring the slot around it.
144
+ * @param props - Label text, whether the user prefers reduced motion and an optional ref to the row.
145
+ * @returns The row of animated letters. Callers hide it from assistive tech and name their control otherwise.
146
+ */
147
+ export function MorphText(t0) {
148
+ const $ = _c(12);
149
+ const { text, reduceMotion, ref } = t0;
150
+ const glyphs = useGlyphs(text);
151
+ let t1;
152
+ if ($[0] !== glyphs || $[1] !== reduceMotion || $[2] !== text) {
153
+ let t2;
154
+ if ($[4] !== reduceMotion || $[5] !== text) {
155
+ t2 = (glyph) => /* @__PURE__ */ _jsx(motion.span, {
156
+ className: "inline-block",
157
+ layout: reduceMotion ? false : "position",
158
+ layoutDependency: text,
159
+ initial: reduceMotion ? reducedEnter : glyphEnter,
160
+ animate: restState,
161
+ exit: reduceMotion ? reducedExit : glyphExit,
162
+ transition: reduceMotion ? reducedTransition : {
163
+ duration: ARC_DURATION.standard,
164
+ ease: [...ARC_EASE.enter],
165
+ delay: Math.min(glyph.order * ARC_STAGGER.char, MORPH_TEXT_MAX_GLYPH_DELAY_S),
166
+ layout: ARC_SPRING_MORPH
167
+ },
168
+ children: glyph.character
169
+ }, glyph.id);
170
+ $[4] = reduceMotion;
171
+ $[5] = text;
172
+ $[6] = t2;
173
+ } else {
174
+ t2 = $[6];
175
+ }
176
+ t1 = glyphs.map(t2);
177
+ $[0] = glyphs;
178
+ $[1] = reduceMotion;
179
+ $[2] = text;
180
+ $[3] = t1;
181
+ } else {
182
+ t1 = $[3];
183
+ }
184
+ let t2;
185
+ if ($[7] !== t1) {
186
+ t2 = /* @__PURE__ */ _jsx(AnimatePresence, {
187
+ mode: "popLayout",
188
+ initial: false,
189
+ children: t1
190
+ });
191
+ $[7] = t1;
192
+ $[8] = t2;
193
+ } else {
194
+ t2 = $[8];
195
+ }
196
+ let t3;
197
+ if ($[9] !== ref || $[10] !== t2) {
198
+ t3 = /* @__PURE__ */ _jsx("span", {
199
+ ref,
200
+ className: "relative inline-flex flex-none whitespace-pre",
201
+ children: t2
202
+ });
203
+ $[9] = ref;
204
+ $[10] = t2;
205
+ $[11] = t3;
206
+ } else {
207
+ t3 = $[11];
208
+ }
209
+ return t3;
210
+ }
@@ -0,0 +1,36 @@
1
+ import type { ReactNode } from "react";
2
+ import type { SplitButtonVariant } from "../constants/split-button.js";
3
+ export type { SplitButtonVariant } from "../constants/split-button.js";
4
+ /** One entry of the menu half. */
5
+ export type SplitButtonAction = {
6
+ label: string;
7
+ onSelect?: () => void;
8
+ disabled?: boolean;
9
+ /** Marks an action that deletes or discards, drawn in the danger color. */
10
+ destructive?: boolean;
11
+ icon?: ReactNode;
12
+ };
13
+ export type SplitButtonProps = {
14
+ /** Label of the main action and its accessible name; changing it morphs the letters in place. */
15
+ label: string;
16
+ /** Actions of the menu half, in order. */
17
+ actions: SplitButtonAction[];
18
+ /** Runs the main action. */
19
+ onClick?: () => void;
20
+ disabled?: boolean;
21
+ /** Icon before the label; swapping it for another element (such as Copy for Check) crossfades it. */
22
+ icon?: ReactNode;
23
+ /** `primary` for the main action of a surface, `secondary` for a quieter, smaller one. */
24
+ variant?: SplitButtonVariant;
25
+ /** Accessible name of the menu half; defaults to "Más acciones de {label}". */
26
+ menuLabel?: string;
27
+ className?: string;
28
+ };
29
+ /**
30
+ * A main action joined to a menu of related actions, such as Copy page with Copy as Markdown and Open in a new tab.
31
+ * The main half announces label changes politely; the menu half opens a `DropdownMenu` aligned to its end.
32
+ * @param props - Main action, its icon, the menu actions, the variant and accessible names.
33
+ * @returns The joined pair of buttons and their menu.
34
+ */
35
+ declare function SplitButton({ label, actions, onClick, disabled, icon, variant, menuLabel, className, }: SplitButtonProps): import("react").JSX.Element;
36
+ export { SplitButton };