figma-plugin-utilities 0.1.0 → 0.3.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,52 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.3.0] - 2026-05-06
11
+
12
+ WCAG 2.2 AA accessibility audit and remediation across all components.
13
+
14
+ ### Added
15
+ - `class` prop passthrough to **ListItem**, **LoadingState**, and **StatusBar** — consistent with other components
16
+ - `role="alert"` for error/warning and `role="status"` for info/success on **StatusBar** — messages are now announced by screen readers on insertion
17
+ - `"AAA-large"` case (4.5:1) to `meetsContrastLevel` in `lib/colors.js` — covers WCAG 1.4.6 large text at AAA level
18
+ - GitHub Actions publish workflow (`.github/workflows/publish.yml`) — triggers `npm publish` on GitHub release creation
19
+ - `aria-pressed={active}` to **ListItem** — communicates selection state to assistive technology
20
+ - Space key activation to **ListItem** — keyboard users can now toggle items with Space as well as Enter
21
+ - `ariaLabel="{title} options"` to the **ListItem** menu `IconButton` — gives the icon-only button an accessible name
22
+ - `role="status"` to **LoadingState** — loading message is announced as a polite live region
23
+ - `aria-hidden="true"` to the decorative icon in **EmptyState** — prevents redundant AT announcement
24
+ - `aria-disabled` attribute to **CheckboxCard** — reflects disabled state without removing from the accessibility tree
25
+ - Dev-mode `console.warn` to **FieldGroup** when `label` is provided but `labelFor` is empty
26
+
27
+ ### Changed
28
+ - **Header**: outer element changed from `<div>` to `<header>`; title changed from `<h2>` to `<h1>` (plugin UI runs in its own iframe, so the heading hierarchy starts fresh)
29
+ - **Footer**: outer element changed from `<div>` to `<footer>`
30
+ - **CheckboxCard**: removed `role="button"` and `tabindex` from the wrapper div — the native checkbox input is the sole interactive/focusable element; the wrapper remains clickable for mouse users via a delegating click handler
31
+ - **CheckboxCard**: added `user-select: none` to prevent text selection on double-click
32
+
33
+ ## [0.1.0] - 2026-04-17
34
+
35
+ Initial release as `figma-plugin-utilities`.
36
+
37
+ ### Added
38
+ - **PluginLayout** — main content wrapper with scrollable area
39
+ - **Header** — header bar with left/center/right slots and optional title
40
+ - **Footer** — footer bar with right, split, and full layout variants
41
+ - **StatusBar** — toast-style notifications with auto-dismiss for info/success types
42
+ - **EmptyState** — empty/error state display with optional icon and action buttons
43
+ - **ListItem** — selectable list item with metadata slot and context menu
44
+ - **LoadingState** — centered loading indicator
45
+ - **FieldGroup** — label + input wrapper for form fields
46
+ - **CheckboxCard** — large-target checkbox with card styling
47
+ - `lib/messages.js` — `sendToPlugin` and `createMessageHandler`
48
+ - `lib/colors.js` — `rgbToHex`, `hexToRgb`, `getLuminance`, `getContrastRatio`, `meetsContrastLevel`
49
+ - `lib/validation.js` — `validateUrl`, `validateJsonString`, `validateEmail`, `validateNumber`, `sanitizeName`, `sanitizeInput`, `isEmpty`
50
+ - `lib/errorHandling.js` — `safeAsync`, `parseJsonSafe`, `notifyError`, `notifySuccess`, `notifyWarning`, and more
51
+ - `lib/resize.js` — `resizeToFit`, `autoResize`, `setDefaultWidth`, `getContentHeight`
52
+ - `lib/figma-helpers.ts` — `sendToUI`, `showError`, `showSuccess`, `getCollections`, `getVariables`, `getSelection`, `focusNodes`, `loadFont`, `saveToStorage`, `loadFromStorage`, `handleResize`
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marius Roosendaal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
- # figma-plugin-utils
1
+ # figma-plugin-utilities
2
2
 
3
3
  Shared Svelte components and utilities for Figma plugins.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install figma-plugin-utils
