@gsa-tts/graymatter-ui 0.5.1 → 0.6.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gsa-tts/graymatter-ui",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -61,8 +61,8 @@
61
61
  "typescript": "5.7.3",
62
62
  "vite": "^6.4.1",
63
63
  "vitest": "^3.0.7",
64
- "@gsa-tts/graymatter-eslint": "0.0.2",
65
64
  "@gsa-tts/graymatter-typescript-config": "0.0.2",
65
+ "@gsa-tts/graymatter-eslint": "0.0.2",
66
66
  "@gsa-tts/graymatter-vitest-config": "0.0.1"
67
67
  },
68
68
  "dependencies": {
@@ -10,6 +10,7 @@
10
10
  * - href?: string (optional - renders as <a> if provided)
11
11
  * - disabled?: boolean (default: false)
12
12
  * - type?: 'button' | 'submit' | 'reset' (default: 'button')
13
+ * - hasBoxShadow?: boolean (default: true) - Whether to show box shadow on square buttons
13
14
  *
14
15
  * Examples:
15
16
  * - Round primary button: <Button>Click me</Button>
@@ -17,6 +18,7 @@
17
18
  * - Small button (any shape): <Button size="small">Small Button</Button>
18
19
  * - Small square button: <Button variant="secondary" shape="square" size="small">Small Square</Button>
19
20
  * - Inverted square button: <Button theme="inverted" shape="square">Inverted Square</Button>
21
+ * - Button without shadow: <Button shape="square" hasBoxShadow={false}>No Shadow</Button>
20
22
  */
21
23
  interface Props {
22
24
  variant?: 'primary' | 'secondary' | 'unstyled';
@@ -27,6 +29,7 @@
27
29
  size?: 'small' | 'medium' | 'large';
28
30
  type?: 'button' | 'submit' | 'reset';
29
31
  onclick?: (event: MouseEvent) => void;
32
+ hasBoxShadow?: boolean;
30
33
  class?: string;
31
34
  children?: import('svelte').Snippet;
32
35
  [key: `data-${string}`]: string;
@@ -42,6 +45,7 @@
42
45
  onclick,
43
46
  size = 'medium',
44
47
  type = 'button',
48
+ hasBoxShadow = true,
45
49
  class: className = '',
46
50
  children,
47
51
  // eslint-disable-next-line svelte/valid-compile
@@ -63,6 +67,7 @@
63
67
  variant === 'secondary' ? 'usa-button--outline ai-button--outline' : '',
64
68
  variant === 'unstyled' ? 'ai-button--unstyled' : '',
65
69
  shape === 'square' ? 'ai-button--square' : '',
70
+ !hasBoxShadow ? 'ai-button--no-shadow' : '',
66
71
  ]
67
72
  .filter(Boolean)
68
73
  .join(' ');
@@ -387,5 +392,14 @@
387
392
  color: inherit;
388
393
  opacity: 0.75;
389
394
  }
395
+
396
+ /* No shadow modifier */
397
+ .ai-button--no-shadow {
398
+ box-shadow: none !important;
399
+ }
400
+
401
+ .ai-button--no-shadow:is(:hover, :focus) {
402
+ box-shadow: none !important;
403
+ }
390
404
  /*! purgecss end ignore */
391
405
  </style>
