@aiaiai-pt/design-system 0.1.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 (52) hide show
  1. package/components/Alert.svelte +100 -0
  2. package/components/Badge.svelte +108 -0
  3. package/components/BottomNav.svelte +37 -0
  4. package/components/BottomNavItem.svelte +121 -0
  5. package/components/Button.svelte +269 -0
  6. package/components/Card.svelte +108 -0
  7. package/components/Checkbox.svelte +138 -0
  8. package/components/CodeBlock.svelte +187 -0
  9. package/components/CodeEditor.svelte +221 -0
  10. package/components/CollapsibleSection.svelte +160 -0
  11. package/components/Combobox.svelte +396 -0
  12. package/components/EmptyState.svelte +148 -0
  13. package/components/FileUpload.svelte +280 -0
  14. package/components/FileUploadItem.svelte +222 -0
  15. package/components/Input.svelte +222 -0
  16. package/components/KeyValue.svelte +79 -0
  17. package/components/Label.svelte +49 -0
  18. package/components/List.svelte +70 -0
  19. package/components/ListItem.svelte +125 -0
  20. package/components/Menu.svelte +161 -0
  21. package/components/MenuItem.svelte +120 -0
  22. package/components/MenuSeparator.svelte +34 -0
  23. package/components/Modal.svelte +260 -0
  24. package/components/OptionGrid.svelte +195 -0
  25. package/components/Panel.svelte +256 -0
  26. package/components/Popover.svelte +194 -0
  27. package/components/Progress.svelte +78 -0
  28. package/components/Select.svelte +182 -0
  29. package/components/Separator.svelte +47 -0
  30. package/components/Sidebar.svelte +106 -0
  31. package/components/SidebarItem.svelte +154 -0
  32. package/components/SidebarSection.svelte +43 -0
  33. package/components/Skeleton.svelte +79 -0
  34. package/components/Status.svelte +104 -0
  35. package/components/Stepper.svelte +142 -0
  36. package/components/Tab.svelte +94 -0
  37. package/components/TabList.svelte +36 -0
  38. package/components/TabPanel.svelte +45 -0
  39. package/components/Tabs.svelte +46 -0
  40. package/components/Tag.svelte +96 -0
  41. package/components/Textarea.svelte +143 -0
  42. package/components/Toast.svelte +114 -0
  43. package/components/Toggle.svelte +132 -0
  44. package/components/index.js +70 -0
  45. package/package.json +45 -0
  46. package/tokens/base.css +175 -0
  47. package/tokens/components.css +530 -0
  48. package/tokens/semantic.css +211 -0
  49. package/tokens/themes/aiaiai.css +53 -0
  50. package/tokens/themes/bespoke-example.css +148 -0
  51. package/tokens/themes/branded-example.css +55 -0
  52. package/tokens/utilities.css +1865 -0