8
+ npm install figma-plugin-utilities
9
9
  ```
10
10
 
11
11
  ## Usage
@@ -23,25 +23,46 @@ import {
23
23
  ListItem,
24
24
  LoadingState,
25
25
  FieldGroup,
26
- // Utilities
26
+ CheckboxCard,
27
+ // Messages
27
28
  sendToPlugin,
28
29
  createMessageHandler,
30
+ // Colors
29
31
  rgbToHex,
30
32
  hexToRgb,
33
+ getLuminance,
34
+ getContrastRatio,
35
+ meetsContrastLevel,
36
+ // Validation
31
37
  validateUrl,
32
38
  validateJsonString,
39
+ validateEmail,
40
+ validateNumber,
33
41
  sanitizeName,
34
- } from "figma-plugin-utils";
42
+ sanitizeInput,
43
+ isEmpty,
44
+ // Error handling
45
+ safeAsync,
46
+ parseJsonSafe,
47
+ notifyError,
48
+ notifySuccess,
49
+ notifyWarning,
50
+ // Resize
51
+ setDefaultWidth,
52
+ getContentHeight,
53
+ resizeToFit,
54
+ autoResize,
55
+ } from "figma-plugin-utilities";
35
56
  ```
36
57
 
37
58
  ### Import Specific Modules
38
59
 
39
60
  ```javascript
40
61
  // Components only
41
- import { PluginLayout, Header, Footer } from "figma-plugin-utils/components";
62
+ import { PluginLayout, Header, Footer } from "figma-plugin-utilities/components";
42
63
 
43
64
  // Utilities only
44
- import { sendToPlugin, createMessageHandler } from "figma-plugin-utils/lib";
65
+ import { sendToPlugin, createMessageHandler } from "figma-plugin-utilities/lib";
45
66
  ```
46
67
 
47
68
  ## Components
@@ -56,6 +77,7 @@ import { sendToPlugin, createMessageHandler } from "figma-plugin-utils/lib";
56
77
  | `ListItem` | Selectable list items with metadata slot and action menu |
57
78
  | `LoadingState` | Centered loading indicator with custom message |
58
79
  | `FieldGroup` | Label + input wrapper for form fields |
80
+ | `CheckboxCard` | Large checkbox with card styling and better touch targets |
59
81
 
60
82
  ### Header
61
83
 
@@ -140,6 +162,37 @@ Types: `info`, `success`, `error`, `warning`. Auto-dismisses after 4s for `info`
140
162
  </ListItem>
141
163
  ```
142
164
 
165
+ ### CheckboxCard
166
+
167
+ Large checkbox with card-style background and better touch targets.
168
+
169
+ ```svelte
170
+ <!-- Basic usage -->
171
+ <CheckboxCard
172
+ checked={isSelected}
173
+ on:change={handleToggle}
174
+ >
175
+ Small
176
+ </CheckboxCard>
177
+
178
+ <!-- With secondary text -->
179
+ <CheckboxCard
180
+ checked={isSelected}
181
+ on:change={handleToggle}
182
+ >
183
+ Small
184
+ <svelte:fragment slot="secondary">400px</svelte:fragment>
185
+ </CheckboxCard>
186
+
187
+ <!-- Disabled state -->
188
+ <CheckboxCard
189
+ checked={true}
190
+ disabled={true}
191
+ >
192
+ Large
193
+ </CheckboxCard>
194
+ ```
195
+
143
196
  ## Utilities
144
197
 
145
198
  ### Messages (`lib/messages.js`)
@@ -162,7 +215,8 @@ window.onmessage = createMessageHandler({
162
215
  const rgb = hexToRgb("#FF0000"); // { r: 1, g: 0, b: 0 }
163
216
  const hex = rgbToHex({ r: 1, g: 0, b: 0 }); // "#FF0000"
164
217
 
165
- // Calculate contrast
218
+ // Contrast utilities
219
+ const luminance = getLuminance({ r: 1, g: 0, b: 0 });
166
220
  const ratio = getContrastRatio(color1, color2);
167
221
  const passes = meetsContrastLevel(ratio, "AA"); // true/false
168
222
  ```
@@ -176,7 +230,12 @@ const urlResult = validateUrl("https://example.com");
176
230
  const jsonResult = validateJsonString('{"key": "value"}');
177
231
  // { valid: true, parsed: {...} } or { valid: false, error: "..." }
178
232
 
233
+ validateEmail("user@example.com"); // { valid: true }
234
+ validateNumber("42", { min: 0, max: 100 }); // { valid: true, value: 42 }
235
+
179
236
  const clean = sanitizeName("My Plugin!!!"); // "My Plugin"
