@civitai/blocks-react 0.11.2 → 0.12.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.
Files changed (50) hide show
  1. package/README.md +90 -3
  2. package/dist/ui/Alert.d.ts +19 -0
  3. package/dist/ui/Alert.d.ts.map +1 -0
  4. package/dist/ui/Alert.js +12 -0
  5. package/dist/ui/Alert.js.map +1 -0
  6. package/dist/ui/Badge.d.ts +18 -0
  7. package/dist/ui/Badge.d.ts.map +1 -0
  8. package/dist/ui/Badge.js +25 -0
  9. package/dist/ui/Badge.js.map +1 -0
  10. package/dist/ui/Button.d.ts +35 -0
  11. package/dist/ui/Button.d.ts.map +1 -0
  12. package/dist/ui/Button.js +44 -0
  13. package/dist/ui/Button.js.map +1 -0
  14. package/dist/ui/Card.d.ts +14 -0
  15. package/dist/ui/Card.d.ts.map +1 -0
  16. package/dist/ui/Card.js +11 -0
  17. package/dist/ui/Card.js.map +1 -0
  18. package/dist/ui/Group.d.ts +15 -0
  19. package/dist/ui/Group.d.ts.map +1 -0
  20. package/dist/ui/Group.js +22 -0
  21. package/dist/ui/Group.js.map +1 -0
  22. package/dist/ui/Loader.d.ts +18 -0
  23. package/dist/ui/Loader.d.ts.map +1 -0
  24. package/dist/ui/Loader.js +14 -0
  25. package/dist/ui/Loader.js.map +1 -0
  26. package/dist/ui/Modal.d.ts +46 -0
  27. package/dist/ui/Modal.d.ts.map +1 -0
  28. package/dist/ui/Modal.js +67 -0
  29. package/dist/ui/Modal.js.map +1 -0
  30. package/dist/ui/Stack.d.ts +13 -0
  31. package/dist/ui/Stack.d.ts.map +1 -0
  32. package/dist/ui/Stack.js +21 -0
  33. package/dist/ui/Stack.js.map +1 -0
  34. package/dist/ui/TextInput.d.ts +26 -0
  35. package/dist/ui/TextInput.d.ts.map +1 -0
  36. package/dist/ui/TextInput.js +23 -0
  37. package/dist/ui/TextInput.js.map +1 -0
  38. package/dist/ui/Textarea.d.ts +32 -0
  39. package/dist/ui/Textarea.d.ts.map +1 -0
  40. package/dist/ui/Textarea.js +23 -0
  41. package/dist/ui/Textarea.js.map +1 -0
  42. package/dist/ui/index.d.ts +33 -4
  43. package/dist/ui/index.d.ts.map +1 -1
  44. package/dist/ui/index.js +26 -4
  45. package/dist/ui/index.js.map +1 -1
  46. package/dist/ui/styles.d.ts +19 -0
  47. package/dist/ui/styles.d.ts.map +1 -0
  48. package/dist/ui/styles.js +416 -0
  49. package/dist/ui/styles.js.map +1 -0
  50. package/package.json +1 -1
package/README.md CHANGED
@@ -208,7 +208,94 @@ track('generate_clicked', { modelId });
208
208
  ## The `/ui` subexport
209
209
 
210
210
  Opinionated components, imported separately so a transport-only block stays lean.
