gbs-add-block 1.2.13 → 1.2.14

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.
Files changed (53) hide show
  1. package/README.md +4 -12
  2. package/index.cjs +4 -0
  3. package/package.json +1 -1
  4. package/source/beta-components/dialog/README.md +39 -0
  5. package/source/beta-components/dialog/__tests__/core.test.ts +86 -0
  6. package/source/beta-components/dialog/core/api.ts +34 -0
  7. package/source/beta-components/dialog/core/dialog.ts +11 -0
  8. package/source/beta-components/dialog/core/index.ts +7 -0
  9. package/source/beta-components/dialog/core/store.ts +81 -0
  10. package/source/beta-components/dialog/core/types.ts +59 -0
  11. package/source/beta-components/dialog/index.ts +7 -0
  12. package/source/beta-components/dialog/react/Dialog.tsx +279 -0
  13. package/source/beta-components/dialog/react/DialogHost.tsx +45 -0
  14. package/source/beta-components/dialog/react/icons.tsx +51 -0
  15. package/source/beta-components/dialog/react/locale.ts +8 -0
  16. package/source/beta-components/dialog/react/props.ts +13 -0
  17. package/source/beta-components/dialog/styles.css +278 -0
  18. package/source/beta-components/input/README.md +44 -0
  19. package/source/beta-components/input/__tests__/core.test.ts +75 -0
  20. package/source/beta-components/input/core/count.ts +14 -0
  21. package/source/beta-components/input/core/index.ts +11 -0
  22. package/source/beta-components/input/core/otp.ts +64 -0
  23. package/source/beta-components/input/core/types.ts +14 -0
  24. package/source/beta-components/input/index.ts +8 -0
  25. package/source/beta-components/input/react/Input.tsx +219 -0
  26. package/source/beta-components/input/react/OtpInput.tsx +256 -0
  27. package/source/beta-components/input/react/dom.ts +10 -0
  28. package/source/beta-components/input/react/icons.tsx +35 -0
  29. package/source/beta-components/input/react/locale.ts +9 -0
  30. package/source/beta-components/input/react/props.ts +6 -0
  31. package/source/beta-components/input/styles.css +296 -0
  32. package/source/beta-components/modal/README.md +51 -0
  33. package/source/beta-components/modal/__tests__/core.test.ts +55 -0
  34. package/source/beta-components/modal/core/dismiss.ts +29 -0
  35. package/source/beta-components/modal/core/index.ts +3 -0
  36. package/source/beta-components/modal/core/types.ts +27 -0
  37. package/source/beta-components/modal/index.ts +5 -0
  38. package/source/beta-components/modal/react/Modal.tsx +238 -0
  39. package/source/beta-components/modal/react/icons.tsx +19 -0
  40. package/source/beta-components/modal/react/locale.ts +5 -0
  41. package/source/beta-components/modal/react/props.ts +17 -0
  42. package/source/beta-components/modal/styles.css +235 -0
  43. package/source/beta-components/textarea/README.md +39 -0
  44. package/source/beta-components/textarea/__tests__/core.test.ts +38 -0
  45. package/source/beta-components/textarea/core/count.ts +14 -0
  46. package/source/beta-components/textarea/core/index.ts +4 -0
  47. package/source/beta-components/textarea/core/size.ts +23 -0
  48. package/source/beta-components/textarea/core/types.ts +17 -0
  49. package/source/beta-components/textarea/index.ts +5 -0
  50. package/source/beta-components/textarea/react/Textarea.tsx +209 -0
  51. package/source/beta-components/textarea/react/locale.ts +5 -0
  52. package/source/beta-components/textarea/react/props.ts +4 -0
  53. package/source/beta-components/textarea/styles.css +159 -0