237
+ sanitizeInput("<script>alert(1)</script>"); // escaped string
238
+ isEmpty(""); // true
180
239
  ```
181
240
 
182
241
  ### Error Handling (`lib/errorHandling.js`)
@@ -196,20 +255,71 @@ if (result.ok) {
196
255
  // Parse JSON safely
197
256
  const parsed = parseJsonSafe(jsonString);
198
257
  // { ok: true, value: {...} } or { ok: false, error: "..." }
258
+
259
+ // Figma notifications
260
+ notifySuccess("Done!");
261
+ notifyError("Something went wrong");
262
+ notifyWarning("Check your input");
263
+ ```
264
+
265
+ ### Resize (`lib/resize.js`)
266
+
267
+ Utilities for dynamically resizing the plugin window to fit its content.
268
+
269
+ ```javascript
270
+ // One-time resize to fit content
271
+ resizeToFit({ width: 300, minHeight: 100, maxHeight: 600 });
272
+
273
+ // Watch for content changes and auto-resize
274
+ const cleanup = autoResize({
275
+ container: myContainerEl, // bind:this on a naturally-flowing wrapper
276
+ width: 300,
277
+ minHeight: 100,
278
+ maxHeight: 600,
279
+ });
280
+
281
+ // Call cleanup when the component is destroyed
282
+ onDestroy(cleanup);
283
+
284
+ // Set default width used across all resize calls
285
+ setDefaultWidth(320);
199
286
  ```
200
287
 
288
+ > **Note:** The `container` element passed to `autoResize` must **not** have `height: 100%` or a fixed height — it should flow naturally with its content so `scrollHeight` can be measured accurately.
289
+
201
290
  ### Figma Helpers (`lib/figma-helpers.ts`)
202
291
 
203
292
  For use in `code.ts`:
204
293
 