211
- v0 ships the headless, manifest-driven `SettingsForm`:
211
+ Two surfaces live here:
212
+
213
+ 1. The **W6 component pack** — a small, Civitai-looking, self-styled component
214
+ set you drop straight into a block.
215
+ 2. The headless, manifest-driven **`SettingsForm`** (host-themed native controls).
216
+
217
+ ### W6 component pack
218
+
219
+ A drop-in set of primitives that match Civitai's look (8px radius, the blue
220
+ primary, the dark/light surfaces) — **with zero setup**:
221
+
222
+ - **No Mantine dependency, no CSS import, no setup step.** The pack ships its
223
+ CSS as a string and injects it into your block document's `<head>` the first
224
+ time you render any component (idempotent). There's nothing to wire up.
225
+ - **First paint is briefly unstyled (FOUC).** Because the CSS injects in a
226
+ `useEffect` (after the first paint), the very first frame of a pack component
227
+ renders unstyled, then snaps to themed. It's a single frame and usually
228
+ unnoticeable. To eliminate it, call `injectBlocksStyles()` at module init in
229
+ your entry file (before the first render) so the stylesheet is present up
230
+ front — see `injectBlocksStyles` below (already exported).
231
+ - **Auto-themed via your block's `data-theme`.** Set `data-theme={theme}` on
232
+ your block's own root (from `useBlockContext().theme` — gotcha #60; the host
233
+ can't reach across the iframe to set it for you). The components read an
234
+ ancestor `[data-theme='dark']` / `[data-theme='light']`; **no attribute =
235
+ light**, matching the starter palette.
236
+
237
+ ```tsx
238
+ import { useRef } from 'react';
239
+ import { useBlockContext } from '@civitai/blocks-react';
240
+ import {
241
+ Button, TextInput, Textarea, Card, Stack, Group,
242
+ Alert, Loader, Badge, Modal,
243
+ } from '@civitai/blocks-react/ui';
244
+
245
+ export function App() {
246
+ const { ready, theme } = useBlockContext();
247
+ const rootRef = useRef<HTMLDivElement>(null);
248
+ if (!ready) return <div ref={rootRef}>Loading…</div>;
249
+
250
+ return (
251
+ // GOTCHA #60 — theme your OWN root; that's what the pack reads.
252
+ <div ref={rootRef} data-theme={theme}>
253
+ <Card>
254
+ <Stack gap={12}>
255
+ <Group justify="space-between">
256
+ <strong>My block</strong>
257
+ <Badge color="success">ready</Badge>
258
+ </Group>
259
+ <TextInput label="Prompt" description="What to generate" />
260
+ <Alert color="info" title="Heads up">Costs Buzz.</Alert>
261
+ <Button fullWidth onClick={() => {/* … */}}>Generate</Button>
262
+ </Stack>
263
+ </Card>
264
+ </div>
265
+ );
266
+ }
267
+ ```
268
+
269
+ **The components** (each with an exported props interface):
270
+
271
+ | Component | Highlights |
272
+ |---|---|
273
+ | `Button` | `variant` (`filled`/`light`/`outline`/`subtle`), `size`, `color`, `loading` (shows a `Loader`, disables + `aria-busy`), `fullWidth`, `leftSection`/`rightSection`. Defaults to `type="button"`. |
274
+ | `TextInput` / `Textarea` | `label` / `description` / `error` / `required`, wired via `htmlFor` + `aria-describedby` + `aria-invalid`. `Textarea` takes `minRows`. |
275
+ | `Card` | themed surface; `withBorder`, `padding`, `radius`. |
276
+ | `Stack` / `Group` | vertical / horizontal flex; `gap`, `align`, `justify` (+ `Group` `wrap`). |
277
+ | `Alert` | `color` (`info`/`success`/`warning`/`error`), `title`, `withCloseButton` + `onClose`; `role="alert"`. |
278
+ | `Loader` | CSS-keyframe spinner; `size`, `color`; `role="status"`. |
279
+ | `Badge` | `variant`, `size`, `color`. |
280
+ | `Modal` | `opened` + `onClose`, `title`, `size`; `role="dialog"` + `aria-modal`, Escape- and overlay-click-to-close, focuses the panel on open and restores focus on close. |
281
+
282
+ Each component forwards `className` + `style`, forwards a `ref` to its DOM
283
+ node (where it wraps one), and carries a `data-civitai-ui="<name>"` hook. Need
284
+ to inject the CSS yourself (SSR, or a non-React shell)? Call
285
+ `injectBlocksStyles(doc?)` once, or read the raw `BLOCKS_UI_STYLES` string.
286
+ `useBlocksStyles()` is the hook the components call internally.
287
+
288
+ > **Modal focus limitation (v0):** the modal focuses its panel on open and
289
+ > restores focus on close, but it does **not** trap focus — Tab can still reach
290
+ > content behind the overlay. That's fine for a simple confirm/settings dialog
291
+ > inside the sandboxed block; a full focus-trap is a v1 follow-up (kept
292
+ > dependency-free here on purpose).
293
+
294
+ ### `SettingsForm`
295
+
296
+ The headless, manifest-driven settings form (its contract is intentionally
297
+ **unstyled native controls** — the host page themes it, so it does *not* use the
298
+ W6 pack):
212
299
 
213
300
  ```tsx
214
301
  import { SettingsForm } from '@civitai/blocks-react/ui';
@@ -222,8 +309,8 @@ import { SettingsForm } from '@civitai/blocks-react/ui';
222
309
  />
223
310
  ```
224
311
 
225
- Unstyled native controls (host themes them). `isFieldVisible` + `SettingsFormError`
226
- are also exported. See the `settings` example.
312
+ `isFieldVisible` + `SettingsFormError` are also exported. See the `settings`
313
+ example.
227
314
 
228
315
  ## Lower-level transport
229
316
 
