@realmorg/ui 0.0.0-stage → 2.0.0-alpha.3

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 (66) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +44 -2
  3. package/components/accordion.html +139 -0
  4. package/components/alert-dialog.html +236 -0
  5. package/components/alert.html +83 -0
  6. package/components/aspect-ratio.html +29 -0
  7. package/components/attachment.html +98 -0
  8. package/components/avatar.html +149 -0
  9. package/components/badge.html +62 -0
  10. package/components/breadcrumb.html +159 -0
  11. package/components/bubble.html +77 -0
  12. package/components/button-group.html +98 -0
  13. package/components/button.html +194 -0
  14. package/components/calendar.html +176 -0
  15. package/components/card.html +143 -0
  16. package/components/carousel.html +208 -0
  17. package/components/chart.html +168 -0
  18. package/components/checkbox.html +99 -0
  19. package/components/code-block.html +64 -0
  20. package/components/collapsible.html +90 -0
  21. package/components/combobox.html +237 -0
  22. package/components/command.html +201 -0
  23. package/components/context-menu.html +81 -0
  24. package/components/dialog.html +327 -0
  25. package/components/direction.html +23 -0
  26. package/components/drawer.html +267 -0
  27. package/components/dropdown-menu.html +421 -0
  28. package/components/empty.html +132 -0
  29. package/components/field.html +143 -0
  30. package/components/hover-card.html +111 -0
  31. package/components/input-group.html +91 -0
  32. package/components/input-otp.html +151 -0
  33. package/components/input.html +94 -0
  34. package/components/item.html +208 -0
  35. package/components/kbd.html +45 -0
  36. package/components/label.html +46 -0
  37. package/components/marker.html +41 -0
  38. package/components/menubar.html +137 -0
  39. package/components/message-scroller.html +68 -0
  40. package/components/message.html +95 -0
  41. package/components/navigation-menu.html +233 -0
  42. package/components/pagination.html +160 -0
  43. package/components/popover.html +164 -0
  44. package/components/progress.html +45 -0
  45. package/components/radio-group.html +120 -0
  46. package/components/resizable.html +170 -0
  47. package/components/scroll-area.html +56 -0
  48. package/components/select.html +103 -0
  49. package/components/separator.html +31 -0
  50. package/components/sheet.html +173 -0
  51. package/components/sidebar.html +393 -0
  52. package/components/skeleton.html +36 -0
  53. package/components/slider.html +108 -0
  54. package/components/spinner.html +53 -0
  55. package/components/switch.html +95 -0
  56. package/components/table.html +81 -0
  57. package/components/tabs.html +182 -0
  58. package/components/textarea.html +80 -0
  59. package/components/toaster.html +134 -0
  60. package/components/toggle-group.html +71 -0
  61. package/components/toggle.html +87 -0
  62. package/components/tooltip.html +114 -0
  63. package/dist/theme.css +158 -0
  64. package/dist/ui.html +7923 -0
  65. package/package.json +40 -3
  66. package/theme.css +158 -0