@@ -0,0 +1,100 @@
1
+ <!--
2
+ @component Alert
3
+
4
+ Inline callout for contextual messages.
5
+ Unlike Toast (floating, transient), Alert is in-flow and persistent.
6
+ Consumes --alert-* tokens from components.css.
7
+
8
+ @example Info
9
+ <Alert variant="info">
10
+ <strong>Note:</strong> This pipeline requires a source connection.
11
+ </Alert>
12
+
13
+ @example Error with icon
14
+ <Alert variant="error">
15
+ {#snippet icon()}<svg>...</svg>{/snippet}
16
+ <strong>Build failed.</strong> Check the CI logs for details.
17
+ </Alert>
18
+ -->
19
+ <script>
20
+ /**
21
+ * @typedef {'info' | 'success' | 'warning' | 'error'} Variant
22
+ */
23
+
24
+ let {
25
+ /** @type {Variant} */
26
+ variant = 'info',
27
+ /** @type {string} */
28
+ class: className = '',
29
+ /** @type {import('svelte').Snippet | undefined} */
30
+ icon = undefined,
31
+ /** @type {import('svelte').Snippet | undefined} */
32
+ children = undefined,
33
+ ...rest
34
+ } = $props();
35
+
36
+ /** @type {Record<string, string>} */
37
+ const accentColors = {
38
+ info: 'var(--color-info)',
39
+ success: 'var(--color-success)',
40
+ warning: 'var(--color-warning)',
41
+ error: 'var(--color-destructive)',
42
+ };
43
+ </script>
44
+
45
+ <div
46
+ class="alert {className}"
47
+ role={variant === 'error' ? 'alert' : 'status'}
48
+ {...rest}
49
+ >
50
+ <div class="alert-accent" style:background={accentColors[variant]}></div>
51
+ <div class="alert-content">
52
+ {#if icon}
53
+ <span class="alert-icon" aria-hidden="true">{@render icon()}</span>
54
+ {/if}
55
+ <div class="alert-body">
56
+ {#if children}{@render children()}{/if}
57
+ </div>
58
+ </div>
59
+ </div>
60
+
61
+ <style>
62
+ .alert {
63
+ display: flex;
64
+ overflow: hidden;
65
+ border-radius: var(--alert-radius);
66
+ border: var(--alert-border);
67
+ background: var(--alert-bg);
68
+ }
69
+
70
+ .alert-accent {
71
+ width: var(--accent-stripe-width);
72
+ flex-shrink: 0;
73
+ }
74
+
75
+ .alert-content {
76
+ padding: var(--alert-padding);
77
+ display: flex;
78
+ gap: var(--space-sm);
79
+ flex: 1;
80
+ }
81
+
82
+ .alert-icon {
83
+ display: flex;
84
+ flex-shrink: 0;
85
+ width: 16px;
86
+ height: 16px;
87
+ margin-top: 2px;
88
+ }
89
+
90
+ .alert-icon :global(svg) {
91
+ width: 100%;
92
+ height: 100%;
93
+ }
94
+
95
+ .alert-body {
96
+ font-family: var(--alert-font);
97
+ font-size: var(--alert-font-size);
98
+ flex: 1;
99
+ }
100
+ </style>
@@ -0,0 +1,108 @@
1
+ <!--
2
+ @component Badge
3
+
4
+ Semantic status label. Pill-shaped, mono font, color-coded.
5
+ Consumes --badge-* tokens from components.css.
6
+
7
+ @example
8
+ <Badge variant="success">PASSED</Badge>
9
+
10
+ @example With dot
11
+ <Badge variant="info" dot>SYNCING</Badge>
12
+ -->
13
+ <script>
14
+ /**
15
+ * @typedef {'neutral' | 'info' | 'success' | 'warning' | 'error' | 'outline'} Variant
16
+ */
17
+
18
+ let {
19
+ /** @type {Variant} */
20
+ variant = 'neutral',
21
+ /** @type {boolean} */
22
+ dot = false,
23
+ /** @type {string} */
24
+ class: className = '',
25
+ /** @type {import('svelte').Snippet | undefined} */
26
+ icon = undefined,
27
+ /** @type {import('svelte').Snippet | undefined} */
28
+ children = undefined,
29
+ ...rest
30
+ } = $props();
31
+ </script>
32
+
33
+ <span class="badge badge-{variant} {className}" {...rest}>
34
+ {#if dot}
35
+ <span class="badge-dot" aria-hidden="true"></span>
36
+ {/if}
37
+ {#if icon}
38
+ <span class="badge-icon-wrap">{@render icon()}</span>
39
+ {/if}
40
+ {#if children}
41
+ {@render children()}
42
+ {/if}
43
+ </span>
44
+
45
+ <style>
46
+ .badge {
47
+ display: inline-flex;
48
+ align-items: center;
49
+ gap: var(--space-xs);
50
+ font-family: var(--badge-font);
51
+ font-size: var(--badge-size);
52
+ letter-spacing: var(--badge-tracking);
53
+ border-radius: var(--badge-radius);
54
+ padding: var(--badge-padding-y) var(--badge-padding-x);
55
+ white-space: nowrap;
56
+ }
57
+
58
+ .badge-neutral {
59
+ background: var(--badge-neutral-bg);
60
+ color: var(--badge-neutral-text);
61
+ }
62
+
63
+ .badge-info {
64
+ background: var(--color-info-subtle);
65
+ color: var(--color-info);
66
+ }
67
+
68
+ .badge-success {
69
+ background: var(--color-success-subtle);
70
+ color: var(--color-success);
71
+ }
72
+
73
+ .badge-warning {
74
+ background: var(--color-warning-subtle);
75
+ color: var(--color-warning);
76
+ }
77
+
78
+ .badge-error {
79
+ background: var(--color-destructive-subtle);
80
+ color: var(--color-destructive);
81
+ }
82
+
83
+ .badge-outline {
84
+ background: transparent;
85
+ color: var(--color-text);
86
+ border: var(--border-width) solid var(--color-border);
87
+ }
88
+
89
+ .badge-dot {
90
+ width: 6px;
91
+ height: 6px;
92
+ border-radius: var(--radius-circle);
93
+ background: currentColor;
94
+ flex-shrink: 0;
95
+ }
96
+
97
+ .badge-icon-wrap {
98
+ display: flex;
99
+ width: 12px;
100
+ height: 12px;
101
+ flex-shrink: 0;
102
+ }
103
+
104
+ .badge-icon-wrap :global(svg) {
105
+ width: 100%;
106
+ height: 100%;
107
+ }
108
+ </style>
@@ -0,0 +1,37 @@
1
+ <!--
2
+ @component BottomNav
3
+
4
+ Mobile bottom navigation bar.
5
+ Consumes --nav-bottom-* tokens from components.css.
6
+
7
+ @example
8
+ <BottomNav>
9
+ <BottomNavItem active label="Home">
10
+ {#snippet icon()}<svg>...</svg>{/snippet}
11
+ </BottomNavItem>
12
+ </BottomNav>
13
+ -->
14
+ <script>
15
+ let {
16
+ /** @type {string} */
17
+ class: className = '',
18
+ /** @type {import('svelte').Snippet | undefined} */
19
+ children = undefined,
20
+ ...rest
21
+ } = $props();
22
+ </script>
23
+
24
+ <nav class="bottom-nav {className}" {...rest}>
25
+ {#if children}{@render children()}{/if}
26
+ </nav>
27
+
28
+ <style>
29
+ .bottom-nav {
30
+ height: var(--nav-bottom-height);
31
+ background: var(--nav-bottom-bg);
32
+ border-top: var(--nav-bottom-border-top);
33
+ display: flex;
34
+ align-items: center;
35
+ justify-content: space-around;
36
+ }
37
+ </style>
@@ -0,0 +1,121 @@
1
+ <!--
2
+ @component BottomNavItem
3
+
4
+ Individual item in a BottomNav.
5
+ Consumes --nav-bottom-* tokens from components.css.
6
+
7
+ @example
8
+ <BottomNavItem active label="Home" onclick={() => goto('/')}>
9
+ {#snippet icon()}<svg>...</svg>{/snippet}
10
+ </BottomNavItem>
11
+
12
+ @example With badge dot
13
+ <BottomNavItem label="Activity" badge>
14
+ {#snippet icon()}<svg>...</svg>{/snippet}
15
+ </BottomNavItem>
16
+ -->
17
+ <script>
18
+ let {
19
+ /** @type {boolean} */
20
+ active = false,
21
+ /** @type {boolean} */
22
+ disabled = false,
23
+ /** @type {string | undefined} */
24
+ href = undefined,
25
+ /** @type {string} */
26
+ label,
27
+ /** @type {boolean} */
28
+ badge = false,
29
+ /** @type {string} */
30
+ class: className = '',
31
+ /** @type {import('svelte').Snippet | undefined} */
32
+ icon = undefined,
33
+ ...rest
34
+ } = $props();
35
+ </script>
36
+
37
+ {#if href && !disabled}
38
+ <a
39
+ {href}
40
+ class="bottom-nav-item {className}"
41
+ class:bottom-nav-active={active}
42
+ aria-current={active ? 'page' : undefined}
43
+ {...rest}
44
+ >
45
+ <div class="bottom-nav-icon-wrap">
46
+ {#if icon}{@render icon()}{/if}
47
+ {#if badge}<span class="bottom-nav-badge" aria-hidden="true"></span>{/if}
48
+ </div>
49
+ <span class="bottom-nav-label">{label}</span>
50
+ </a>
51
+ {:else}
52
+ <button
53
+ class="bottom-nav-item {className}"
54
+ class:bottom-nav-active={active}
55
+ class:bottom-nav-disabled={disabled}
56
+ {disabled}
57
+ aria-current={active ? 'page' : undefined}
58
+ {...rest}
59
+ >
60
+ <div class="bottom-nav-icon-wrap">
61
+ {#if icon}{@render icon()}{/if}
62
+ {#if badge}<span class="bottom-nav-badge" aria-hidden="true"></span>{/if}
63
+ </div>
64
+ <span class="bottom-nav-label">{label}</span>
65
+ </button>
66
+ {/if}
67
+
68
+ <style>
69
+ .bottom-nav-item {
70
+ all: unset;
71
+ display: flex;
72
+ flex-direction: column;
73
+ align-items: center;
74
+ gap: var(--space-2xs);
75
+ cursor: pointer;
76
+ color: var(--nav-bottom-item-color);
77
+ transition: color var(--duration-instant) var(--easing-default);
78
+ padding: var(--space-xs);
79
+ text-decoration: none;
80
+ }
81
+
82
+ .bottom-nav-item:focus-visible {
83
+ outline: var(--focus-ring-width) solid var(--focus-ring-color);
84
+ outline-offset: var(--focus-ring-offset);
85
+ }
86
+
87
+ .bottom-nav-active {
88
+ color: var(--nav-bottom-item-color-active);
89
+ }
90
+
91
+ .bottom-nav-disabled {
92
+ opacity: 0.4;
93
+ cursor: not-allowed;
94
+ pointer-events: none;
95
+ }
96
+
97
+ .bottom-nav-icon-wrap {
98
+ position: relative;
99
+ display: flex;
100
+ }
101
+
102
+ .bottom-nav-icon-wrap :global(svg) {
103
+ width: 20px;
104
+ height: 20px;
105
+ }
106
+
107
+ .bottom-nav-badge {
108
+ position: absolute;
109
+ top: -2px;
110
+ right: -4px;
111
+ width: var(--status-dot-size);
112
+ height: var(--status-dot-size);
113
+ background: var(--color-accent);
114
+ border-radius: var(--radius-circle);
115
+ }
116
+
117
+ .bottom-nav-label {
118
+ font-family: var(--nav-bottom-item-font);
119
+ font-size: var(--nav-bottom-item-size);
120
+ }
121
+ </style>
@@ -0,0 +1,269 @@
1
+ <!--
2
+ @component Button
3
+
4
+ Four variants, three sizes. Labels always mono (Berkeley Mono).
5
+ Renders as `<a>` when `href` is provided, `<button>` otherwise.
6
+ Consumes --button-* tokens from components.css.
7
+
8
+ @example
9
+ <Button variant="primary" size="md">CREATE</Button>
10
+
11
+ @example With icon
12
+ <Button variant="secondary" size="sm">
13
+ {#snippet icon()}<PhPlus size={16} />{/snippet}
14
+ ADD ITEM
15
+ </Button>
16
+
17
+ @example As link
18
+ <Button variant="secondary" size="sm" href="/some/path">
19
+ GO THERE
20
+ </Button>
21
+
22
+ @example Loading
23
+ <Button variant="primary" loading>SAVING</Button>
24
+
25
+ @example Icon only
26
+ <Button variant="ghost" size="md" iconOnly aria-label="Settings">
27
+ {#snippet icon()}<PhGear size={16} />{/snippet}
28
+ </Button>
29
+ -->
30
+ <script>
31
+ /**
32
+ * @typedef {'primary' | 'secondary' | 'ghost' | 'destructive'} Variant
33
+ * @typedef {'sm' | 'md' | 'lg'} Size
34
+ * @typedef {'button' | 'submit' | 'reset'} ButtonType
35
+ */
36
+
37
+ let {
38
+ /** @type {Variant} */
39
+ variant = 'primary',
40
+ /** @type {Size} */
41
+ size = 'md',
42
+ /** @type {boolean} */
43
+ loading = false,
44
+ /** @type {boolean} */
45
+ disabled = false,
46
+ /** @type {boolean} */
47
+ iconOnly = false,
48
+ /** @type {ButtonType} */
49
+ type = 'button',
50
+ /** @type {string | undefined} */
51
+ href = undefined,
52
+ /** @type {string} */
53
+ class: className = '',
54
+ /** @type {import('svelte').Snippet | undefined} */
55
+ icon = undefined,
56
+ /** @type {import('svelte').Snippet | undefined} */
57
+ children = undefined,
58
+ ...rest
59
+ } = $props();
60
+
61
+ /** @type {'button' | 'submit' | 'reset'} — cast needed for svelte-check on <button type> */
62
+ const buttonType = $derived(/** @type {'button' | 'submit' | 'reset'} */ (type));
63
+ const classes = $derived(`btn btn-${variant} btn-${size} ${iconOnly ? 'btn-icon-only' : ''} ${className}`.trim());
64
+ </script>
65
+
66
+ {#snippet content()}
67
+ {#if loading}
68
+ <span class="spinner" aria-hidden="true"></span>
69
+ {/if}
70
+ {#if icon && !loading}
71
+ <span class="btn-icon-wrap">{@render icon()}</span>
72
+ {/if}
73
+ {#if children}
74
+ <span class="btn-label">{@render children()}</span>
75
+ {/if}
76
+ {/snippet}
77
+
78
+ {#if href && !disabled}
79
+ <a
80
+ {href}
81
+ class={classes}
82
+ aria-busy={loading || undefined}
83
+ {...rest}
84
+ >
85
+ {@render content()}
86
+ </a>
87
+ {:else}
88
+ <button
89
+ type={buttonType}
90
+ class={classes}
91
+ disabled={disabled || loading}
92
+ aria-busy={loading || undefined}
93
+ {...rest}
94
+ >
95
+ {@render content()}
96
+ </button>
97
+ {/if}
98
+
99
+ <style>
100
+ /* ─── Base ─── */
101
+ .btn {
102
+ display: inline-flex;
103
+ align-items: center;
104
+ justify-content: center;
105
+ gap: var(--space-xs);
106
+ font-family: var(--button-font);
107
+ letter-spacing: var(--button-tracking);
108
+ border-radius: var(--button-radius);
109
+ border: none;
110
+ cursor: pointer;
111
+ transition: all var(--button-transition);
112
+ white-space: nowrap;
113
+ text-decoration: none;
114
+ }
115
+
116
+ .btn:disabled {
117
+ opacity: 0.5;
118
+ cursor: not-allowed;
119
+ }
120
+
121
+ .btn:focus-visible {
122
+ outline: var(--focus-ring-width) solid var(--focus-ring-color);
123
+ outline-offset: var(--focus-ring-offset);
124
+ }
125
+
126
+ /* ─── Sizes ─── */
127
+ .btn-sm {
128
+ height: var(--button-sm-height);
129
+ padding: 0 var(--button-sm-padding-x);
130
+ font-size: var(--button-sm-font-size);
131
+ }
132
+
133
+ .btn-md {
134
+ height: var(--button-md-height);
135
+ padding: 0 var(--button-md-padding-x);
136
+ font-size: var(--button-md-font-size);
137
+ }
138
+
139
+ .btn-lg {
140
+ height: var(--button-lg-height);
141
+ padding: 0 var(--button-lg-padding-x);
142
+ font-size: var(--button-lg-font-size);
143
+ }
144
+
145
+ /* ─── Primary ─── */
146
+ .btn-primary {
147
+ background: var(--button-primary-bg);
148
+ color: var(--button-primary-text);
149
+ border: var(--button-primary-border);
150
+ }
151
+
152
+ .btn-primary:hover:not(:disabled) {
153
+ background: var(--button-primary-bg-hover);
154
+ }
155
+
156
+ .btn-primary:active:not(:disabled) {
157
+ background: var(--button-primary-bg-hover);
158
+ transform: scale(0.97);
159
+ }
160
+
161
+ /* ─── Secondary ─── */
162
+ .btn-secondary {
163
+ background: var(--button-secondary-bg);
164
+ color: var(--button-secondary-text);
165
+ border: var(--button-secondary-border);
166
+ }
167
+
168
+ .btn-secondary:hover:not(:disabled) {
169
+ background: var(--button-secondary-bg-hover);
170
+ }
171
+
172
+ .btn-secondary:active:not(:disabled) {
173
+ background: var(--button-secondary-bg-hover);
174
+ transform: scale(0.97);
175
+ }
176
+
177
+ /* ─── Ghost ─── */
178
+ .btn-ghost {
179
+ background: var(--button-ghost-bg);
180
+ color: var(--button-ghost-text);
181
+ border: var(--button-ghost-border);
182
+ }
183
+
184
+ .btn-ghost:hover:not(:disabled) {
185
+ background: var(--button-ghost-bg-hover);
186
+ }
187
+
188
+ .btn-ghost:active:not(:disabled) {
189
+ background: var(--button-ghost-bg-hover);
190
+ transform: scale(0.97);
191
+ }
192
+
193
+ /* ─── Destructive ─── */
194
+ .btn-destructive {
195
+ background: var(--button-destructive-bg);
196
+ color: var(--button-destructive-text);
197
+ border: var(--button-destructive-border);
198
+ }
199
+
200
+ .btn-destructive:hover:not(:disabled) {
201
+ background: var(--button-destructive-bg-hover);
202
+ }
203
+
204
+ .btn-destructive:active:not(:disabled) {
205
+ background: var(--button-destructive-bg-hover);
206
+ transform: scale(0.97);
207
+ }
208
+
209
+ /* ─── Label ─── */
210
+ .btn-label {
211
+ font-family: inherit;
212
+ font-size: inherit;
213
+ letter-spacing: inherit;
214
+ }
215
+
216
+ /* ─── Icon ─── */
217
+ .btn-icon-wrap {
218
+ display: flex;
219
+ align-items: center;
220
+ justify-content: center;
221
+ width: 16px;
222
+ height: 16px;
223
+ flex-shrink: 0;
224
+ }
225
+
226
+ .btn-icon-wrap :global(svg) {
227
+ width: 100%;
228
+ height: 100%;
229
+ }
230
+
231
+ .btn-icon-only {
232
+ padding: 0;
233
+ }
234
+
235
+ .btn-icon-only.btn-sm {
236
+ width: var(--button-sm-height);
237
+ }
238
+
239
+ .btn-icon-only.btn-md {
240
+ width: var(--button-md-height);
241
+ }
242
+
243
+ .btn-icon-only.btn-lg {
244
+ width: var(--button-lg-height);
245
+ }
246
+
247
+ /* ─── Spinner ─── */
248
+ .spinner {
249
+ width: 14px;
250
+ height: 14px;
251
+ border: var(--border-width-thick) solid currentColor;
252
+ border-top-color: transparent;
253
+ border-radius: var(--radius-circle);
254
+ animation: spin 0.6s linear infinite;
255
+ flex-shrink: 0;
256
+ }
257
+
258
+ @keyframes spin {
259
+ to {
260
+ transform: rotate(360deg);
261
+ }
262
+ }
263
+
264
+ @media (prefers-reduced-motion: reduce) {
265
+ .spinner {
266
+ animation: none;
267
+ }
268
+ }
269
+ </style>