@@ -0,0 +1,209 @@
1
+ "use client";
2
+
3
+ import {
4
+ useCallback,
5
+ useEffect,
6
+ useId,
7
+ useImperativeHandle,
8
+ useLayoutEffect,
9
+ useMemo,
10
+ useRef,
11
+ useState,
12
+ type ChangeEvent,
13
+ type CSSProperties,
14
+ type ReactNode,
15
+ type Ref,
16
+ type TextareaHTMLAttributes,
17
+ } from "react";
18
+ import { countCharacters } from "../core/count";
19
+ import { fitHeight } from "../core/size";
20
+ import type { FieldSize, TextareaLocaleText, TextareaResize } from "../core/types";
21
+ import { defaultTextareaText } from "./locale";
22
+ import { cx, type TextareaSlot } from "./props";
23
+
24
+ export interface TextareaProps
25
+ extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, "value" | "defaultValue"> {
26
+ value?: string;
27
+ defaultValue?: string;
28
+ /** The new text on every change. The native `onChange` fires as well. */
29
+ onValueChange?(value: string): void;
30
+ label?: ReactNode;
31
+ description?: ReactNode;
32
+ /** Message below the field; also marks it invalid. */
33
+ error?: ReactNode;
34
+ size?: FieldSize;
35
+ /** Grow and shrink with the text, between `minRows` and `maxRows`. Default false. */
36
+ autoResize?: boolean;
37
+ /** Smallest height in rows. Default `rows`, or 3. */
38
+ minRows?: number;
39
+ /** Largest height in rows before the text scrolls. No limit by default. */
40
+ maxRows?: number;
41
+ /** Which way people can drag to resize. Default `vertical`, or `none` with `autoResize`. */
42
+ resize?: TextareaResize;
43
+ /** A character counter; shows `count / maxLength` when `maxLength` is set. */
44
+ showCount?: boolean;
45
+ classNames?: Partial<Record<TextareaSlot, string>>;
46
+ localeText?: Partial<TextareaLocaleText>;
47
+ /** The `<textarea>` element. */
48
+ ref?: Ref<HTMLTextAreaElement>;
49
+ }
50
+
51
+ const supportsFieldSizing = () =>
52
+ typeof CSS !== "undefined" && CSS.supports("field-sizing", "content");
53
+
54
+ const px = (value: string) => Number.parseFloat(value) || 0;
55
+
56
+ /**
57
+ * A multi-line text field with label, hint, error, character counter and
58
+ * auto-resize. Every other prop goes to the `<textarea>`.
59
+ */
60
+ export function Textarea(props: TextareaProps) {
61
+ const {
62
+ ref,
63
+ value: valueProp,
64
+ defaultValue,
65
+ onChange,
66
+ onValueChange,
67
+ label,
68
+ description,
69
+ error,
70
+ size = "md",
71
+ autoResize = false,
72
+ minRows,
73
+ maxRows,
74
+ rows,
75
+ resize,
76
+ showCount = false,
77
+ className,
78
+ classNames,
79
+ style,
80
+ localeText,
81
+ id: idProp,
82
+ disabled,
83
+ readOnly,
84
+ required,
85
+ maxLength,
86
+ "aria-describedby": describedByProp,
87
+ ...textareaProps
88
+ } = props;
89
+
90
+ const reactId = useId();
91
+ const id = idProp ?? reactId;
92
+ const text = useMemo(() => ({ ...defaultTextareaText, ...localeText }), [localeText]);
93
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
94
+ useImperativeHandle(ref, () => textareaRef.current as HTMLTextAreaElement, []);
95
+
96
+ // Mirrors the text even when uncontrolled, for the counter and auto-resize.
97
+ const [internal, setInternal] = useState(defaultValue ?? "");
98
+ const current = valueProp ?? internal;
99
+ const floor = Math.max(1, minRows ?? rows ?? 3);
100
+
101
+ const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
102
+ if (valueProp === undefined) setInternal(event.target.value);
103
+ onChange?.(event);
104
+ onValueChange?.(event.target.value);
105
+ };
106
+
107
+ // Browsers with `field-sizing: content` resize in CSS; others are measured here.
108
+ const measure = useCallback(() => {
109
+ const element = textareaRef.current;
110
+ if (!autoResize || !element || supportsFieldSizing()) return;
111
+ const computed = getComputedStyle(element);
112
+ const metrics = {
113
+ lineHeight: px(computed.lineHeight) || px(computed.fontSize) * 1.5,
114
+ paddingBlock: px(computed.paddingTop) + px(computed.paddingBottom),
115
+ borderBlock: px(computed.borderTopWidth) + px(computed.borderBottomWidth),
116
+ };
117
+ element.style.height = "auto";
118
+ const { height, overflow } = fitHeight(element.scrollHeight, metrics, floor, maxRows);
119
+ element.style.height = `${height}px`;
120
+ element.style.overflowY = overflow ? "auto" : "hidden";
121
+ }, [autoResize, floor, maxRows]);
122
+
123
+ useLayoutEffect(() => {
124
+ measure();
125
+ }, [measure, current]);
126
+
127
+ // A narrower field wraps more lines, so measure again when the width changes.
128
+ useEffect(() => {
129
+ const element = textareaRef.current;
130
+ if (!autoResize || !element || supportsFieldSizing()) return;
131
+ let width = element.offsetWidth;
132
+ const observer = new ResizeObserver(() => {
133
+ if (element.offsetWidth === width) return;
134
+ width = element.offsetWidth;
135
+ measure();
136
+ });
137
+ observer.observe(element);
138
+ return () => observer.disconnect();
139
+ }, [autoResize, measure]);
140
+
141
+ const invalid = Boolean(error);
142
+ const count = countCharacters(current);
143
+ const describedBy =
144
+ cx(describedByProp, description ? `${id}-description` : "", error ? `${id}-error` : "") || undefined;
145
+ const rowVariables = {
146
+ "--ta-min-rows": floor,
147
+ "--ta-max-rows": maxRows ?? "none",
148
+ } as CSSProperties;
149
+
150
+ return (
151
+ <div
152
+ className={cx("ta-root", classNames?.root, className)}
153
+ style={style}
154
+ data-size={size}
155
+ data-disabled={disabled || undefined}
156
+ data-invalid={invalid ? "" : undefined}
157
+ >
158
+ {label && (
159
+ <label htmlFor={id} className={cx("ta-label", classNames?.label)} data-required={required || undefined}>
160
+ {label}
161
+ </label>
162
+ )}
163
+
164
+ <textarea
165
+ {...textareaProps}
166
+ ref={textareaRef}
167
+ id={id}
168
+ className={cx("ta-input", classNames?.textarea)}
169
+ style={rowVariables}
170
+ rows={autoResize ? floor : (rows ?? floor)}
171
+ value={valueProp}
172
+ defaultValue={valueProp === undefined ? defaultValue : undefined}
173
+ disabled={disabled}
174
+ readOnly={readOnly}
175
+ required={required}
176
+ maxLength={maxLength}
177
+ data-autoresize={autoResize || undefined}
178
+ data-resize={resize ?? (autoResize ? "none" : "vertical")}
179
+ data-invalid={invalid ? "" : undefined}
180
+ aria-invalid={invalid || undefined}
181
+ aria-describedby={describedBy}
182
+ onChange={handleChange}
183
+ />
184
+
185
+ {(description || showCount) && (
186
+ <div className="ta-footer">
187
+ {description && (
188
+ <span id={`${id}-description`} className={cx("ta-description", classNames?.description)}>
189
+ {description}
190
+ </span>
191
+ )}
192
+ {showCount && (
193
+ <span
194
+ className={cx("ta-count", classNames?.count)}
195
+ data-over={maxLength !== undefined && count > maxLength ? "" : undefined}
196
+ >
197
+ {text.characterCount(String(count), maxLength !== undefined ? String(maxLength) : undefined)}
198
+ </span>
199
+ )}
200
+ </div>
201
+ )}
202
+ {error && (
203
+ <span id={`${id}-error`} className={cx("ta-error", classNames?.error)} role="alert">
204
+ {error}
205
+ </span>
206
+ )}
207
+ </div>
208
+ );
209
+ }
@@ -0,0 +1,5 @@
1
+ import type { TextareaLocaleText } from "../core/types";
2
+
3
+ export const defaultTextareaText: TextareaLocaleText = {
4
+ characterCount: (count, max) => (max === undefined ? count : `${count} / ${max}`),
5
+ };
@@ -0,0 +1,4 @@
1
+ export type TextareaSlot = "root" | "label" | "textarea" | "description" | "error" | "count";
2
+
3
+ export const cx = (...names: (string | false | null | undefined)[]) =>
4
+ names.filter(Boolean).join(" ");
@@ -0,0 +1,159 @@
1
+ /*
2
+ * Textarea styles.
3
+ *
4
+ * Tokens read the DataGrid's --dg-* variables when they are set on an ancestor
5
+ * (set them on :root to share one theme) and fall back to the same palette
6
+ * otherwise. Rules live in the `components` layer, so utility classes passed
7
+ * through `className` / `classNames` override them.
8
+ */
9
+ @layer theme, base, components, utilities;
10
+
11
+ @layer components {
12
+ .ta-root {
13
+ --ta-fg: var(--dg-fg, light-dark(#18181b, #f4f4f5));
14
+ --ta-muted: var(--dg-muted, light-dark(#71717a, #a1a1aa));
15
+ --ta-border: var(--dg-border, light-dark(#e4e4e7, #27272a));
16
+ --ta-input-bg: var(--dg-input-bg, light-dark(#ffffff, #121216));
17
+ --ta-readonly-bg: light-dark(#fafafa, #0e0e12);
18
+ --ta-focus: var(--dg-focus, light-dark(#2563eb, #60a5fa));
19
+ --ta-danger: var(--dg-danger, light-dark(#dc2626, #f87171));
20
+ --ta-radius: var(--dg-radius, 8px);
21
+ --ta-font-size: var(--dg-font-size, 13px);
22
+ --ta-px: 10px;
23
+ --ta-py: 8px;
24
+
25
+ color-scheme: inherit;
26
+ display: flex;
27
+ flex-direction: column;
28
+ gap: 4px;
29
+ width: 100%;
30
+ min-width: 0;
31
+ font-size: var(--ta-font-size);
32
+ line-height: 1.4;
33
+ color: var(--ta-fg);
34
+ }
35
+
36
+ :where(.dark, [data-theme="dark"]) .ta-root {
37
+ color-scheme: dark;
38
+ }
39
+ :where(.light, [data-theme="light"]) .ta-root {
40
+ color-scheme: light;
41
+ }
42
+
43
+ :where(.ta-root) *,
44
+ :where(.ta-root) *::before,
45
+ :where(.ta-root) *::after {
46
+ box-sizing: border-box;
47
+ }
48
+
49
+ .ta-root[data-size="sm"] {
50
+ --ta-font-size: 12px;
51
+ --ta-px: 8px;
52
+ --ta-py: 6px;
53
+ }
54
+ .ta-root[data-size="lg"] {
55
+ --ta-font-size: 14px;
56
+ --ta-px: 12px;
57
+ --ta-py: 10px;
58
+ }
59
+
60
+ .ta-label {
61
+ font-weight: 500;
62
+ }
63
+ .ta-label[data-required]::after {
64
+ content: " *";
65
+ color: var(--ta-danger);
66
+ }
67
+
68
+ .ta-input {
69
+ display: block;
70
+ width: 100%;
71
+ min-height: calc(1lh + 2 * var(--ta-py) + 2px);
72
+ margin: 0;
73
+ padding: var(--ta-py) var(--ta-px);
74
+ border: 1px solid var(--ta-border);
75
+ border-radius: var(--ta-radius);
76
+ background: var(--ta-input-bg);
77
+ color: inherit;
78
+ font: inherit;
79
+ line-height: 1.5;
80
+ }
81
+ .ta-input::placeholder {
82
+ color: var(--ta-muted);
83
+ }
84
+ .ta-input:hover:not(:disabled) {
85
+ border-color: color-mix(in oklab, var(--ta-border), var(--ta-fg) 25%);
86
+ }
87
+ .ta-input:focus-visible {
88
+ outline: 2px solid var(--ta-focus);
89
+ outline-offset: -1px;
90
+ }
91
+ .ta-input[data-invalid] {
92
+ border-color: var(--ta-danger);
93
+ outline-color: var(--ta-danger);
94
+ }
95
+ .ta-input:read-only {
96
+ background: var(--ta-readonly-bg);
97
+ }
98
+ .ta-input:disabled {
99
+ opacity: 0.55;
100
+ cursor: not-allowed;
101
+ }
102
+
103
+ .ta-input[data-resize="none"] {
104
+ resize: none;
105
+ }
106
+ .ta-input[data-resize="vertical"] {
107
+ resize: vertical;
108
+ }
109
+ .ta-input[data-resize="horizontal"] {
110
+ resize: horizontal;
111
+ }
112
+ .ta-input[data-resize="both"] {
113
+ resize: both;
114
+ }
115
+
116
+ /*
117
+ * Auto-resize in CSS where supported. The row limits come from the
118
+ * component; `--ta-max-rows: none` makes max-height invalid, which means no limit.
119
+ */
120
+ @supports (field-sizing: content) {
121
+ .ta-input[data-autoresize] {
122
+ field-sizing: content;
123
+ min-height: calc(var(--ta-min-rows, 3) * 1lh + 2 * var(--ta-py) + 2px);
124
+ max-height: calc(var(--ta-max-rows) * 1lh + 2 * var(--ta-py) + 2px);
125
+ }
126
+ }
127
+
128
+ .ta-footer {
129
+ display: flex;
130
+ align-items: baseline;
131
+ gap: 8px;
132
+ }
133
+ .ta-description {
134
+ flex: 1 1 auto;
135
+ min-width: 0;
136
+ color: var(--ta-muted);
137
+ font-size: 0.92em;
138
+ }
139
+ .ta-count {
140
+ flex: none;
141
+ margin-inline-start: auto;
142
+ color: var(--ta-muted);
143
+ font-size: 0.92em;
144
+ font-variant-numeric: tabular-nums;
145
+ }
146
+ .ta-count[data-over] {
147
+ color: var(--ta-danger);
148
+ }
149
+ .ta-error {
150
+ color: var(--ta-danger);
151
+ font-size: 0.92em;
152
+ }
153
+
154
+ @media (forced-colors: active) {
155
+ .ta-input:focus-visible {
156
+ outline-color: Highlight;
157
+ }
158
+ }
159
+ }