@@ -0,0 +1,421 @@
1
+ <custom-element name="ui-dropdown-menu">
2
+ <script>
3
+ let wasOpen = false;
4
+
5
+ function press() {
6
+ wasOpen = host.querySelector(":scope > ui-dropdown-menu-content").matches(":popover-open");
7
+ }
8
+
9
+ function toggle(trigger) {
10
+ const content = host.querySelector(":scope > ui-dropdown-menu-content");
11
+ const open = wasOpen || content.matches(":popover-open");
12
+ wasOpen = false;
13
+ if (open) content.hidePopover();
14
+ else {
15
+ trigger.firstElementChild?.focus();
16
+ content.showPopover({ source: trigger });
17
+ }
18
+ }
19
+ </script>
20
+
21
+ <style>
22
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
23
+ touch-action does not reach shadow content in iOS Safari. */
24
+ :host,
25
+ * {
26
+ touch-action: manipulation;
27
+ }
28
+ :host {
29
+ display: contents;
30
+ }
31
+ </style>
32
+
33
+ <slot></slot>
34
+ </custom-element>
35
+
36
+ <custom-element name="ui-dropdown-menu-trigger">
37
+ <style>
38
+ :host,
39
+ * {
40
+ touch-action: manipulation;
41
+ }
42
+ :host {
43
+ display: inline-block;
44
+ vertical-align: middle;
45
+ }
46
+ </style>
47
+
48
+ <span part="trigger" onpointerdown="press()" onclick="toggle(host)"><slot></slot></span>
49
+ </custom-element>
50
+
51
+ <custom-element name="ui-dropdown-menu-content" role="menu">
52
+ <script>
53
+ defineProps({
54
+ placement: prop.enum(["bottom-start", "bottom-end", "top-start", "top-end"]).default("bottom-start"),
55
+ });
56
+
57
+ const ITEMS = ":is(ui-menu-item, ui-menu-checkbox-item, ui-menu-radio-item):not([disabled])";
58
+
59
+ function opened(event) {
60
+ if (event.newState === "open") host.querySelector(ITEMS)?.focus();
61
+ }
62
+
63
+ function leave(event) {
64
+ if (document.hasFocus() && !host.contains(event.relatedTarget)) host.hidePopover();
65
+ }
66
+
67
+ attrs.popover ??= "auto";
68
+
69
+ listen("toggle", opened);
70
+ listen("focusout", leave);
71
+ listen("select", () => host.hidePopover());
72
+ </script>
73
+
74
+ <style>
75
+ :host,
76
+ * {
77
+ touch-action: manipulation;
78
+ }
79
+ :host {
80
+ box-sizing: border-box;
81
+ min-width: calc(var(--spacing) * 32);
82
+ margin: calc(var(--spacing) * 1) 0 0;
83
+ padding: calc(var(--spacing) * 1);
84
+ border: 1px solid var(--border);
85
+ border-radius: var(--radius-md);
86
+ background: var(--popover);
87
+ color: var(--popover-foreground);
88
+ box-shadow: var(--shadow-md);
89
+ inset: auto;
90
+ height: fit-content;
91
+ max-height: calc(100svh - calc(var(--spacing) * 8));
92
+ overflow-y: auto;
93
+ position-area: bottom span-right;
94
+ position-try-fallbacks: flip-block, flip-inline;
95
+ }
96
+ :host(:state(attr-placement-is-bottom-end)) {
97
+ position-area: bottom span-left;
98
+ }
99
+ :host(:state(attr-placement-is-top-start)) {
100
+ position-area: top span-right;
101
+ margin: 0 0 calc(var(--spacing) * 1);
102
+ }
103
+ :host(:state(attr-placement-is-top-end)) {
104
+ position-area: top span-left;
105
+ margin: 0 0 calc(var(--spacing) * 1);
106
+ }
107
+ </style>
108
+
109
+ <div part="menu" focusgroup="menu block wrap"><slot></slot></div>
110
+ </custom-element>
111
+
112
+ <custom-element name="ui-menu-item" role="menuitem">
113
+ <script>
114
+ const props = defineProps({
115
+ value: prop.string(),
116
+ disabled: prop.boolean(),
117
+ variant: prop.enum(["default", "destructive"]).default("default"),
118
+ });
119
+
120
+ const ACTIVATE = new Set(["Enter", " "]);
121
+
122
+ function choose() {
123
+ if (!props.disabled) dispatch("select", { value: props.value });
124
+ }
125
+
126
+ function activate(event) {
127
+ if (!ACTIVATE.has(event.key)) return;
128
+ event.preventDefault();
129
+ choose();
130
+ }
131
+
132
+ attrs.tabindex ??= -1;
133
+
134
+ listen("click", choose);
135
+ listen("keydown", activate);
136
+
137
+ observe(() => {
138
+ internals.ariaDisabled = String(props.disabled);
139
+ });
140
+ </script>
141
+
142
+ <style>
143
+ :host,
144
+ * {
145
+ touch-action: manipulation;
146
+ }
147
+ :host {
148
+ display: flex;
149
+ align-items: center;
150
+ gap: calc(var(--spacing) * 2);
151
+ padding: calc(var(--spacing) * 1.5) calc(var(--spacing) * 2);
152
+ border-radius: var(--radius-sm);
153
+ font-size: var(--text-sm);
154
+ cursor: default;
155
+ outline: none;
156
+ user-select: none;
157
+ }
158
+ :host(:hover),
159
+ :host(:focus-visible) {
160
+ background: var(--accent);
161
+ color: var(--accent-foreground);
162
+ }
163
+ :host(:state(attr-variant-is-destructive)) {
164
+ color: var(--destructive);
165
+ }
166
+ :host(:state(attr-disabled)) {
167
+ pointer-events: none;
168
+ opacity: 0.5;
169
+ }
170
+ ::slotted(svg) {
171
+ width: calc(var(--spacing) * 4);
172
+ height: calc(var(--spacing) * 4);
173
+ color: var(--muted-foreground);
174
+ }
175
+ </style>
176
+
177
+ <slot></slot>
178
+ </custom-element>
179
+
180
+ <custom-element name="ui-menu-checkbox-item" role="menuitemcheckbox">
181
+ <script>
182
+ const props = defineProps({
183
+ value: prop.string(),
184
+ checked: prop.boolean().reflect(),
185
+ disabled: prop.boolean(),
186
+ });
187
+
188
+ const ACTIVATE = new Set(["Enter", " "]);
189
+
190
+ function choose() {
191
+ if (props.disabled) return;
192
+ props.checked = !props.checked;
193
+ dispatch("select", { value: props.value, checked: props.checked });
194
+ }
195
+
196
+ function activate(event) {
197
+ if (!ACTIVATE.has(event.key)) return;
198
+ event.preventDefault();
199
+ choose();
200
+ }
201
+
202
+ attrs.tabindex ??= -1;
203
+
204
+ listen("click", choose);
205
+ listen("keydown", activate);
206
+
207
+ observe(() => {
208
+ internals.ariaDisabled = String(props.disabled);
209
+ internals.ariaChecked = String(props.checked);
210
+ });
211
+ </script>
212
+
213
+ <style>
214
+ :host,
215
+ * {
216
+ touch-action: manipulation;
217
+ }
218
+ :host {
219
+ display: flex;
220
+ align-items: center;
221
+ gap: calc(var(--spacing) * 2);
222
+ padding: calc(var(--spacing) * 1.5) calc(var(--spacing) * 2) calc(var(--spacing) * 1.5) calc(var(--spacing) * 8);
223
+ position: relative;
224
+ border-radius: var(--radius-sm);
225
+ font-size: var(--text-sm);
226
+ cursor: default;
227
+ outline: none;
228
+ user-select: none;
229
+ }
230
+ :host(:hover),
231
+ :host(:focus-visible) {
232
+ background: var(--accent);
233
+ color: var(--accent-foreground);
234
+ }
235
+ :host(:state(attr-disabled)) {
236
+ pointer-events: none;
237
+ opacity: 0.5;
238
+ }
239
+ svg {
240
+ position: absolute;
241
+ left: calc(var(--spacing) * 2);
242
+ width: calc(var(--spacing) * 4);
243
+ height: calc(var(--spacing) * 4);
244
+ visibility: hidden;
245
+ }
246
+ :host(:state(attr-checked)) svg {
247
+ visibility: visible;
248
+ }
249
+ </style>
250
+
251
+ <svg aria-hidden="true" part="indicator" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6 9 17l-5-5" /></svg>
252
+ <slot></slot>
253
+ </custom-element>
254
+
255
+ <custom-element name="ui-menu-radio-group" role="group">
256
+ <script>
257
+ const props = defineProps({ value: prop.string().reflect() });
258
+
259
+ const items = useChildren("ui-menu-radio-item", ["value"]);
260
+
261
+ function picked(event) {
262
+ if (event.target.localName === "ui-menu-radio-item") props.value = event.detail.value;
263
+ }
264
+
265
+ listen("select", picked);
266
+
267
+ observe(() => {
268
+ for (const item of items) item.toggleAttribute("checked", item.getAttribute("value") === props.value);
269
+ });
270
+ </script>
271
+
272
+ <style>
273
+ :host,
274
+ * {
275
+ touch-action: manipulation;
276
+ }
277
+ :host {
278
+ display: block;
279
+ }
280
+ </style>
281
+
282
+ <slot></slot>
283
+ </custom-element>
284
+
285
+ <custom-element name="ui-menu-radio-item" role="menuitemradio">
286
+ <script>
287
+ const props = defineProps({
288
+ value: prop.string(),
289
+ checked: prop.boolean(),
290
+ disabled: prop.boolean(),
291
+ });
292
+
293
+ const ACTIVATE = new Set(["Enter", " "]);
294
+
295
+ function choose() {
296
+ if (!props.disabled) dispatch("select", { value: props.value });
297
+ }
298
+
299
+ function activate(event) {
300
+ if (!ACTIVATE.has(event.key)) return;
301
+ event.preventDefault();
302
+ choose();
303
+ }
304
+
305
+ attrs.tabindex ??= -1;
306
+
307
+ listen("click", choose);
308
+ listen("keydown", activate);
309
+
310
+ observe(() => {
311
+ internals.ariaDisabled = String(props.disabled);
312
+ internals.ariaChecked = String(props.checked);
313
+ });
314
+ </script>
315
+
316
+ <style>
317
+ :host,
318
+ * {
319
+ touch-action: manipulation;
320
+ }
321
+ :host {
322
+ display: flex;
323
+ align-items: center;
324
+ gap: calc(var(--spacing) * 2);
325
+ padding: calc(var(--spacing) * 1.5) calc(var(--spacing) * 2) calc(var(--spacing) * 1.5) calc(var(--spacing) * 8);
326
+ position: relative;
327
+ border-radius: var(--radius-sm);
328
+ font-size: var(--text-sm);
329
+ cursor: default;
330
+ outline: none;
331
+ user-select: none;
332
+ }
333
+ :host(:hover),
334
+ :host(:focus-visible) {
335
+ background: var(--accent);
336
+ color: var(--accent-foreground);
337
+ }
338
+ :host(:state(attr-disabled)) {
339
+ pointer-events: none;
340
+ opacity: 0.5;
341
+ }
342
+ span {
343
+ position: absolute;
344
+ left: calc(var(--spacing) * 3.5);
345
+ width: calc(var(--spacing) * 2);
346
+ height: calc(var(--spacing) * 2);
347
+ border-radius: var(--radius-full);
348
+ background: currentColor;
349
+ visibility: hidden;
350
+ }
351
+ :host(:state(attr-checked)) span {
352
+ visibility: visible;
353
+ }
354
+ </style>
355
+
356
+ <span aria-hidden="true" part="indicator"></span>
357
+ <slot></slot>
358
+ </custom-element>
359
+
360
+ <custom-element name="ui-menu-label">
361
+ <style>
362
+ :host,
363
+ * {
364
+ touch-action: manipulation;
365
+ }
366
+ :host {
367
+ display: block;
368
+ padding: calc(var(--spacing) * 1.5) calc(var(--spacing) * 2);
369
+ font-size: var(--text-sm);
370
+ font-weight: var(--font-weight-medium);
371
+ }
372
+ </style>
373
+
374
+ <slot></slot>
375
+ </custom-element>
376
+
377
+ <custom-element name="ui-menu-group" role="group">
378
+ <style>
379
+ :host,
380
+ * {
381
+ touch-action: manipulation;
382
+ }
383
+ :host {
384
+ display: block;
385
+ }
386
+ </style>
387
+
388
+ <slot></slot>
389
+ </custom-element>
390
+
391
+ <custom-element name="ui-menu-separator" role="separator">
392
+ <style>
393
+ :host,
394
+ * {
395
+ touch-action: manipulation;
396
+ }
397
+ :host {
398
+ display: block;
399
+ height: 1px;
400
+ margin: calc(var(--spacing) * 1) calc(var(--spacing) * -1);
401
+ background: var(--border);
402
+ }
403
+ </style>
404
+ </custom-element>
405
+
406
+ <custom-element name="ui-menu-shortcut">
407
+ <style>
408
+ :host,
409
+ * {
410
+ touch-action: manipulation;
411
+ }
412
+ :host {
413
+ margin-inline-start: auto;
414
+ font-size: var(--text-xs);
415
+ letter-spacing: 0.1em;
416
+ color: var(--muted-foreground);
417
+ }
418
+ </style>
419
+
420
+ <slot></slot>
421
+ </custom-element>
@@ -0,0 +1,132 @@
1
+ <custom-element name="ui-empty">
2
+ <style>
3
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
4
+ touch-action does not reach shadow content in iOS Safari. */
5
+ :host,
6
+ * {
7
+ touch-action: manipulation;
8
+ }
9
+ :host {
10
+ display: flex;
11
+ flex-direction: column;
12
+ align-items: center;
13
+ justify-content: center;
14
+ gap: calc(var(--spacing) * 6);
15
+ min-width: 0;
16
+ padding: calc(var(--spacing) * 12) calc(var(--spacing) * 6);
17
+ border: 1px dashed var(--border);
18
+ border-radius: var(--radius-lg);
19
+ text-align: center;
20
+ text-wrap: balance;
21
+ }
22
+ </style>
23
+
24
+ <slot></slot>
25
+ </custom-element>
26
+
27
+ <custom-element name="ui-empty-header">
28
+ <style>
29
+ :host,
30
+ * {
31
+ touch-action: manipulation;
32
+ }
33
+ :host {
34
+ display: flex;
35
+ flex-direction: column;
36
+ align-items: center;
37
+ gap: calc(var(--spacing) * 2);
38
+ max-width: calc(var(--spacing) * 96);
39
+ }
40
+ </style>
41
+
42
+ <slot></slot>
43
+ </custom-element>
44
+
45
+ <custom-element name="ui-empty-media">
46
+ <style>
47
+ :host,
48
+ * {
49
+ touch-action: manipulation;
50
+ }
51
+ :host {
52
+ display: flex;
53
+ flex-shrink: 0;
54
+ align-items: center;
55
+ justify-content: center;
56
+ margin-bottom: calc(var(--spacing) * 2);
57
+ }
58
+ :host(:state(attr-variant-is-icon)) {
59
+ width: calc(var(--spacing) * 10);
60
+ height: calc(var(--spacing) * 10);
61
+ border-radius: var(--radius-lg);
62
+ background: var(--muted);
63
+ color: var(--foreground);
64
+ }
65
+ ::slotted(svg) {
66
+ width: calc(var(--spacing) * 6);
67
+ height: calc(var(--spacing) * 6);
68
+ }
69
+ </style>
70
+
71
+ <slot></slot>
72
+ </custom-element>
73
+
74
+ <custom-element name="ui-empty-title">
75
+ <script>
76
+ defineProps({ level: prop.number().int().min(1).max(6).default(3) });
77
+ </script>
78
+
79
+ <style>
80
+ :host,
81
+ * {
82
+ touch-action: manipulation;
83
+ }
84
+ :host {
85
+ display: block;
86
+ }
87
+ [part="title"] {
88
+ margin: 0;
89
+ font-size: var(--text-lg);
90
+ font-weight: var(--font-weight-medium);
91
+ letter-spacing: -0.01em;
92
+ }
93
+ </style>
94
+
95
+ <div part="title" role="heading" _aria-level="@level"><slot></slot></div>
96
+ </custom-element>
97
+
98
+ <custom-element name="ui-empty-description">
99
+ <style>
100
+ :host,
101
+ * {
102
+ touch-action: manipulation;
103
+ }
104
+ :host {
105
+ display: block;
106
+ font-size: var(--text-sm);
107
+ color: var(--muted-foreground);
108
+ }
109
+ </style>
110
+
111
+ <slot></slot>
112
+ </custom-element>
113
+
114
+ <custom-element name="ui-empty-content">
115
+ <style>
116
+ :host,
117
+ * {
118
+ touch-action: manipulation;
119
+ }
120
+ :host {
121
+ display: flex;
122
+ flex-direction: column;
123
+ align-items: center;
124
+ gap: calc(var(--spacing) * 4);
125
+ width: 100%;
126
+ max-width: calc(var(--spacing) * 96);
127
+ font-size: var(--text-sm);
128
+ }
129
+ </style>
130
+
131
+ <slot></slot>
132
+ </custom-element>
@@ -0,0 +1,143 @@
1
+ <custom-element name="ui-field" role="group">
2
+ <script>
3
+ const props = defineProps({
4
+ orientation: prop.enum(["vertical", "horizontal"]).default("vertical"),
5
+ invalid: prop.boolean(),
6
+ error: prop.string(),
7
+ });
8
+ </script>
9
+
10
+ <style>
11
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
12
+ touch-action does not reach shadow content in iOS Safari. */
13
+ :host,
14
+ * {
15
+ touch-action: manipulation;
16
+ }
17
+ :host {
18
+ display: flex;
19
+ flex-direction: column;
20
+ gap: calc(var(--spacing) * 3);
21
+ width: 100%;
22
+ }
23
+ :host(:state(attr-orientation-is-horizontal)) {
24
+ flex-flow: row wrap;
25
+ align-items: center;
26
+ }
27
+ :host(:state(attr-orientation-is-horizontal)) ::slotted(ui-label) {
28
+ flex: 1 1 auto;
29
+ }
30
+ ::slotted(ui-label) {
31
+ line-height: 1.375;
32
+ }
33
+ :host(:state(attr-invalid)) ::slotted(ui-label) {
34
+ color: var(--destructive);
35
+ }
36
+ .error {
37
+ flex-basis: 100%;
38
+ margin: 0;
39
+ font-size: var(--text-sm);
40
+ color: var(--destructive);
41
+ }
42
+ </style>
43
+
44
+ <slot></slot>
45
+ <p class="error" role="alert" part="error" _hidden="!@error"><slot name="@error"></slot></p>
46
+ </custom-element>
47
+
48
+ <custom-element name="ui-field-content">
49
+ <style>
50
+ :host,
51
+ * {
52
+ touch-action: manipulation;
53
+ }
54
+ :host {
55
+ display: flex;
56
+ flex: 1;
57
+ flex-direction: column;
58
+ gap: calc(var(--spacing) * 1.5);
59
+ line-height: 1.375;
60
+ }
61
+ </style>
62
+
63
+ <slot></slot>
64
+ </custom-element>
65
+
66
+ <custom-element name="ui-field-description">
67
+ <style>
68
+ :host,
69
+ * {
70
+ touch-action: manipulation;
71
+ }
72
+ :host {
73
+ display: block;
74
+ font-size: var(--text-sm);
75
+ line-height: 1.375;
76
+ color: var(--muted-foreground);
77
+ }
78
+ </style>
79
+
80
+ <slot></slot>
81
+ </custom-element>
82
+
83
+ <custom-element name="ui-field-group">
84
+ <style>
85
+ :host,
86
+ * {
87
+ touch-action: manipulation;
88
+ }
89
+ :host {
90
+ display: flex;
91
+ flex-direction: column;
92
+ gap: calc(var(--spacing) * 7);
93
+ width: 100%;
94
+ }
95
+ </style>
96
+
97
+ <slot></slot>
98
+ </custom-element>
99
+
100
+ <custom-element name="ui-field-set" role="group">
101
+ <script>
102
+ function named(slot) {
103
+ internals.ariaLabelledByElements = slot.assignedElements();
104
+ }
105
+ </script>
106
+
107
+ <style>
108
+ :host,
109
+ * {
110
+ touch-action: manipulation;
111
+ }
112
+ :host {
113
+ display: flex;
114
+ flex-direction: column;
115
+ gap: calc(var(--spacing) * 6);
116
+ }
117
+ ::slotted(ui-field-description) {
118
+ margin-top: calc(var(--spacing) * -4);
119
+ }
120
+ </style>
121
+
122
+ <slot name="legend" onslotchange="named(this)"></slot>
123
+ <slot></slot>
124
+ </custom-element>
125
+
126
+ <custom-element name="ui-field-legend" slot="legend">
127
+ <style>
128
+ :host,
129
+ * {
130
+ touch-action: manipulation;
131
+ }
132
+ :host {
133
+ display: block;
134
+ font-size: var(--text-base);
135
+ font-weight: var(--font-weight-medium);
136
+ }
137
+ :host(:state(attr-variant-is-label)) {
138
+ font-size: var(--text-sm);
139
+ }
140
+ </style>
141
+
142
+ <slot></slot>
143
+ </custom-element>