@@ -0,0 +1,374 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Modal Component
4
+ *
5
+ * A flexible modal component that can be used for dialogs, confirmations, and custom overlays.
6
+ *
7
+ * Props:
8
+ * - open: boolean - Controls whether the modal is visible
9
+ * - onClose?: () => void - Callback when modal should close (backdrop click or ESC key)
10
+ * - closeOnBackdropClick?: boolean - Whether clicking backdrop closes modal (default: true)
11
+ * - closeOnEscape?: boolean - Whether ESC key closes modal (default: true)
12
+ * - size?: 'small' | 'medium' | 'large' - Modal width (default: 'medium')
13
+ * - showCloseButton?: boolean - Whether to show X close button (default: true)
14
+ * - centered?: boolean - Center content text (default: true)
15
+ * - icon?: Snippet - Optional icon to display at top
16
+ * - title?: string - Optional title text
17
+ * - content?: string[] - Array of paragraph texts to display
18
+ * - buttons?: Array<ButtonConfig> - Optional buttons to display
19
+ * - children?: Snippet - Custom content (overrides title/content/buttons if provided)
20
+ * - class?: string - Additional CSS classes for the modal
21
+ *
22
+ * Example (Simple):
23
+ * ```svelte
24
+ * <Modal
25
+ * open={isOpen}
26
+ * title="Delete Item"
27
+ * content={["This action cannot be undone.", "Are you sure?"]}
28
+ * buttons={[
29
+ * { text: 'Delete', variant: 'primary', onclick: handleDelete },
30
+ * { text: 'Cancel', variant: 'secondary', onclick: () => isOpen = false }
31
+ * ]}
32
+ * />
33
+ * ```
34
+ *
35
+ * Example (Custom):
36
+ * ```svelte
37
+ * <Modal open={isOpen} onClose={() => isOpen = false}>
38
+ * <h2>Custom Content</h2>
39
+ * <p>Your custom HTML here</p>
40
+ * </Modal>
41
+ * ```
42
+ */
43
+ import { CloseIcon } from './icons/index.js';
44
+ import Button from './Button.svelte';
45
+
46
+ interface ButtonConfig {
47
+ text: string;
48
+ variant?: 'primary' | 'secondary' | 'unstyled';
49
+ onclick: (event: MouseEvent) => void;
50
+ class?: string;
51
+ disabled?: boolean;
52
+ }
53
+
54
+ interface Props {
55
+ open: boolean;
56
+ onClose?: () => void;
57
+ closeOnBackdropClick?: boolean;
58
+ closeOnEscape?: boolean;
59
+ size?: 'small' | 'medium' | 'large';
60
+ showCloseButton?: boolean;
61
+ centered?: boolean;
62
+ icon?: import('svelte').Snippet;
63
+ title?: string;
64
+ content?: string[];
65
+ buttons?: ButtonConfig[];
66
+ children?: import('svelte').Snippet;
67
+ class?: string;
68
+ }
69
+
70
+ let {
71
+ open = $bindable(),
72
+ onClose,
73
+ closeOnBackdropClick = true,
74
+ closeOnEscape = true,
75
+ size = 'medium',
76
+ showCloseButton = true,
77
+ centered = true,
78
+ icon,
79
+ title,
80
+ content,
81
+ buttons,
82
+ children,
83
+ class: className = '',
84
+ }: Props = $props();
85
+
86
+ function handleBackdropClick(event: MouseEvent) {
87
+ if (closeOnBackdropClick && event.target === event.currentTarget) {
88
+ open = false;
89
+ onClose?.();
90
+ }
91
+ }
92
+
93
+ function handleKeydown(event: KeyboardEvent) {
94
+ if (closeOnEscape && event.key === 'Escape') {
95
+ open = false;
96
+ onClose?.();
97
+ }
98
+ }
99
+
100
+ function handleCloseClick() {
101
+ open = false;
102
+ onClose?.();
103
+ }
104
+
105
+ // Add/remove body scroll lock when modal is open
106
+ $effect(() => {
107
+ if (open) {
108
+ document.body.style.overflow = 'hidden';
109
+ return () => {
110
+ document.body.style.overflow = '';
111
+ };
112
+ }
113
+ });
114
+ </script>
115
+
116
+ <svelte:window onkeydown={handleKeydown} />
117
+
118
+ {#if open}
119
+ <div
120
+ class="ai-modal-backdrop"
121
+ onclick={handleBackdropClick}
122
+ role="presentation"
123
+ >
124
+ <div
125
+ class="ai-modal ai-modal--{size} {className}"
126
+ class:ai-modal--centered={centered}
127
+ role="dialog"
128
+ aria-modal="true"
129
+ >
130
+ {#if showCloseButton}
131
+ <button
132
+ class="ai-modal__close"
133
+ onclick={handleCloseClick}
134
+ aria-label="Close modal"
135
+ type="button"
136
+ >
137
+ <CloseIcon />
138
+ </button>
139
+ {/if}
140
+ <div class="ai-modal__content">
141
+ {#if children}
142
+ {@render children()}
143
+ {:else}
144
+ <div class="ai-modal__structured-content">
145
+ {#if icon}
146
+ <div class="ai-modal__icon-container">
147
+ {@render icon()}
148
+ </div>
149
+ {/if}
150
+
151
+ {#if title}
152
+ <h2 class="ai-modal__title">
153
+ {title}
154
+ </h2>
155
+ {/if}
156
+
157
+ {#if content && content.length > 0}
158
+ <div class="ai-modal__text">
159
+ {#each content as paragraph}
160
+ <p class="ai-modal__paragraph">
161
+ {paragraph}
162
+ </p>
163
+ {/each}
164
+ </div>
165
+ {/if}
166
+
167
+ {#if buttons && buttons.length > 0}
168
+ <div class="ai-modal__actions">
169
+ {#each buttons as button}
170
+ <Button
171
+ variant={button.variant || 'primary'}
172
+ shape="square"
173
+ onclick={button.onclick}
174
+ disabled={button.disabled}
175
+ hasBoxShadow={false}
176
+ class="ai-modal__button {button.class || ''}"
177
+ >
178
+ {button.text}
179
+ </Button>
180
+ {/each}
181
+ </div>
182
+ {/if}
183
+ </div>
184
+ {/if}
185
+ </div>
186
+ </div>
187
+ </div>
188
+ {/if}
189
+
190
+ <style>
191
+ /*! purgecss start ignore */
192
+ :root,
193
+ :host {
194
+ --ai-modal-backdrop-bg: rgba(0, 0, 0, 0.5);
195
+ --ai-modal-bg: var(--ai-color-white);
196
+ --ai-modal-border-radius: 1.0625rem;
197
+ --ai-modal-border: 0.0625rem solid var(--ai-color-steel-200);
198
+ --ai-modal-padding: 2rem;
199
+ --ai-modal-gap: 1.5rem;
200
+ --ai-modal-content-width: 22.4375rem;
201
+ --ai-modal-width: 26.4375rem; /* 22.4375rem content + 2rem padding * 2 */
202
+ --ai-modal-max-width-small: 25rem;
203
+ --ai-modal-max-width-large: 50rem;
204
+ --ai-modal-shadow: 0 0.1875rem 1.25rem 0.0625rem rgba(50, 54, 61, 0.15);
205
+ --ai-modal-close-size: var(--ai-size-32, 2rem);
206
+ --ai-modal-close-top: var(--ai-size-16, 1rem);
207
+ --ai-modal-close-right: var(--ai-size-16, 1rem);
208
+
209
+ /* Typography tokens */
210
+ --ai-modal-title-size: var(--ai-size-20, 1.25rem);
211
+ --ai-modal-title-weight: var(--ai-font-weight-semibold, 600);
212
+ --ai-modal-text-size: 1rem;
213
+ --ai-modal-text-line-height: 1.3;
214
+ }
215
+
216
+ .ai-modal-backdrop {
217
+ position: fixed;
218
+ inset: 0;
219
+ background-color: var(--ai-modal-backdrop-bg);
220
+ display: flex;
221
+ align-items: center;
222
+ justify-content: center;
223
+ padding: var(--ai-size-16);
224
+ z-index: 9999;
225
+ animation: fadeIn 0.2s ease-out;
226
+ }
227
+
228
+ @keyframes fadeIn {
229
+ from {
230
+ opacity: 0;
231
+ }
232
+ to {
233
+ opacity: 1;
234
+ }
235
+ }
236
+
237
+ .ai-modal {
238
+ position: relative;
239
+ display: inline-flex;
240
+ padding: var(--ai-modal-padding);
241
+ flex-direction: column;
242
+ align-items: flex-start;
243
+ gap: var(--ai-modal-gap);
244
+ background-color: var(--ai-modal-bg);
245
+ border-radius: var(--ai-modal-border-radius);
246
+ border: var(--ai-modal-border);
247
+ box-shadow: var(--ai-modal-shadow);
248
+ max-height: calc(100vh - var(--ai-size-32, 2rem));
249
+ overflow-y: auto;
250
+ animation: slideUp 0.3s ease-out;
251
+ }
252
+
253
+ @keyframes slideUp {
254
+ from {
255
+ transform: translateY(var(--ai-size-32));
256
+ opacity: 0;
257
+ }
258
+ to {
259
+ transform: translateY(0);
260
+ opacity: 1;
261
+ }
262
+ }
263
+
264
+ .ai-modal--small {
265
+ max-width: var(--ai-modal-max-width-small);
266
+ width: 100%;
267
+ }
268
+
269
+ .ai-modal--medium {
270
+ width: var(--ai-modal-width);
271
+ }
272
+
273
+ .ai-modal--large {
274
+ max-width: var(--ai-modal-max-width-large);
275
+ width: 100%;
276
+ }
277
+
278
+ .ai-modal__close {
279
+ position: absolute;
280
+ top: var(--ai-modal-close-top);
281
+ right: var(--ai-modal-close-right);
282
+ width: var(--ai-modal-close-size);
283
+ height: var(--ai-modal-close-size);
284
+ padding: var(--ai-size-6);
285
+ background: transparent;
286
+ border: none;
287
+ cursor: pointer;
288
+ display: flex;
289
+ align-items: center;
290
+ justify-content: center;
291
+ border-radius: var(--ai-size-4);
292
+ transition: background-color 0.2s ease;
293
+ color: var(--ai-color-neutral-600);
294
+ }
295
+
296
+ .ai-modal__close:hover {
297
+ background-color: var(--ai-color-neutral-100);
298
+ color: var(--ai-color-black);
299
+ }
300
+
301
+ .ai-modal__close:focus {
302
+ outline: 0.125rem solid var(--ai-color-black);
303
+ outline-offset: 0.125rem;
304
+ }
305
+
306
+ .ai-modal__content {
307
+ width: 100%;
308
+ }
309
+
310
+ .ai-modal--centered .ai-modal__content {
311
+ text-align: center;
312
+ }
313
+
314
+ /* Structured content layout */
315
+ .ai-modal__structured-content {
316
+ display: flex;
317
+ flex-direction: column;
318
+ align-items: flex-start;
319
+ gap: var(--ai-modal-gap);
320
+ }
321
+
322
+ .ai-modal--centered .ai-modal__structured-content {
323
+ align-items: center;
324
+ text-align: center;
325
+ }
326
+
327
+ /* Icon container - just for layout, no styling */
328
+ .ai-modal__icon-container {
329
+ display: flex;
330
+ align-items: center;
331
+ justify-content: center;
332
+ flex-shrink: 0;
333
+ }
334
+
335
+ /* Title */
336
+ .ai-modal__title {
337
+ font-size: var(--ai-modal-title-size);
338
+ font-weight: var(--ai-modal-title-weight);
339
+ margin: 0;
340
+ color: var(--ai-color-black, #000);
341
+ line-height: 1.3;
342
+ }
343
+
344
+ /* Text content */
345
+ .ai-modal__text {
346
+ display: flex;
347
+ flex-direction: column;
348
+ gap: calc(var(--ai-modal-gap) / 2);
349
+ width: 100%;
350
+ }
351
+
352
+ .ai-modal__paragraph {
353
+ font-size: var(--ai-modal-text-size);
354
+ font-style: normal;
355
+ font-weight: 400;
356
+ line-height: var(--ai-modal-text-line-height);
357
+ margin: 0;
358
+ color: var(--ai-color-black);
359
+ }
360
+
361
+ /* Actions */
362
+ .ai-modal__actions {
363
+ display: flex;
364
+ flex-direction: column;
365
+ gap: var(--ai-size-12, 0.75rem);
366
+ width: 100%;
367
+ margin-top: calc(var(--ai-modal-gap) / 2);
368
+ }
369
+
370
+ .ai-modal__button {
371
+ width: 100%;
372
+ }
373
+ /*! purgecss end ignore */
374
+ </style>
@@ -14,6 +14,7 @@ export { default as ApiDocSubNavList } from './ApiDocSubNavList.svelte';
14
14
  export { default as ApiDocSubNavMenuItem } from './ApiDocSubNavMenuItem.svelte';
15
15
  export { default as PlatformFooter } from './PlatformFooter.svelte';
16
16
  export { default as PlatformFooterMenu } from './PlatformFooterMenu.svelte';
17
+ export { default as Modal } from './Modal.svelte';
17
18
 
18
19
  // Icon Components
19
20
  export * from './icons/index.js';
@@ -27,6 +27,13 @@ USAi API is organized around [REST](http://en.wikipedia.org/wiki/Representationa
27
27
  - [Llama 3.2 11B](https://www.llama.com/docs/model-cards-and-prompt-formats/llama3_2/)
28
28
  - [Llama 4 Maverick](https://www.llama.com/docs/model-cards-and-prompt-formats/llama4/)
29
29
 
30
+ **OpenAI models**
31
+ - [GPT 5.2](https://developers.openai.com/api/docs/models/gpt-5.2)
32
+ - [GPT 5.4](https://developers.openai.com/api/docs/models/gpt-5.4)
33
+
34
+ **xAI models**
35
+ - [Grok 4](https://docs.x.ai/developers/models/grok-4.20)
36
+
30
37
  The interface is modeled after the [OpenAI Chat Completion API](https://platform.openai.com/docs/guides/text?api-mode=responses). We will continue to add more models over time; we may also remove models if they are found to not meet our standards. You will be notified if a model is removed.
31
38
 
32
39
  Each model we draw on has unique capabilities. Models may also respond differently to the same API request. Below is a summary of each endpoint’s functionality:
@@ -58,7 +65,9 @@ Because the underlying platforms have different capabilities and interfaces, not
58
65
 
59
66
  #### Guardrail limitations
60
67
 
61
- We do not have guardrails beyond those provided by our model providers; this means that an API user has full control over what inputs and outputs are allowed when using the API. We expect API users to interact with the API ethically and deliberately, and add their own guardrails and system prompts as needed. We recommend implementing system prompts when using the API in situations where you do not know what inputs the model will receive.
68
+ For OpenAI and xAI models we have Azure's DefaultV2 content filtering enabled, which provides automated harm detection across four categories (Hate, Self-Harm, Sexual, Violence) plus advanced protections against prompt injection, and copyright violations. When the platform detects harmful content it will raise an HTTP 400 error in non-streaming contexts and return an error chunk when streaming.
69
+
70
+ For Google, Anthropic, and Meta models we do not have guardrails beyond those provided by our model providers; this means that an API user has full control over what inputs and outputs are allowed when using the API. We expect API users to interact with the API ethically and deliberately, and add their own guardrails and system prompts as needed. We recommend implementing system prompts when using the API in situations where you do not know what inputs the model will receive.
62
71
 
63
72
  System prompts should address your needs and concerns. Some of the system prompts in USAi Chat include:
64
73
  - You are a helpful assistant that works for a government agency.