205
294
  ```typescript
206
- import { sendToUI, showError, focusNodes, loadFont } from "figma-plugin-utils/lib/figma-helpers";
295
+ import {
296
+ sendToUI,
297
+ showError,
298
+ showSuccess,
299
+ getCollections,
300
+ getVariables,
301
+ getSelection,
302
+ focusNodes,
303
+ loadFont,
304
+ saveToStorage,
305
+ loadFromStorage,
306
+ handleResize,
307
+ } from "figma-plugin-utilities/lib/figma-helpers";
207
308
 
208
309
  // Send message to UI
209
310
  sendToUI("success", { message: "Done!" });
210
311
 
211
- // Show notification
312
+ // Show notifications
212
313
  showError("Something went wrong");
314
+ showSuccess("Created!");
315
+
316
+ // Variables
317
+ const collections = await getCollections();
318
+ const colorVars = await getVariables("COLOR");
319
+
320
+ // Selection
321
+ const selected = getSelection(); // all selected nodes
322
+ const frames = getSelection("FRAME"); // filtered by type
213
323
 
214
324
  // Focus viewport on nodes
215
325
  focusNodes(figma.currentPage.selection);
@@ -220,4 +330,7 @@ await loadFont("Inter", "Regular");
220
330
  // Client storage
221
331
  await saveToStorage("settings", { theme: "dark" });
222
332
  const settings = await loadFromStorage("settings", { theme: "light" });
333
+
334
+ // Handle resize message from UI (call in your message handler)
335
+ if (msg.type === "resize") handleResize(msg);
223
336
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "figma-plugin-utilities",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Shared Svelte components and utilities for Figma plugins",
5
5
  "type": "module",
6
6
  "svelte": "./src/index.js",
@@ -21,13 +21,21 @@
21
21
  "import": "./src/lib/index.js",
22
22
  "default": "./src/lib/index.js"
23
23
  },
24
+ "./lib/*": {
25
+ "import": "./src/lib/*.js"
26
+ },
24
27
  "./lib/figma-helpers": {
25
28
  "types": "./src/lib/figma-helpers.ts",
26
29
  "import": "./src/lib/figma-helpers.ts",
27
30
  "default": "./src/lib/figma-helpers.ts"
28
31
  }
29
32
  },
30
- "files": ["src"],
33
+ "files": [
34
+ "src",
35
+ "README.md",
36
+ "LICENSE",
37
+ "CHANGELOG.md"
38
+ ],
31
39
  "repository": {
32
40
  "type": "git",
33
41
  "url": "git+https://github.com/mariusroosendaal/figma-plugin-utilities.git"
@@ -47,7 +55,7 @@
47
55
  "author": "Marius Roosendaal",
48
56
  "license": "MIT",
49
57
  "dependencies": {
50
- "figma-ui3-kit-svelte": "^0.4.0",
58
+ "figma-ui3-kit-svelte": "^0.5.0",
51
59
  "svelte": "^4.2.20"
52
60
  }
53
61
  }
@@ -0,0 +1,108 @@
1
+ <script>
2
+ import { createEventDispatcher } from "svelte";
3
+ import { Checkbox } from "figma-ui3-kit-svelte";
4
+
5
+ /**
6
+ * Large checkbox card component with better touch targets
7
+ * Wraps the standard checkbox in a card-like layout
8
+ *
9
+ * @example
10
+ * <CheckboxCard
11
+ * checked={isSelected}
12
+ * on:change={handleToggle}
13
+ * >
14
+ * Small
15
+ * </CheckboxCard>
16
+ *
17
+ * @example with secondary text
18
+ * <CheckboxCard
19
+ * checked={isSelected}
20
+ * on:change={handleToggle}
21
+ * >
22
+ * Small
23
+ * <svelte:fragment slot="secondary">400px</svelte:fragment>
24
+ * </CheckboxCard>
25
+ */
26
+
27
+ const dispatch = createEventDispatcher();
28
+
29
+ /** Whether checkbox is checked */
30
+ export let checked = false;
31
+
32
+ /** Whether checkbox is disabled */
33
+ export let disabled = false;
34
+
35
+ let cardEl;
36
+
37
+ function handleChange(e) {
38
+ if (disabled) return;
39
+ checked = e.target.checked;
40
+ dispatch("change", { checked });
41
+ }
42
+
43
+ function handleCardClick(e) {
44
+ if (disabled) return;
45
+ // Clicks inside the Checkbox component (label/input) are handled natively
46
+ if (e.target.closest(".checkbox-container")) return;
47
+ cardEl?.querySelector('input[type="checkbox"]')?.click();
48
+ }
49
+ </script>
50
+
51
+ <!-- svelte-ignore a11y-click-events-have-key-events a11y-no-static-element-interactions -->
52
+ <!-- Keyboard users interact with the native checkbox input inside; this div is a mouse-only larger click target -->
53
+ <div
54
+ class="checkbox-card"
55
+ class:disabled
56
+ aria-disabled={disabled || undefined}
57
+ bind:this={cardEl}
58
+ on:click={handleCardClick}
59
+ >
60
+ <Checkbox {checked} {disabled} on:change={handleChange}>
61
+ <slot />
62
+ </Checkbox>
63
+ {#if $$slots.secondary}
64
+ <div class="checkbox-card__secondary">
65
+ <slot name="secondary" />
66
+ </div>
67
+ {/if}
68
+ </div>
69
+
70
+ <style>
71
+ .checkbox-card {
72
+ display: flex;
73
+ align-items: center;
74
+ justify-content: space-between;
75
+ padding: 0 var(--size-xxxsmall);
76
+ background: var(--figma-color-bg-secondary);
77
+ border: 1px solid transparent;
78
+ border-radius: var(--border-radius-medium);
79
+ cursor: pointer;
80
+ min-height: 24px;
81
+ user-select: none;
82
+ }
83
+
84
+ .checkbox-card:hover {
85
+ border-color: var(--figma-color-border);
86
+ background: var(--figma-color-bg-secondary);
87
+ }
88
+
89
+ .checkbox-card.disabled {
90
+ opacity: 0.4;
91
+ cursor: not-allowed;
92
+ }
93
+
94
+ /* Custom checkbox styling for card variant */
95
+ .checkbox-card :global(.checkbox-box) {
96
+ background-color: var(--figma-color-bg);
97
+ }
98
+
99
+ .checkbox-card__secondary {
100
+ font-size: var(--body-small-font-size);
101
+ font-weight: var(--body-small-font-weight);
102
+ letter-spacing: var(--body-small-letter-spacing);
103
+ line-height: var(--body-small-line-height);
104
+ color: var(--figma-color-text-secondary);
105
+ margin-left: auto;
106
+ padding-left: var(--size-xsmall);
107
+ }
108
+ </style>
@@ -30,18 +30,20 @@
30
30
  /** Whether to center vertically */
31
31
  export let centered = true;
32
32
 
33
+ let className = '';
34
+ export { className as class };
33
35
  // Normalize actions
34
36
  $: normalizedActions = actions ? actions : action ? [action] : null;
35
37
  </script>
36
38
 
37
39
  <div
38
- class="empty-state"
40
+ class="empty-state {className}"
39
41
  class:centered
40
42
  class:small={size === "small"}
41
43
  class:large={size === "large"}
42
44
  >
43
45
  {#if icon}
44
- <div class="empty-state__icon">
46
+ <div class="empty-state__icon" aria-hidden="true">
45
47
  {#if typeof icon === "string"}
46
48
  <Icon iconName={icon} />
47
49
  {:else}
@@ -16,11 +16,18 @@
16
16
 
17
17
  /** For attribute for the label (optional) */
18
18
  export let labelFor = "";
19
+
20
+ /** Size of the label (optional) */
21
+ export let size = undefined;
22
+
23
+ $: if (typeof window !== "undefined" && label && !labelFor) {
24
+ console.warn("[FieldGroup] A label is rendered but no labelFor is set. Associate the label with its control using the labelFor prop.");
25
+ }
19
26
  </script>
20
27
 
21
- <div class="field-group">
28
+ <div class="field-group" class:small={size === "small"}>
22
29
  {#if label}
23
- <Label for={labelFor}>{label}</Label>
30
+ <Label for={labelFor} {size}>{label}</Label>
24
31
  {/if}
25
32
  <slot />
26
33
  </div>
@@ -31,4 +38,8 @@
31
38
  flex-direction: column;
32
39
  gap: var(--size-xxsmall);
33
40
  }
41
+
42
+ .field-group.small {
43
+ gap: var(--size-xxxsmall);
44
+ }
34
45
  </style>
@@ -31,7 +31,7 @@
31
31
  export let className = "";
32
32
  </script>
33
33
 
34
- <div class="footer footer--{variant} {className}">
34
+ <footer class="footer footer--{variant} {className}">
35
35
  {#if variant === "right"}
36
36
  <div class="footer__right">
37
37
  <slot />
@@ -46,7 +46,7 @@
46
46
  {:else if variant === "full"}
47
47
  <slot />
48
48
  {/if}
49
- </div>
49
+ </footer>
50
50
 
51
51
  <style>
52
52
  .footer {
@@ -11,7 +11,7 @@
11
11
  export let noBorder = false;
12
12
  </script>
13
13
 
14
- <div
14
+ <header
15
15
  class="header {className}"
16
16
  class:has-left-content={$$slots.left}
17
17
  class:no-border={noBorder}
@@ -19,7 +19,7 @@
19
19
  <div class="header__left">
20
20
  <slot name="left" />
21
21
  {#if title}
22
- <h2 class="header__title">{title}</h2>
22
+ <h1 class="header__title">{title}</h1>
23
23
  {/if}
24
24
  </div>
25
25
  <div class="header__center">
@@ -28,7 +28,7 @@
28
28
  <div class="header__right">
29
29
  <slot name="right" />
30
30
  </div>
31
- </div>
31
+ </header>
32
32
 
33
33
  <style>
34
34
  .header {
@@ -44,6 +44,9 @@
44
44
  /** Whether to show badge slot */
45
45
  export let hasBadge = false;
46
46
 
47
+ let className = "";
48
+ export { className as class };
49
+
47
50
  function handleClick() {
48
51
  dispatch("click", { id });
49
52
  }
@@ -65,14 +68,15 @@
65
68
  }
66
69
  </script>
67
70
 
68
- <div class="list-item-wrapper">
71
+ <div class="list-item-wrapper {className}">
69
72
  <div
70
73
  class="list-item"
71
74
  class:active
72
75
  on:click={handleClick}
73
- on:keydown={(e) => e.key === "Enter" && handleClick()}
76
+ on:keydown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); handleClick(); } }}
74
77
  role="button"
75
78
  tabindex="0"
79
+ aria-pressed={active}
76
80
  >
77
81
  <div class="list-item__content">
78
82
  <div class="list-item__title">{title}</div>
@@ -92,6 +96,7 @@
92
96
  {#if menuItems.length > 0}
93
97
  <IconButton
94
98
  iconName={IconMore}
99
+ ariaLabel="{title} options"
95
100
  bind:element={menuButtonElement}
96
101
  on:click={handleMenuToggle}
97
102
  />
@@ -15,9 +15,12 @@
15
15
 
16
16
  /** Message to display */
17
17
  export let message = "Loading...";
18
+
19
+ let className = "";
20
+ export { className as class };
18
21
  </script>
19
22
 
20
- <div class="loading-state">
23
+ <div class="loading-state {className}" role="status">
21
24
  <Text variant="body-medium" color="--figma-color-text-secondary">{message}</Text>
22
25
  </div>
23
26
 
@@ -6,9 +6,7 @@
6
6
  * @example
7
7
  * <PluginLayout>
8
8
  * <p>Main content here</p>
9
- * <svelte:fragment slot="footer">
10
- * <Button variant="primary">Create item</Button>
11
- * </svelte:fragment>
9
+ * <Button variant="primary">Create item</Button>
12
10
  * </PluginLayout>
13
11
  */
14
12
 
@@ -40,4 +38,8 @@
40
38
  overflow-y: auto;
41
39
  padding: var(--size-xsmall);
42
40
  }
41
+
42
+ .plugin-footer {
43
+ flex-shrink: 0;
44
+ }
43
45
  </style>
@@ -14,6 +14,9 @@
14
14
  /** Status type: 'info', 'success', 'error', 'warning' */
15
15
  export let type = "info";
16
16
 
17
+ let className = "";
18
+ export { className as class };
19
+
17
20
  let visible = false;
18
21
  let timeoutId;
19
22
 
@@ -53,14 +56,16 @@
53
56
 
54
57
  {#if visible && message}
55
58
  <div
56
- class="status-bar"
59
+ class="status-bar {className}"
57
60
  class:status-bar--error={type === "error"}
58
61
  class:status-bar--success={type === "success"}
59
62
  class:status-bar--warning={type === "warning"}
63
+ role={type === "error" || type === "warning" ? "alert" : "status"}
60
64
  >
61
65
  <span>{message}</span>
62
66
  <IconButton
63
67
  iconName={IconClose}
68
+ ariaLabel="Dismiss"
64
69
  on:click={handleClose}
65
70
  iconColor={computedIconColor}
66
71
  />
@@ -1,4 +1,5 @@
1
1
  // Svelte components
2
+ export { default as CheckboxCard } from "./CheckboxCard.svelte";
2
3
  export { default as EmptyState } from "./EmptyState.svelte";
3
4
  export { default as FieldGroup } from "./FieldGroup.svelte";
4
5
  export { default as Footer } from "./Footer.svelte";
package/src/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // Re-export all components
2
2
  export {
3
+ CheckboxCard,
3
4
  EmptyState,
4
5
  FieldGroup,
5
6
  Footer,
@@ -37,6 +38,9 @@ export {
37
38
  withErrorHandling,
38
39
  safeAsync,
39
40
  parseJsonSafe,
41
+ notifyError,
42
+ notifySuccess,
43
+ notifyWarning,
40
44
  // Resize
41
45
  setDefaultWidth,
42
46
  getContentHeight,
package/src/lib/colors.js CHANGED
@@ -60,13 +60,15 @@ export function getContrastRatio(color1, color2) {
60
60
  /**
61
61
  * Check if contrast ratio meets WCAG level
62
62
  * @param {number} ratio - Contrast ratio
63
- * @param {"AA" | "AAA" | "AA-large"} level - WCAG level to check
63
+ * @param {"AA" | "AAA" | "AA-large" | "AAA-large"} level - WCAG level to check
64
64
  * @returns {boolean} Whether the ratio meets the level
65
65
  */
66
66
  export function meetsContrastLevel(ratio, level) {
67
67
  switch (level) {
68
68
  case "AAA":
69
69
  return ratio >= 7;
70
+ case "AAA-large":
71
+ return ratio >= 4.5;
70
72
  case "AA":
71
73
  return ratio >= 4.5;
72
74
  case "AA-large":
package/src/lib/resize.js CHANGED
@@ -2,12 +2,12 @@
2
2
  * Auto-resize utilities for Figma plugin windows
3
3
  *
4
4
  * Usage in UI:
5
- * import { resizeToFit, autoResize } from "figma-plugin-utils";
5
+ * import { resizeToFit, autoResize } from "figma-plugin-utilities";
6
6
  * resizeToFit(); // One-time resize
7
7
  * autoResize(); // Watch for changes and auto-resize
8
8
  *
9
9
  * Usage in code.ts:
10
- * import { handleResize } from "figma-plugin-utils/lib/figma-helpers";
10
+ * import { handleResize } from "figma-plugin-utilities/lib/figma-helpers";
11
11
  * // In your message handler:
12
12
  * if (msg.type === "resize") handleResize(msg);
13
13
  */