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,296 @@
1
+ /*
2
+ * Input and OtpInput 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
+ .in-root {
13
+ --in-bg: var(--dg-bg, light-dark(#ffffff, #0b0b0e));
14
+ --in-fg: var(--dg-fg, light-dark(#18181b, #f4f4f5));
15
+ --in-muted: var(--dg-muted, light-dark(#71717a, #a1a1aa));
16
+ --in-border: var(--dg-border, light-dark(#e4e4e7, #27272a));
17
+ --in-hover: var(--dg-hover, light-dark(#f4f4f5, #1f1f23));
18
+ --in-input-bg: var(--dg-input-bg, light-dark(#ffffff, #121216));
19
+ --in-readonly-bg: light-dark(#fafafa, #0e0e12);
20
+ --in-accent: var(--dg-accent, light-dark(#2563eb, #60a5fa));
21
+ --in-focus: var(--dg-focus, light-dark(#2563eb, #60a5fa));
22
+ --in-danger: var(--dg-danger, light-dark(#dc2626, #f87171));
23
+ --in-radius: var(--dg-radius, 8px);
24
+ --in-font-size: var(--dg-font-size, 13px);
25
+ --in-height: 36px;
26
+ --in-px: 10px;
27
+ --in-cell: 42px;
28
+
29
+ color-scheme: inherit;
30
+ display: flex;
31
+ flex-direction: column;
32
+ gap: 4px;
33
+ width: 100%;
34
+ min-width: 0;
35
+ font-size: var(--in-font-size);
36
+ line-height: 1.4;
37
+ color: var(--in-fg);
38
+ }
39
+
40
+ :where(.dark, [data-theme="dark"]) .in-root {
41
+ color-scheme: dark;
42
+ }
43
+ :where(.light, [data-theme="light"]) .in-root {
44
+ color-scheme: light;
45
+ }
46
+
47
+ :where(.in-root) *,
48
+ :where(.in-root) *::before,
49
+ :where(.in-root) *::after {
50
+ box-sizing: border-box;
51
+ }
52
+
53
+ .in-root[data-size="sm"] {
54
+ --in-height: 30px;
55
+ --in-font-size: 12px;
56
+ --in-px: 8px;
57
+ --in-cell: 34px;
58
+ }
59
+ .in-root[data-size="lg"] {
60
+ --in-height: 44px;
61
+ --in-font-size: 14px;
62
+ --in-px: 12px;
63
+ --in-cell: 50px;
64
+ }
65
+
66
+ .in-label {
67
+ font-weight: 500;
68
+ }
69
+ .in-label[data-required]::after {
70
+ content: " *";
71
+ color: var(--in-danger);
72
+ }
73
+ .in-footer {
74
+ display: flex;
75
+ align-items: baseline;
76
+ gap: 8px;
77
+ }
78
+ .in-description {
79
+ flex: 1 1 auto;
80
+ min-width: 0;
81
+ color: var(--in-muted);
82
+ font-size: 0.92em;
83
+ }
84
+ .in-count {
85
+ flex: none;
86
+ margin-inline-start: auto;
87
+ color: var(--in-muted);
88
+ font-size: 0.92em;
89
+ font-variant-numeric: tabular-nums;
90
+ }
91
+ .in-count[data-over] {
92
+ color: var(--in-danger);
93
+ }
94
+ .in-error {
95
+ color: var(--in-danger);
96
+ font-size: 0.92em;
97
+ }
98
+
99
+ /* --------------------------------------------------------------- input */
100
+
101
+ .in-control {
102
+ display: flex;
103
+ align-items: center;
104
+ gap: 6px;
105
+ width: 100%;
106
+ height: var(--in-height);
107
+ padding: 0 calc(var(--in-px) - 4px) 0 var(--in-px);
108
+ border: 1px solid var(--in-border);
109
+ border-radius: var(--in-radius);
110
+ background: var(--in-input-bg);
111
+ cursor: text;
112
+ }
113
+ .in-control:hover:not([data-disabled]) {
114
+ border-color: color-mix(in oklab, var(--in-border), var(--in-fg) 25%);
115
+ }
116
+ .in-control:focus-within {
117
+ outline: 2px solid var(--in-focus);
118
+ outline-offset: -1px;
119
+ }
120
+ .in-control[data-invalid] {
121
+ border-color: var(--in-danger);
122
+ outline-color: var(--in-danger);
123
+ }
124
+ .in-control[data-readonly] {
125
+ background: var(--in-readonly-bg);
126
+ }
127
+ .in-control[data-disabled] {
128
+ opacity: 0.55;
129
+ cursor: not-allowed;
130
+ }
131
+
132
+ .in-input {
133
+ flex: 1 1 auto;
134
+ width: 100%;
135
+ min-width: 0;
136
+ height: 100%;
137
+ padding: 0;
138
+ border: 0;
139
+ outline: 0;
140
+ background: transparent;
141
+ color: inherit;
142
+ font: inherit;
143
+ }
144
+ .in-input::placeholder {
145
+ color: var(--in-muted);
146
+ }
147
+ .in-input:disabled {
148
+ cursor: not-allowed;
149
+ }
150
+ /* Keep browser autofill from painting its own background over the field. */
151
+ .in-input:autofill {
152
+ box-shadow: 0 0 0 100px var(--in-input-bg) inset;
153
+ -webkit-text-fill-color: var(--in-fg);
154
+ }
155
+
156
+ .in-adornment {
157
+ display: inline-flex;
158
+ flex: none;
159
+ align-items: center;
160
+ color: var(--in-muted);
161
+ white-space: nowrap;
162
+ }
163
+ .in-adornment[data-side="trailing"] {
164
+ padding-inline-end: 4px;
165
+ }
166
+
167
+ .in-icon-button {
168
+ display: inline-grid;
169
+ flex: none;
170
+ place-items: center;
171
+ width: 26px;
172
+ height: 26px;
173
+ padding: 0;
174
+ border: 0;
175
+ border-radius: 6px;
176
+ background: transparent;
177
+ color: var(--in-muted);
178
+ cursor: pointer;
179
+ }
180
+ .in-icon-button:hover:not(:disabled) {
181
+ background: var(--in-hover);
182
+ color: var(--in-fg);
183
+ }
184
+ .in-icon-button:focus-visible {
185
+ outline: 2px solid var(--in-focus);
186
+ outline-offset: 1px;
187
+ }
188
+
189
+ /* ----------------------------------------------------------------- otp */
190
+
191
+ .in-otp {
192
+ position: relative;
193
+ display: inline-flex;
194
+ align-items: center;
195
+ gap: 8px;
196
+ width: max-content;
197
+ max-width: 100%;
198
+ }
199
+ /*
200
+ * The real input covers the cells but draws nothing: the cells show its value,
201
+ * and it still receives typing, paste, autofill and assistive technology.
202
+ */
203
+ .in-otp-input {
204
+ position: absolute;
205
+ inset: 0;
206
+ z-index: 1;
207
+ width: 100%;
208
+ height: 100%;
209
+ padding: 0;
210
+ border: 0;
211
+ outline: 0;
212
+ background: transparent;
213
+ color: transparent;
214
+ caret-color: transparent;
215
+ font-size: 16px; /* 16px keeps iOS from zooming in on focus */
216
+ letter-spacing: 0;
217
+ cursor: text;
218
+ }
219
+ .in-otp-input::selection {
220
+ background: transparent;
221
+ }
222
+ .in-otp-input:autofill {
223
+ box-shadow: none;
224
+ -webkit-text-fill-color: transparent;
225
+ }
226
+ .in-otp-input:disabled {
227
+ cursor: not-allowed;
228
+ }
229
+
230
+ .in-otp-cell {
231
+ display: grid;
232
+ flex: none;
233
+ place-items: center;
234
+ width: var(--in-cell);
235
+ height: calc(var(--in-cell) * 1.15);
236
+ border: 1px solid var(--in-border);
237
+ border-radius: var(--in-radius);
238
+ background: var(--in-input-bg);
239
+ font-size: 1.35em;
240
+ font-weight: 600;
241
+ font-variant-numeric: tabular-nums;
242
+ transition: border-color 120ms ease;
243
+ }
244
+ .in-otp-cell[data-filled] {
245
+ border-color: color-mix(in oklab, var(--in-border), var(--in-fg) 30%);
246
+ }
247
+ .in-otp-cell[data-active] {
248
+ outline: 2px solid var(--in-focus);
249
+ outline-offset: -1px;
250
+ }
251
+ .in-otp[data-invalid] .in-otp-cell {
252
+ border-color: var(--in-danger);
253
+ }
254
+ .in-otp[data-invalid] .in-otp-cell[data-active] {
255
+ outline-color: var(--in-danger);
256
+ }
257
+ .in-otp[data-disabled] .in-otp-cell {
258
+ opacity: 0.55;
259
+ }
260
+
261
+ .in-otp-caret {
262
+ width: 1.5px;
263
+ height: 1.1em;
264
+ background: currentColor;
265
+ animation: in-caret 1s steps(2, jump-none) infinite;
266
+ }
267
+ @keyframes in-caret {
268
+ 50% {
269
+ opacity: 0;
270
+ }
271
+ }
272
+
273
+ .in-otp-separator {
274
+ flex: none;
275
+ width: 10px;
276
+ height: 2px;
277
+ border-radius: 1px;
278
+ background: var(--in-muted);
279
+ }
280
+
281
+ @media (prefers-reduced-motion: reduce) {
282
+ .in-otp-caret {
283
+ animation: none;
284
+ }
285
+ .in-otp-cell {
286
+ transition: none;
287
+ }
288
+ }
289
+
290
+ @media (forced-colors: active) {
291
+ .in-otp-cell[data-active],
292
+ .in-control:focus-within {
293
+ outline-color: Highlight;
294
+ }
295
+ }
296
+ }
@@ -0,0 +1,51 @@
1
+ # Modal
2
+
3
+ A modal and drawer for React 19 on the native `<dialog>` element, styled to match
4
+ the rest of the library. No runtime dependencies besides React.
5
+
6
+ The browser provides the top layer, the inert page behind, the focus trap and
7
+ focus return. The component adds controlled state, dismiss rules, an
8
+ `onBeforeClose` guard, sizes, drawers and enter/exit animation.
9
+
10
+ ## Setup
11
+
12
+ ```ts
13
+ import { Modal } from "@/components/modal";
14
+ import "@/components/modal/styles.css";
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```tsx
20
+ const [open, setOpen] = useState(false);
21
+
22
+ <button onClick={() => setOpen(true)}>Edit</button>
23
+ <Modal
24
+ open={open}
25
+ onOpenChange={setOpen}
26
+ title="Edit profile"
27
+ footer={({ close }) => <button onClick={close}>Done</button>}
28
+ >
29
+
30
+ </Modal>
31
+ ```
32
+
33
+ ## Props
34
+
35
+ | Prop | Type | Default | Description |
36
+ | --- | --- | --- | --- |
37
+ | `open` / `defaultOpen` / `onOpenChange` | `boolean` / `boolean` / `(open, reason?) => void` | — / `false` | Controlled or uncontrolled state. `reason`: `escape`, `backdrop`, `close-button`, `api`. |
38
+ | `title`, `description`, `aria-label` | `ReactNode` / `string` | — | Header text; `aria-label` names an untitled modal. |
39
+ | `children`, `footer` | `ReactNode \| ({ close }) => ReactNode` | — | Body and footer. |
40
+ | `size` | `sm` \| `md` \| `lg` \| `xl` \| `full` | `md` | 400 / 520 / 720 / 960 px or the whole screen. |
41
+ | `placement` | `center` \| `top` \| `left` \| `right` \| `bottom` | `center` | `left`, `right` and `bottom` are drawers. |
42
+ | `closeButton`, `closeOnEscape`, `closeOnBackdrop` | `boolean` | `true` | Dismiss options. |
43
+ | `onBeforeClose` | `(reason) => boolean \| Promise<boolean>` | — | Return `false` to stay open. |
44
+ | `initialFocus` | `RefObject<HTMLElement>` | — | Otherwise `[data-autofocus]`, then the first focusable element. |
45
+ | `keepMounted` | `boolean` | `false` | Keep the content's state while closed. |
46
+ | `className`, `classNames`, `style`, `localeText`, `id`, `ref` | — | — | Slots: `root`, `header`, `title`, `description`, `close`, `body`, `footer`. `ref`: `open()`, `close()`, `getElement()`. |
47
+
48
+ ## Known limits
49
+
50
+ - Exit animations need `transition-behavior: allow-discrete` (Chrome 117+, Safari 18+); elsewhere the modal closes instantly.
51
+ - Page scrolling is locked with `:root:has(.md-root[open])`, which can shift content by the scrollbar's width.
@@ -0,0 +1,55 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { canDismiss, confirmClose, isOutside } from "../core/dismiss";
3
+
4
+ describe("dismiss rules", () => {
5
+ it("allows Escape and backdrop by default, and lets each be turned off", () => {
6
+ expect(canDismiss("escape")).toBe(true);
7
+ expect(canDismiss("backdrop")).toBe(true);
8
+ expect(canDismiss("escape", { closeOnEscape: false })).toBe(false);
9
+ expect(canDismiss("backdrop", { closeOnBackdrop: false })).toBe(false);
10
+ });
11
+
12
+ it("always allows the close button and code", () => {
13
+ const locked = { closeOnEscape: false, closeOnBackdrop: false };
14
+ expect(canDismiss("close-button", locked)).toBe(true);
15
+ expect(canDismiss("api", locked)).toBe(true);
16
+ });
17
+ });
18
+
19
+ describe("backdrop hit test", () => {
20
+ const rect = { left: 100, top: 50, right: 500, bottom: 350 };
21
+
22
+ it("treats points outside the box as the backdrop", () => {
23
+ expect(isOutside(rect, 50, 200)).toBe(true);
24
+ expect(isOutside(rect, 300, 400)).toBe(true);
25
+ expect(isOutside(rect, 300, 200)).toBe(false);
26
+ });
27
+
28
+ it("counts the edges as inside", () => {
29
+ expect(isOutside(rect, 100, 50)).toBe(false);
30
+ expect(isOutside(rect, 500, 350)).toBe(false);
31
+ });
32
+ });
33
+
34
+ describe("close guard", () => {
35
+ it("closes without a guard", async () => {
36
+ expect(await confirmClose(undefined, "escape")).toBe(true);
37
+ });
38
+
39
+ it("stays open only on an explicit false", async () => {
40
+ expect(await confirmClose(() => true, "escape")).toBe(true);
41
+ expect(await confirmClose(() => false, "escape")).toBe(false);
42
+ expect(await confirmClose(() => Promise.resolve(false), "backdrop")).toBe(false);
43
+ expect(await confirmClose(() => undefined as unknown as boolean, "api")).toBe(true);
44
+ });
45
+
46
+ it("passes the reason and stays open when the guard throws", async () => {
47
+ const reasons: string[] = [];
48
+ await confirmClose((reason) => {
49
+ reasons.push(reason);
50
+ return true;
51
+ }, "close-button");
52
+ expect(reasons).toEqual(["close-button"]);
53
+ expect(await confirmClose(() => Promise.reject(new Error("save failed")), "api")).toBe(false);
54
+ });
55
+ });
@@ -0,0 +1,29 @@
1
+ import type { CloseGuard, CloseReason, DismissOptions, Rect } from "./types";
2
+
3
+ /** Whether a close request is allowed before asking `onBeforeClose`. Code and the close button always are. */
4
+ export function canDismiss(reason: CloseReason, options: DismissOptions = {}): boolean {
5
+ if (reason === "escape") return options.closeOnEscape ?? true;
6
+ if (reason === "backdrop") return options.closeOnBackdrop ?? true;
7
+ return true;
8
+ }
9
+
10
+ /**
11
+ * A native `<dialog>` reports clicks on its backdrop as clicks on itself, so a
12
+ * click counts as "on the backdrop" when it lands outside the dialog's box.
13
+ */
14
+ export function isOutside(rect: Rect, x: number, y: number): boolean {
15
+ return x < rect.left || x > rect.right || y < rect.top || y > rect.bottom;
16
+ }
17
+
18
+ /**
19
+ * Runs `onBeforeClose`. Only an explicit `false` (or a promise of it) keeps the
20
+ * modal open; a guard that throws keeps it open too, so unsaved work isn't lost.
21
+ */
22
+ export async function confirmClose(guard: CloseGuard | undefined, reason: CloseReason): Promise<boolean> {
23
+ if (!guard) return true;
24
+ try {
25
+ return (await guard(reason)) !== false;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
@@ -0,0 +1,3 @@
1
+ // Framework-free: the dismiss rules the Modal uses, so they can be tested and reused.
2
+ export { canDismiss, confirmClose, isOutside } from "./dismiss";
3
+ export type * from "./types";
@@ -0,0 +1,27 @@
1
+ export type ModalSize = "sm" | "md" | "lg" | "xl" | "full";
2
+
3
+ /** `center` and `top` are dialogs; `left`, `right` and `bottom` slide in as drawers. */
4
+ export type ModalPlacement = "center" | "top" | "left" | "right" | "bottom";
5
+
6
+ /** What asked the modal to close. */
7
+ export type CloseReason = "escape" | "backdrop" | "close-button" | "api";
8
+
9
+ export interface DismissOptions {
10
+ /** Default true. */
11
+ closeOnEscape?: boolean;
12
+ /** Default true. */
13
+ closeOnBackdrop?: boolean;
14
+ }
15
+
16
+ export interface Rect {
17
+ left: number;
18
+ top: number;
19
+ right: number;
20
+ bottom: number;
21
+ }
22
+
23
+ export type CloseGuard = (reason: CloseReason) => boolean | Promise<boolean>;
24
+
25
+ export interface ModalLocaleText {
26
+ close: string;
27
+ }
@@ -0,0 +1,5 @@
1
+ export { Modal } from "./react/Modal";
2
+ export type { ModalProps } from "./react/Modal";
3
+ export { defaultModalText } from "./react/locale";
4
+ export type { ModalHandle, ModalRenderProps, ModalSlot } from "./react/props";
5
+ export * from "./core";