@@ -0,0 +1,19 @@
1
+ export type AlertColor = 'info' | 'success' | 'warning' | 'error';
2
+ export interface AlertProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'color' | 'title'> {
3
+ /** Semantic color. Defaults to `'info'`. */
4
+ color?: AlertColor;
5
+ /** Optional bold title above the body. */
6
+ title?: React.ReactNode;
7
+ /** Render a close (×) button. Requires `onClose` to be meaningful. */
8
+ withCloseButton?: boolean;
9
+ /** Invoked when the close button is clicked. */
10
+ onClose?: () => void;
11
+ /** Accessible label for the close button. Defaults to "Close alert". */
12
+ closeButtonLabel?: string;
13
+ }
14
+ /**
15
+ * A themed callout. Carries `role="alert"` so it's announced. Optional
16
+ * dismiss button calls `onClose`. Auto-themed via `useBlocksStyles()`.
17
+ */
18
+ export declare const Alert: import("react").ForwardRefExoticComponent<AlertProps & import("react").RefAttributes<HTMLDivElement>>;
19
+ //# sourceMappingURL=Alert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Alert.d.ts","sourceRoot":"","sources":["../../src/ui/Alert.tsx"],"names":[],"mappings":"AAIA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAElE,MAAM,WAAW,UACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IACrE,4CAA4C;IAC5C,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,sEAAsE;IACtE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;GAGG;AACH,eAAO,MAAM,KAAK,uGAuChB,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from 'react';
3
+ import { useBlocksStyles } from './styles.js';
4
+ /**
5
+ * A themed callout. Carries `role="alert"` so it's announced. Optional
6
+ * dismiss button calls `onClose`. Auto-themed via `useBlocksStyles()`.
7
+ */
8
+ export const Alert = forwardRef(function Alert({ color = 'info', title, withCloseButton = false, onClose, closeButtonLabel = 'Close alert', children, ...rest }, ref) {
9
+ useBlocksStyles();
10
+ return (_jsxs("div", { ref: ref, ...rest, "data-civitai-ui": "alert", "data-color": color, role: "alert", children: [_jsxs("div", { "data-civitai-ui-alert-body": true, children: [title != null ? (_jsx("div", { "data-civitai-ui-alert-title": true, children: title })) : null, children] }), withCloseButton ? (_jsx("button", { type: "button", "data-civitai-ui-alert-close": true, "aria-label": closeButtonLabel, onClick: () => onClose?.(), children: "\u00D7" })) : null] }));
11
+ });
12
+ //# sourceMappingURL=Alert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Alert.js","sourceRoot":"","sources":["../../src/ui/Alert.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAEnC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAkB9C;;;GAGG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,UAAU,CAA6B,SAAS,KAAK,CACxE,EACE,KAAK,GAAG,MAAM,EACd,KAAK,EACL,eAAe,GAAG,KAAK,EACvB,OAAO,EACP,gBAAgB,GAAG,aAAa,EAChC,QAAQ,EACR,GAAG,IAAI,EACR,EACD,GAAG;IAEH,eAAe,EAAE,CAAC;IAClB,OAAO,CACL,eACE,GAAG,EAAE,GAAG,KACJ,IAAI,qBACQ,OAAO,gBACX,KAAK,EACjB,IAAI,EAAC,OAAO,aAEZ,8DACG,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CACf,6DAAkC,KAAK,GAAO,CAC/C,CAAC,CAAC,CAAC,IAAI,EACP,QAAQ,IACL,EACL,eAAe,CAAC,CAAC,CAAC,CACjB,iBACE,IAAI,EAAC,QAAQ,qDAED,gBAAgB,EAC5B,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,uBAGnB,CACV,CAAC,CAAC,CAAC,IAAI,IACJ,CACP,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,18 @@
1
+ export type BadgeVariant = 'filled' | 'light' | 'outline';
2
+ export type BadgeSize = 'sm' | 'md' | 'lg';
3
+ export interface BadgeProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'color'> {
4
+ /** Visual style. Defaults to `'light'`. */
5
+ variant?: BadgeVariant;
6
+ /** Size preset. Defaults to `'md'`. */
7
+ size?: BadgeSize;
8
+ /**
9
+ * Accent color. `'primary'` (default), a semantic token name
10
+ * (`'error' | 'success' | 'warning' | 'info'`), or any CSS color string.
11
+ */
12
+ color?: 'primary' | 'error' | 'success' | 'warning' | 'info' | (string & {});
13
+ }
14
+ /**
15
+ * A small status/label pill. Wraps a `<span>` (ref-forwarded). Auto-themed.
16
+ */
17
+ export declare const Badge: import("react").ForwardRefExoticComponent<BadgeProps & import("react").RefAttributes<HTMLSpanElement>>;
18
+ //# sourceMappingURL=Badge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Badge.d.ts","sourceRoot":"","sources":["../../src/ui/Badge.tsx"],"names":[],"mappings":"AAIA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAC1D,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3C,MAAM,WAAW,UACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC5D,2CAA2C;IAC3C,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,uCAAuC;IACvC,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;CAC9E;AAUD;;GAEG;AACH,eAAO,MAAM,KAAK,wGAyBhB,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from 'react';
3
+ import { useBlocksStyles } from './styles.js';
4
+ const SEMANTIC_COLORS = new Set(['error', 'success', 'warning', 'info']);
5
+ function resolveAccent(color) {
6
+ if (!color || color === 'primary')
7
+ return undefined;
8
+ if (SEMANTIC_COLORS.has(color))
9
+ return `var(--ci-color-${color})`;
10
+ return color;
11
+ }
12
+ /**
13
+ * A small status/label pill. Wraps a `<span>` (ref-forwarded). Auto-themed.
14
+ */
15
+ export const Badge = forwardRef(function Badge({ variant = 'light', size = 'md', color = 'primary', style, children, ...rest }, ref) {
16
+ useBlocksStyles();
17
+ const accent = resolveAccent(color);
18
+ return (_jsx("span", { ref: ref, ...rest, "data-civitai-ui": "badge", "data-variant": variant, "data-size": size, style: accent
19
+ ? {
20
+ ['--ci-color-primary']: accent,
21
+ ...style,
22
+ }
23
+ : style, children: children }));
24
+ });
25
+ //# sourceMappingURL=Badge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Badge.js","sourceRoot":"","sources":["../../src/ui/Badge.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAEnC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAkB9C,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;AAEzE,SAAS,aAAa,CAAC,KAA0B;IAC/C,IAAI,CAAC,KAAK,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACpD,IAAI,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC;QAAE,OAAO,kBAAkB,KAAK,GAAG,CAAC;IAClE,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,UAAU,CAA8B,SAAS,KAAK,CACzE,EAAE,OAAO,GAAG,OAAO,EAAE,IAAI,GAAG,IAAI,EAAE,KAAK,GAAG,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAC/E,GAAG;IAEH,eAAe,EAAE,CAAC;IAClB,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACpC,OAAO,CACL,eACE,GAAG,EAAE,GAAG,KACJ,IAAI,qBACQ,OAAO,kBACT,OAAO,eACV,IAAI,EACf,KAAK,EACH,MAAM;YACJ,CAAC,CAAE;gBACC,CAAC,oBAA8B,CAAC,EAAE,MAAM;gBACxC,GAAG,KAAK;aACe;YAC3B,CAAC,CAAC,KAAK,YAGV,QAAQ,GACJ,CACR,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,35 @@
1
+ export type ButtonVariant = 'filled' | 'light' | 'outline' | 'subtle';
2
+ export type ButtonSize = 'sm' | 'md' | 'lg';
3
+ export interface ButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'color'> {
4
+ /** Visual style. Defaults to `'filled'`. */
5
+ variant?: ButtonVariant;
6
+ /** Size preset. Defaults to `'md'`. */
7
+ size?: ButtonSize;
8
+ /**
9
+ * Accent color. `'primary'` (default) or a semantic token name
10
+ * (`'error' | 'success' | 'warning' | 'info'`), or any CSS color string —
11
+ * it overrides the `--ci-color-primary` the variant styling reads.
12
+ */
13
+ color?: 'primary' | 'error' | 'success' | 'warning' | 'info' | (string & {});
14
+ /**
15
+ * Show a spinner and disable the button. Sets `aria-busy` and blocks clicks
16
+ * (both via the native `disabled` and a guard, so a controlled `onClick`
17
+ * never fires while loading).
18
+ */
19
+ loading?: boolean;
20
+ /** Stretch to fill the container width. */
21
+ fullWidth?: boolean;
22
+ /** Content rendered before the label (icon, etc.). */
23
+ leftSection?: React.ReactNode;
24
+ /** Content rendered after the label. */
25
+ rightSection?: React.ReactNode;
26
+ }
27
+ /**
28
+ * Themed button. Wraps a native `<button>` (ref-forwarded, all native props
29
+ * pass through). `loading` shows a `<Loader>` and disables the control.
30
+ *
31
+ * Styling is automatic via `useBlocksStyles()`; the `data-variant` / `data-size`
32
+ * / `data-full-width` attributes drive the injected CSS.
33
+ */
34
+ export declare const Button: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
35
+ //# sourceMappingURL=Button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Button.d.ts","sourceRoot":"","sources":["../../src/ui/Button.tsx"],"names":[],"mappings":"AAKA,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAC;AACtE,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE5C,MAAM,WAAW,WACf,SAAQ,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IACpE,4CAA4C;IAC5C,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,uCAAuC;IACvC,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IAC7E;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,sDAAsD;IACtD,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,wCAAwC;IACxC,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAChC;AAUD;;;;;;GAMG;AACH,eAAO,MAAM,MAAM,2GAmEjB,CAAC"}
@@ -0,0 +1,44 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from 'react';
3
+ import { Loader } from './Loader.js';
4
+ import { useBlocksStyles } from './styles.js';
5
+ const SEMANTIC_COLORS = new Set(['error', 'success', 'warning', 'info']);
6
+ function resolveAccent(color) {
7
+ if (!color || color === 'primary')
8
+ return undefined;
9
+ if (SEMANTIC_COLORS.has(color))
10
+ return `var(--ci-color-${color})`;
11
+ return color;
12
+ }
13
+ /**
14
+ * Themed button. Wraps a native `<button>` (ref-forwarded, all native props
15
+ * pass through). `loading` shows a `<Loader>` and disables the control.
16
+ *
17
+ * Styling is automatic via `useBlocksStyles()`; the `data-variant` / `data-size`
18
+ * / `data-full-width` attributes drive the injected CSS.
19
+ */
20
+ export const Button = forwardRef(function Button({ variant = 'filled', size = 'md', color = 'primary', loading = false, fullWidth = false, leftSection, rightSection, disabled, onClick, children, style, type, ...rest }, ref) {
21
+ useBlocksStyles();
22
+ const accent = resolveAccent(color);
23
+ const isDisabled = disabled || loading;
24
+ return (_jsxs("button", { ref: ref,
25
+ // Default to type="button" — a block author dropping a <Button> into a
26
+ // <form> almost never wants an implicit submit. They can opt into
27
+ // type="submit" explicitly.
28
+ type: type ?? 'button', ...rest, "data-civitai-ui": "button", "data-variant": variant, "data-size": size, "data-full-width": fullWidth ? 'true' : undefined, disabled: isDisabled, "aria-busy": loading || undefined, onClick: (e) => {
29
+ // Belt-and-suspenders: a `disabled` <button> won't fire onClick, but
30
+ // guard anyway so `loading` reliably blocks the handler.
31
+ if (isDisabled) {
32
+ e.preventDefault();
33
+ return;
34
+ }
35
+ onClick?.(e);
36
+ }, style: accent
37
+ ? {
38
+ ['--ci-color-primary']: accent,
39
+ ['--ci-color-primary-hover']: accent,
40
+ ...style,
41
+ }
42
+ : style, children: [loading ? _jsx(Loader, { size: "sm", "aria-hidden": "true" }) : null, leftSection != null ? (_jsx("span", { "data-civitai-ui-section": "left", children: leftSection })) : null, children, rightSection != null ? (_jsx("span", { "data-civitai-ui-section": "right", children: rightSection })) : null] }));
43
+ });
44
+ //# sourceMappingURL=Button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Button.js","sourceRoot":"","sources":["../../src/ui/Button.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAEnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AA+B9C,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;AAEzE,SAAS,aAAa,CAAC,KAA2B;IAChD,IAAI,CAAC,KAAK,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACpD,IAAI,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC;QAAE,OAAO,kBAAkB,KAAK,GAAG,CAAC;IAClE,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,UAAU,CAAiC,SAAS,MAAM,CAC9E,EACE,OAAO,GAAG,QAAQ,EAClB,IAAI,GAAG,IAAI,EACX,KAAK,GAAG,SAAS,EACjB,OAAO,GAAG,KAAK,EACf,SAAS,GAAG,KAAK,EACjB,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,KAAK,EACL,IAAI,EACJ,GAAG,IAAI,EACR,EACD,GAAG;IAEH,eAAe,EAAE,CAAC;IAClB,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,UAAU,GAAG,QAAQ,IAAI,OAAO,CAAC;IACvC,OAAO,CACL,kBACE,GAAG,EAAE,GAAG;QACR,uEAAuE;QACvE,kEAAkE;QAClE,4BAA4B;QAC5B,IAAI,EAAE,IAAI,IAAI,QAAQ,KAIlB,IAAI,qBACQ,QAAQ,kBACV,OAAO,eACV,IAAI,qBACE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAC/C,QAAQ,EAAE,UAAU,eACT,OAAO,IAAI,SAAS,EAC/B,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;YACb,qEAAqE;YACrE,yDAAyD;YACzD,IAAI,UAAU,EAAE,CAAC;gBACf,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,OAAO;YACT,CAAC;YACD,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;QACf,CAAC,EACD,KAAK,EACH,MAAM;YACJ,CAAC,CAAE;gBACC,CAAC,oBAA8B,CAAC,EAAE,MAAM;gBACxC,CAAC,0BAAoC,CAAC,EAAE,MAAM;gBAC9C,GAAG,KAAK;aACe;YAC3B,CAAC,CAAC,KAAK,aAGV,OAAO,CAAC,CAAC,CAAC,KAAC,MAAM,IAAC,IAAI,EAAC,IAAI,iBAAa,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,EACxD,WAAW,IAAI,IAAI,CAAC,CAAC,CAAC,CACrB,0CAA8B,MAAM,YAAE,WAAW,GAAQ,CAC1D,CAAC,CAAC,CAAC,IAAI,EACP,QAAQ,EACR,YAAY,IAAI,IAAI,CAAC,CAAC,CAAC,CACtB,0CAA8B,OAAO,YAAE,YAAY,GAAQ,CAC5D,CAAC,CAAC,CAAC,IAAI,IACD,CACV,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,14 @@
1
+ export type CardPadding = 'sm' | 'md' | 'lg';
2
+ export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
3
+ /** Draw the themed border. Defaults to `true`. */
4
+ withBorder?: boolean;
5
+ /** Inner padding preset. Defaults to `'md'`. */
6
+ padding?: CardPadding;
7
+ /** Override the corner radius (any CSS length). Defaults to the token. */
8
+ radius?: string | number;
9
+ }
10
+ /**
11
+ * A themed surface container. Wraps a `<div>` (ref-forwarded). Auto-themed.
12
+ */
13
+ export declare const Card: import("react").ForwardRefExoticComponent<CardProps & import("react").RefAttributes<HTMLDivElement>>;
14
+ //# sourceMappingURL=Card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Card.d.ts","sourceRoot":"","sources":["../../src/ui/Card.tsx"],"names":[],"mappings":"AAIA,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE7C,MAAM,WAAW,SAAU,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IACrE,kDAAkD;IAClD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gDAAgD;IAChD,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED;;GAEG;AACH,eAAO,MAAM,IAAI,sGAiBf,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from 'react';
3
+ import { useBlocksStyles } from './styles.js';
4
+ /**
5
+ * A themed surface container. Wraps a `<div>` (ref-forwarded). Auto-themed.
6
+ */
7
+ export const Card = forwardRef(function Card({ withBorder = true, padding = 'md', radius, style, children, ...rest }, ref) {
8
+ useBlocksStyles();
9
+ return (_jsx("div", { ref: ref, ...rest, "data-civitai-ui": "card", "data-with-border": withBorder ? 'true' : undefined, "data-padding": padding, style: radius != null ? { borderRadius: radius, ...style } : style, children: children }));
10
+ });
11
+ //# sourceMappingURL=Card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Card.js","sourceRoot":"","sources":["../../src/ui/Card.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAEnC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAa9C;;GAEG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,UAAU,CAA4B,SAAS,IAAI,CACrE,EAAE,UAAU,GAAG,IAAI,EAAE,OAAO,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EACvE,GAAG;IAEH,eAAe,EAAE,CAAC;IAClB,OAAO,CACL,cACE,GAAG,EAAE,GAAG,KACJ,IAAI,qBACQ,MAAM,sBACJ,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,kBACnC,OAAO,EACrB,KAAK,EAAE,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,YAEjE,QAAQ,GACL,CACP,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,15 @@
1
+ export interface GroupProps extends React.HTMLAttributes<HTMLDivElement> {
2
+ /** Gap between children (any CSS length, or a number → px). Defaults to 12. */
3
+ gap?: string | number;
4
+ /** `justify-content` value. */
5
+ justify?: React.CSSProperties['justifyContent'];
6
+ /** `align-items` value. Defaults to `'center'`. */
7
+ align?: React.CSSProperties['alignItems'];
8
+ /** Allow children to wrap onto multiple lines. Defaults to `true`. */
9
+ wrap?: boolean;
10
+ }
11
+ /**
12
+ * Horizontal flex container. Wraps a `<div>` (ref-forwarded). Auto-themed.
13
+ */
14
+ export declare const Group: import("react").ForwardRefExoticComponent<GroupProps & import("react").RefAttributes<HTMLDivElement>>;
15
+ //# sourceMappingURL=Group.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Group.d.ts","sourceRoot":"","sources":["../../src/ui/Group.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,UAAW,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IACtE,+EAA+E;IAC/E,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAChD,mDAAmD;IACnD,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;IAC1C,sEAAsE;IACtE,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAOD;;GAEG;AACH,eAAO,MAAM,KAAK,uGAqBhB,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from 'react';
3
+ import { useBlocksStyles } from './styles.js';
4
+ function toLength(v) {
5
+ if (v == null)
6
+ return undefined;
7
+ return typeof v === 'number' ? `${v}px` : v;
8
+ }
9
+ /**
10
+ * Horizontal flex container. Wraps a `<div>` (ref-forwarded). Auto-themed.
11
+ */
12
+ export const Group = forwardRef(function Group({ gap = 12, justify, align = 'center', wrap = true, style, children, ...rest }, ref) {
13
+ useBlocksStyles();
14
+ return (_jsx("div", { ref: ref, ...rest, "data-civitai-ui": "group", style: {
15
+ gap: toLength(gap),
16
+ justifyContent: justify,
17
+ alignItems: align,
18
+ flexWrap: wrap ? 'wrap' : 'nowrap',
19
+ ...style,
20
+ }, children: children }));
21
+ });
22
+ //# sourceMappingURL=Group.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Group.js","sourceRoot":"","sources":["../../src/ui/Group.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAEnC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAa9C,SAAS,QAAQ,CAAC,CAA8B;IAC9C,IAAI,CAAC,IAAI,IAAI;QAAE,OAAO,SAAS,CAAC;IAChC,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,UAAU,CAA6B,SAAS,KAAK,CACxE,EAAE,GAAG,GAAG,EAAE,EAAE,OAAO,EAAE,KAAK,GAAG,QAAQ,EAAE,IAAI,GAAG,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAC9E,GAAG;IAEH,eAAe,EAAE,CAAC;IAClB,OAAO,CACL,cACE,GAAG,EAAE,GAAG,KACJ,IAAI,qBACQ,OAAO,EACvB,KAAK,EAAE;YACL,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC;YAClB,cAAc,EAAE,OAAO;YACvB,UAAU,EAAE,KAAK;YACjB,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ;YAClC,GAAG,KAAK;SACT,YAEA,QAAQ,GACL,CACP,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,18 @@
1
+ export type LoaderSize = 'sm' | 'md' | 'lg';
2
+ export interface LoaderProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'color'> {
3
+ /** Spinner diameter. Defaults to `'md'`. */
4
+ size?: LoaderSize;
5
+ /**
6
+ * Spinner color. Any CSS color; defaults to the primary token. Inside a
7
+ * `<Button loading>` the loader inherits the button's text color instead.
8
+ */
9
+ color?: string;
10
+ }
11
+ /**
12
+ * A CSS-keyframe spinner. Headless, auto-themed. Reused by `<Button loading>`.
13
+ *
14
+ * Carries `role="status"` + an accessible label so screen readers announce a
15
+ * busy state; pass `aria-label` to override the default "Loading".
16
+ */
17
+ export declare const Loader: import("react").ForwardRefExoticComponent<LoaderProps & import("react").RefAttributes<HTMLSpanElement>>;
18
+ //# sourceMappingURL=Loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Loader.d.ts","sourceRoot":"","sources":["../../src/ui/Loader.tsx"],"names":[],"mappings":"AAIA,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE5C,MAAM,WAAW,WACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC5D,4CAA4C;IAC5C,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,eAAO,MAAM,MAAM,yGAkBjB,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from 'react';
3
+ import { useBlocksStyles } from './styles.js';
4
+ /**
5
+ * A CSS-keyframe spinner. Headless, auto-themed. Reused by `<Button loading>`.
6
+ *
7
+ * Carries `role="status"` + an accessible label so screen readers announce a
8
+ * busy state; pass `aria-label` to override the default "Loading".
9
+ */
10
+ export const Loader = forwardRef(function Loader({ size = 'md', color, style, ...rest }, ref) {
11
+ useBlocksStyles();
12
+ return (_jsx("span", { ref: ref, ...rest, "data-civitai-ui": "loader", "data-size": size, role: "status", "aria-label": rest['aria-label'] ?? 'Loading', style: color ? { color, ...style } : style }));
13
+ });
14
+ //# sourceMappingURL=Loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Loader.js","sourceRoot":"","sources":["../../src/ui/Loader.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAEnC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAe9C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,UAAU,CAA+B,SAAS,MAAM,CAC5E,EAAE,IAAI,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,EACtC,GAAG;IAEH,eAAe,EAAE,CAAC;IAClB,OAAO,CACL,eACE,GAAG,EAAE,GAAG,KACJ,IAAI,qBACQ,QAAQ,eACb,IAAI,EACf,IAAI,EAAC,QAAQ,gBAGD,IAAI,CAAC,YAAY,CAAC,IAAI,SAAS,EAC3C,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,GAC1C,CACH,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,46 @@
1
+ export type ModalSize = 'sm' | 'md' | 'lg';
2
+ export interface ModalProps {
3
+ /** Whether the modal is shown. When `false`, nothing is rendered. */
4
+ opened: boolean;
5
+ /**
6
+ * Called when the user requests close — Escape, overlay click, or the
7
+ * header close button. The parent owns `opened`, so it must flip it.
8
+ */
9
+ onClose: () => void;
10
+ /** Optional header title (also wires `aria-labelledby`). */
11
+ title?: React.ReactNode;
12
+ /** Panel width preset. Defaults to `'md'`. */
13
+ size?: ModalSize;
14
+ /** Show the header close (×) button. Defaults to `true`. */
15
+ withCloseButton?: boolean;
16
+ /** Close when the dimmed overlay (outside the panel) is clicked. Default `true`. */
17
+ closeOnOverlayClick?: boolean;
18
+ /** Close when Escape is pressed. Default `true`. */
19
+ closeOnEscape?: boolean;
20
+ /** Accessible label for the close button. Defaults to "Close". */
21
+ closeButtonLabel?: string;
22
+ /** Class applied to the panel element. */
23
+ className?: string;
24
+ /** Style applied to the panel element. */
25
+ style?: React.CSSProperties;
26
+ /** Modal body content. */
27
+ children?: React.ReactNode;
28
+ }
29
+ /**
30
+ * A lightweight modal dialog.
31
+ *
32
+ * `position: fixed` overlay covers the iframe viewport — correct here, since
33
+ * the iframe IS the block's surface (no host bleed-through to worry about).
34
+ *
35
+ * Accessibility: `role="dialog"` + `aria-modal="true"`, `aria-labelledby` when
36
+ * a `title` is given. On open the panel is focused and the previously-focused
37
+ * element is restored on close. Escape and overlay-click both call `onClose`;
38
+ * a click inside the panel does NOT.
39
+ *
40
+ * v0 limitation (documented): this does NOT trap focus inside the panel — Tab
41
+ * can still reach content behind the overlay. Sufficient for a simple
42
+ * confirm/settings dialog inside the sandboxed block; a full focus-trap is a
43
+ * v1 follow-up (kept dependency-free here on purpose).
44
+ */
45
+ export declare function Modal(props: ModalProps): React.JSX.Element | null;
46
+ //# sourceMappingURL=Modal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../src/ui/Modal.tsx"],"names":[],"mappings":"AAIA,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3C,MAAM,WAAW,UAAU;IACzB,qEAAqE;IACrE,MAAM,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,4DAA4D;IAC5D,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,4DAA4D;IAC5D,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,oFAAoF;IACpF,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,oDAAoD;IACpD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kEAAkE;IAClE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,GAAG,IAAI,CAmGjE"}
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useId, useRef } from 'react';
3
+ import { useBlocksStyles } from './styles.js';
4
+ /**
5
+ * A lightweight modal dialog.
6
+ *
7
+ * `position: fixed` overlay covers the iframe viewport — correct here, since
8
+ * the iframe IS the block's surface (no host bleed-through to worry about).
9
+ *
10
+ * Accessibility: `role="dialog"` + `aria-modal="true"`, `aria-labelledby` when
11
+ * a `title` is given. On open the panel is focused and the previously-focused
12
+ * element is restored on close. Escape and overlay-click both call `onClose`;
13
+ * a click inside the panel does NOT.
14
+ *
15
+ * v0 limitation (documented): this does NOT trap focus inside the panel — Tab
16
+ * can still reach content behind the overlay. Sufficient for a simple
17
+ * confirm/settings dialog inside the sandboxed block; a full focus-trap is a
18
+ * v1 follow-up (kept dependency-free here on purpose).
19
+ */
20
+ export function Modal(props) {
21
+ const { opened, onClose, title, size = 'md', withCloseButton = true, closeOnOverlayClick = true, closeOnEscape = true, closeButtonLabel = 'Close', className, style, children, } = props;
22
+ useBlocksStyles();
23
+ const panelRef = useRef(null);
24
+ const previouslyFocused = useRef(null);
25
+ const reactId = useId();
26
+ const titleId = `ci-modal-title-${reactId}`;
27
+ // Focus the panel on open; restore focus to the prior element on close.
28
+ useEffect(() => {
29
+ if (!opened)
30
+ return;
31
+ previouslyFocused.current =
32
+ typeof document !== 'undefined' ? document.activeElement : null;
33
+ // Focus after paint so the element exists and is focusable.
34
+ panelRef.current?.focus();
35
+ return () => {
36
+ const prev = previouslyFocused.current;
37
+ if (prev instanceof HTMLElement)
38
+ prev.focus();
39
+ };
40
+ }, [opened]);
41
+ // Escape-to-close. Listener only attached while open.
42
+ useEffect(() => {
43
+ if (!opened || !closeOnEscape)
44
+ return;
45
+ const onKeyDown = (e) => {
46
+ if (e.key === 'Escape') {
47
+ // Don't stopPropagation — that swallows Escape unpredictably when two
48
+ // modals (or an author's own document Escape handler) are present. v0
49
+ // assumes a single modal, so letting the event continue is correct.
50
+ onClose();
51
+ }
52
+ };
53
+ document.addEventListener('keydown', onKeyDown);
54
+ return () => document.removeEventListener('keydown', onKeyDown);
55
+ }, [opened, closeOnEscape, onClose]);
56
+ if (!opened)
57
+ return null;
58
+ return (_jsx("div", { "data-civitai-ui": "modal-overlay", onMouseDown: (e) => {
59
+ // Only close on a click that starts AND lands on the overlay itself —
60
+ // a drag that began inside the panel and released on the overlay must
61
+ // not close it.
62
+ if (closeOnOverlayClick && e.target === e.currentTarget) {
63
+ onClose();
64
+ }
65
+ }, children: _jsxs("div", { ref: panelRef, className: className, "data-civitai-ui": "modal", "data-size": size, role: "dialog", "aria-modal": "true", "aria-labelledby": title != null ? titleId : undefined, tabIndex: -1, style: style, children: [title != null || withCloseButton ? (_jsxs("div", { "data-civitai-ui-modal-header": true, children: [title != null ? (_jsx("h2", { id: titleId, "data-civitai-ui-modal-title": true, children: title })) : (_jsx("span", {})), withCloseButton ? (_jsx("button", { type: "button", "data-civitai-ui-modal-close": true, "aria-label": closeButtonLabel, onClick: () => onClose(), children: "\u00D7" })) : null] })) : null, _jsx("div", { "data-civitai-ui-modal-body": true, children: children })] }) }));
66
+ }
67
+ //# sourceMappingURL=Modal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../src/ui/Modal.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export interface StackProps extends React.HTMLAttributes<HTMLDivElement> {
2
+ /** Gap between children (any CSS length, or a number → px). Defaults to 12. */
3
+ gap?: string | number;
4
+ /** `align-items` value. */
5
+ align?: React.CSSProperties['alignItems'];
6
+ /** `justify-content` value. */
7
+ justify?: React.CSSProperties['justifyContent'];
8
+ }
9
+ /**
10
+ * Vertical flex container. Wraps a `<div>` (ref-forwarded). Auto-themed.
11
+ */
12
+ export declare const Stack: import("react").ForwardRefExoticComponent<StackProps & import("react").RefAttributes<HTMLDivElement>>;
13
+ //# sourceMappingURL=Stack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Stack.d.ts","sourceRoot":"","sources":["../../src/ui/Stack.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,UAAW,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IACtE,+EAA+E;IAC/E,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;IAC1C,+BAA+B;IAC/B,OAAO,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC;CACjD;AAOD;;GAEG;AACH,eAAO,MAAM,KAAK,uGAoBhB,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from 'react';
3
+ import { useBlocksStyles } from './styles.js';
4
+ function toLength(v) {
5
+ if (v == null)
6
+ return undefined;
7
+ return typeof v === 'number' ? `${v}px` : v;
8
+ }
9
+ /**
10
+ * Vertical flex container. Wraps a `<div>` (ref-forwarded). Auto-themed.
11
+ */
12
+ export const Stack = forwardRef(function Stack({ gap = 12, align, justify, style, children, ...rest }, ref) {
13
+ useBlocksStyles();
14
+ return (_jsx("div", { ref: ref, ...rest, "data-civitai-ui": "stack", style: {
15
+ gap: toLength(gap),
16
+ alignItems: align,
17
+ justifyContent: justify,
18
+ ...style,
19
+ }, children: children }));
20
+ });
21
+ //# sourceMappingURL=Stack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Stack.js","sourceRoot":"","sources":["../../src/ui/Stack.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAEnC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAW9C,SAAS,QAAQ,CAAC,CAA8B;IAC9C,IAAI,CAAC,IAAI,IAAI;QAAE,OAAO,SAAS,CAAC;IAChC,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,UAAU,CAA6B,SAAS,KAAK,CACxE,EAAE,GAAG,GAAG,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EACtD,GAAG;IAEH,eAAe,EAAE,CAAC;IAClB,OAAO,CACL,cACE,GAAG,EAAE,GAAG,KACJ,IAAI,qBACQ,OAAO,EACvB,KAAK,EAAE;YACL,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC;YAClB,UAAU,EAAE,KAAK;YACjB,cAAc,EAAE,OAAO;YACvB,GAAG,KAAK;SACT,YAEA,QAAQ,GACL,CACP,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,26 @@
1
+ export interface TextInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
2
+ /** Visible field label, rendered in a `<label>` linked to the input. */
3
+ label?: React.ReactNode;
4
+ /** Helper text under the label, linked via `aria-describedby`. */
5
+ description?: React.ReactNode;
6
+ /**
7
+ * Error message. When set, the input gets `aria-invalid="true"`, the message
8
+ * is announced (`role="alert"`) and linked via `aria-describedby`.
9
+ */
10
+ error?: React.ReactNode;
11
+ /** Mark required: shows an asterisk and sets the native `required`. */
12
+ required?: boolean;
13
+ /** Class on the wrapping element (the `<input>` is `inputClassName`). */
14
+ className?: string;
15
+ /** Class applied to the native `<input>`. */
16
+ inputClassName?: string;
17
+ }
18
+ /**
19
+ * Labeled text input. Wraps a native `<input>` (ref-forwarded). The label,
20
+ * description and error are wired to the control via `htmlFor` / `id` /
21
+ * `aria-describedby` / `aria-invalid` so the field is announced correctly.
22
+ *
23
+ * Auto-themed via `useBlocksStyles()`.
24
+ */
25
+ export declare const TextInput: import("react").ForwardRefExoticComponent<TextInputProps & import("react").RefAttributes<HTMLInputElement>>;
26
+ //# sourceMappingURL=TextInput.d.ts.map