@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,46 @@
1
+ <custom-element name="ui-label">
2
+ <script>
3
+ const props = defineProps({ for: prop.string() });
4
+
5
+ const CHECKABLE = new Set(["checkbox", "radio"]);
6
+
7
+ function target() {
8
+ return props.for ? host.getRootNode().getElementById(props.for) : null;
9
+ }
10
+
11
+ function activate() {
12
+ const field = target();
13
+ if (!field) return;
14
+ field.focus();
15
+ if (CHECKABLE.has(field.type) || "checked" in field) (field.shadowRoot?.querySelector('[ref="control"]') ?? field).click();
16
+ }
17
+
18
+ onConnected(() => {
19
+ const field = target();
20
+ if (field && !field.hasAttribute("aria-label")) field.setAttribute("aria-label", host.textContent.trim());
21
+ });
22
+ </script>
23
+
24
+ <style>
25
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
26
+ touch-action does not reach shadow content in iOS Safari. */
27
+ :host,
28
+ * {
29
+ touch-action: manipulation;
30
+ }
31
+ :host {
32
+ display: inline-flex;
33
+ align-items: center;
34
+ gap: calc(var(--spacing) * 2);
35
+ font-size: var(--text-sm);
36
+ font-weight: var(--font-weight-medium);
37
+ line-height: 1;
38
+ user-select: none;
39
+ }
40
+ :host(:state(attr-for)) {
41
+ cursor: pointer;
42
+ }
43
+ </style>
44
+
45
+ <span part="label" onclick="activate()"><slot></slot></span>
46
+ </custom-element>
@@ -0,0 +1,41 @@
1
+ <custom-element name="ui-marker">
2
+ <script>
3
+ defineProps({ variant: prop.enum(["default", "separator", "border"]).default("default") });
4
+ </script>
5
+
6
+ <style>
7
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
8
+ touch-action does not reach shadow content in iOS Safari. */
9
+ :host,
10
+ * {
11
+ touch-action: manipulation;
12
+ }
13
+ :host {
14
+ display: flex;
15
+ align-items: center;
16
+ gap: calc(var(--spacing) * 2);
17
+ width: 100%;
18
+ min-height: calc(var(--spacing) * 4);
19
+ font-size: var(--text-sm);
20
+ color: var(--muted-foreground);
21
+ }
22
+ :host(:state(attr-variant-is-separator))::before,
23
+ :host(:state(attr-variant-is-separator))::after {
24
+ content: "";
25
+ flex: 1;
26
+ height: 1px;
27
+ background: var(--border);
28
+ }
29
+ :host(:state(attr-variant-is-border)) {
30
+ padding-bottom: calc(var(--spacing) * 2);
31
+ border-bottom: 1px solid var(--border);
32
+ }
33
+ ::slotted(svg) {
34
+ width: calc(var(--spacing) * 4);
35
+ height: calc(var(--spacing) * 4);
36
+ flex-shrink: 0;
37
+ }
38
+ </style>
39
+
40
+ <slot></slot>
41
+ </custom-element>
@@ -0,0 +1,137 @@
1
+ <custom-element name="ui-menubar" role="menubar">
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
+ align-items: center;
12
+ gap: calc(var(--spacing) * 1);
13
+ width: fit-content;
14
+ height: calc(var(--spacing) * 9);
15
+ padding: calc(var(--spacing) * 1);
16
+ box-sizing: border-box;
17
+ border: 1px solid var(--border);
18
+ border-radius: var(--radius-md);
19
+ background: var(--background);
20
+ box-shadow: var(--shadow-xs);
21
+ }
22
+ div {
23
+ display: contents;
24
+ }
25
+ </style>
26
+
27
+ <div part="bar" focusgroup="menubar inline wrap"><slot></slot></div>
28
+ </custom-element>
29
+
30
+ <custom-element name="ui-menubar-menu">
31
+ <script>
32
+ let wasOpen = false;
33
+
34
+ function press() {
35
+ wasOpen = host.querySelector(":scope > ui-menubar-content").matches(":popover-open");
36
+ }
37
+
38
+ function toggle(trigger) {
39
+ const content = host.querySelector(":scope > ui-menubar-content");
40
+ const open = wasOpen || content.matches(":popover-open");
41
+ wasOpen = false;
42
+ if (open) content.hidePopover();
43
+ else {
44
+ trigger.focus();
45
+ content.showPopover({ source: trigger });
46
+ }
47
+ }
48
+ </script>
49
+
50
+ <style>
51
+ :host,
52
+ * {
53
+ touch-action: manipulation;
54
+ }
55
+ :host {
56
+ display: contents;
57
+ }
58
+ </style>
59
+
60
+ <slot></slot>
61
+ </custom-element>
62
+
63
+ <custom-element name="ui-menubar-trigger">
64
+ <style>
65
+ :host,
66
+ * {
67
+ touch-action: manipulation;
68
+ }
69
+ :host {
70
+ display: inline-flex;
71
+ }
72
+ button {
73
+ all: unset;
74
+ display: inline-flex;
75
+ align-items: center;
76
+ padding: calc(var(--spacing) * 1) calc(var(--spacing) * 2);
77
+ border-radius: var(--radius-sm);
78
+ font-size: var(--text-sm);
79
+ font-weight: var(--font-weight-medium);
80
+ cursor: default;
81
+ user-select: none;
82
+ }
83
+ button:hover,
84
+ button:focus-visible {
85
+ background: var(--accent);
86
+ color: var(--accent-foreground);
87
+ }
88
+ </style>
89
+
90
+ <button type="button" part="trigger" role="menuitem" aria-haspopup="menu" onpointerdown="press()" onclick="toggle(this)"><slot></slot></button>
91
+ </custom-element>
92
+
93
+ <custom-element name="ui-menubar-content" role="menu">
94
+ <script>
95
+ const ITEMS = ":is(ui-menu-item, ui-menu-checkbox-item, ui-menu-radio-item):not([disabled])";
96
+
97
+ function opened(event) {
98
+ if (event.newState === "open") host.querySelector(ITEMS)?.focus();
99
+ }
100
+
101
+ function leave(event) {
102
+ if (document.hasFocus() && !host.contains(event.relatedTarget)) host.hidePopover();
103
+ }
104
+
105
+ attrs.popover ??= "auto";
106
+
107
+ listen("toggle", opened);
108
+ listen("focusout", leave);
109
+ listen("select", () => host.hidePopover());
110
+ </script>
111
+
112
+ <style>
113
+ :host,
114
+ * {
115
+ touch-action: manipulation;
116
+ }
117
+ :host {
118
+ box-sizing: border-box;
119
+ min-width: calc(var(--spacing) * 48);
120
+ margin: calc(var(--spacing) * 2) 0 0 calc(var(--spacing) * -1);
121
+ padding: calc(var(--spacing) * 1);
122
+ border: 1px solid var(--border);
123
+ border-radius: var(--radius-md);
124
+ background: var(--popover);
125
+ color: var(--popover-foreground);
126
+ box-shadow: var(--shadow-md);
127
+ inset: auto;
128
+ height: fit-content;
129
+ max-height: calc(100svh - calc(var(--spacing) * 8));
130
+ overflow-y: auto;
131
+ position-area: bottom span-right;
132
+ position-try-fallbacks: flip-block, flip-inline;
133
+ }
134
+ </style>
135
+
136
+ <div part="menu" focusgroup="menu block wrap"><slot></slot></div>
137
+ </custom-element>
@@ -0,0 +1,68 @@
1
+ <import-element from="./button.html"></import-element>
2
+
3
+ <custom-element name="ui-message-scroller">
4
+ <script>
5
+ const props = defineProps({ threshold: prop.number().min(0).default(48) });
6
+
7
+ const scroll = useState({ pinned: true });
8
+
9
+ function viewport() {
10
+ return refs.viewport;
11
+ }
12
+
13
+ function track(element) {
14
+ scroll.pinned = element.scrollHeight - element.scrollTop - element.clientHeight <= props.threshold;
15
+ }
16
+
17
+ function bottom() {
18
+ viewport().scrollTo({ top: viewport().scrollHeight, behavior: "smooth" });
19
+ }
20
+
21
+ onConnected(() => {
22
+ const observer = new MutationObserver(() => {
23
+ if (scroll.pinned) viewport().scrollTop = viewport().scrollHeight;
24
+ });
25
+ observer.observe(host, { childList: true, subtree: true, characterData: true });
26
+ viewport().scrollTop = viewport().scrollHeight;
27
+ return () => observer.disconnect();
28
+ });
29
+ </script>
30
+
31
+ <style>
32
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
33
+ touch-action does not reach shadow content in iOS Safari. */
34
+ :host,
35
+ * {
36
+ touch-action: manipulation;
37
+ }
38
+ :host {
39
+ position: relative;
40
+ display: block;
41
+ width: 100%;
42
+ min-height: 0;
43
+ }
44
+ :host(:state(attr-height)) {
45
+ height: calc(var(--spacing) * var(--attr-height));
46
+ }
47
+ .viewport {
48
+ height: 100%;
49
+ overflow-y: auto;
50
+ overscroll-behavior: contain;
51
+ scrollbar-width: thin;
52
+ }
53
+ .jump {
54
+ position: absolute;
55
+ left: 50%;
56
+ bottom: calc(var(--spacing) * 3);
57
+ translate: -50% 0;
58
+ }
59
+ :host(:state(state-scroll-pinned)) .jump {
60
+ display: none;
61
+ }
62
+ </style>
63
+
64
+ <div class="viewport" ref="viewport" part="viewport" role="log" aria-live="polite" onscroll="track(this)">
65
+ <slot></slot>
66
+ </div>
67
+ <ui-button class="jump" size="sm" variant="secondary" onclick="bottom()">Jump to latest</ui-button>
68
+ </custom-element>
@@ -0,0 +1,95 @@
1
+ <custom-element name="ui-message">
2
+ <script>
3
+ defineProps({ align: prop.enum(["start", "end"]).default("start") });
4
+ </script>
5
+
6
+ <style>
7
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
8
+ touch-action does not reach shadow content in iOS Safari. */
9
+ :host,
10
+ * {
11
+ touch-action: manipulation;
12
+ }
13
+ :host {
14
+ display: flex;
15
+ gap: calc(var(--spacing) * 2);
16
+ width: 100%;
17
+ min-width: 0;
18
+ font-size: var(--text-sm);
19
+ }
20
+ :host(:state(attr-align-is-end)) {
21
+ flex-direction: row-reverse;
22
+ }
23
+ .content {
24
+ display: flex;
25
+ flex-direction: column;
26
+ gap: calc(var(--spacing) * 2);
27
+ width: 100%;
28
+ min-width: 0;
29
+ }
30
+ :host(:state(attr-align-is-end)) .content {
31
+ align-items: flex-end;
32
+ }
33
+ </style>
34
+
35
+ <slot name="avatar"></slot>
36
+ <div class="content" part="content">
37
+ <slot name="header"></slot>
38
+ <slot></slot>
39
+ <slot name="footer"></slot>
40
+ </div>
41
+ </custom-element>
42
+
43
+ <custom-element name="ui-message-avatar" slot="avatar">
44
+ <style>
45
+ :host,
46
+ * {
47
+ touch-action: manipulation;
48
+ }
49
+ :host {
50
+ display: flex;
51
+ align-self: flex-end;
52
+ flex-shrink: 0;
53
+ }
54
+ </style>
55
+
56
+ <slot></slot>
57
+ </custom-element>
58
+
59
+ <custom-element name="ui-message-header" slot="header">
60
+ <style>
61
+ :host,
62
+ * {
63
+ touch-action: manipulation;
64
+ }
65
+ :host {
66
+ display: flex;
67
+ align-items: center;
68
+ gap: calc(var(--spacing) * 2);
69
+ padding: 0 calc(var(--spacing) * 3);
70
+ font-size: var(--text-xs);
71
+ color: var(--muted-foreground);
72
+ }
73
+ </style>
74
+
75
+ <slot></slot>
76
+ </custom-element>
77
+
78
+ <custom-element name="ui-message-footer" slot="footer">
79
+ <style>
80
+ :host,
81
+ * {
82
+ touch-action: manipulation;
83
+ }
84
+ :host {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: calc(var(--spacing) * 2);
88
+ padding: 0 calc(var(--spacing) * 3);
89
+ font-size: var(--text-xs);
90
+ color: var(--muted-foreground);
91
+ }
92
+ </style>
93
+
94
+ <slot></slot>
95
+ </custom-element>
@@ -0,0 +1,233 @@
1
+ <custom-element name="ui-navigation-menu">
2
+ <script>
3
+ defineProps({ label: prop.string().default("Main") });
4
+ </script>
5
+
6
+ <style>
7
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
8
+ touch-action does not reach shadow content in iOS Safari. */
9
+ :host,
10
+ * {
11
+ touch-action: manipulation;
12
+ }
13
+ :host {
14
+ display: block;
15
+ }
16
+ </style>
17
+
18
+ <nav _aria-label="@label"><slot></slot></nav>
19
+ </custom-element>
20
+
21
+ <custom-element name="ui-navigation-menu-list">
22
+ <style>
23
+ :host,
24
+ * {
25
+ touch-action: manipulation;
26
+ }
27
+ :host {
28
+ display: block;
29
+ }
30
+ ul {
31
+ display: flex;
32
+ align-items: center;
33
+ gap: calc(var(--spacing) * 1);
34
+ margin: 0;
35
+ padding: 0;
36
+ list-style: none;
37
+ }
38
+ </style>
39
+
40
+ <ul part="list" focusgroup="toolbar inline"><slot></slot></ul>
41
+ </custom-element>
42
+
43
+ <custom-element name="ui-navigation-menu-item" role="listitem">
44
+ <script>
45
+ const menu = useState({ open: false });
46
+
47
+ let restoreFocus = false;
48
+ let timer;
49
+
50
+ function schedule(open) {
51
+ clearTimeout(timer);
52
+ timer = setTimeout(() => refs.panel.togglePopover(open), open ? 150 : 200);
53
+ }
54
+
55
+ function toggle() {
56
+ refs.panel.togglePopover();
57
+ }
58
+
59
+ function closing(event) {
60
+ restoreFocus = event.newState === "closed" && host.contains(host.getRootNode().activeElement);
61
+ }
62
+
63
+ function toggled(event) {
64
+ menu.open = event.newState === "open";
65
+ if (!menu.open && restoreFocus) host.querySelector("ui-navigation-menu-trigger")?.focus();
66
+ }
67
+ </script>
68
+
69
+ <style>
70
+ :host,
71
+ * {
72
+ touch-action: manipulation;
73
+ }
74
+ :host {
75
+ position: relative;
76
+ display: block;
77
+ }
78
+ .anchor {
79
+ display: flex;
80
+ anchor-name: --navigation-trigger;
81
+ }
82
+ .panel {
83
+ box-sizing: border-box;
84
+ min-width: calc(var(--spacing) * 64);
85
+ margin: calc(var(--spacing) * 1.5) 0 0;
86
+ padding: calc(var(--spacing) * 2);
87
+ border: 1px solid var(--border);
88
+ border-radius: var(--radius-md);
89
+ background: var(--popover);
90
+ color: var(--popover-foreground);
91
+ box-shadow: var(--shadow-md);
92
+ inset: auto;
93
+ position-anchor: --navigation-trigger;
94
+ height: fit-content;
95
+ max-height: calc(100svh - calc(var(--spacing) * 8));
96
+ overflow-y: auto;
97
+ position-area: bottom span-right;
98
+ }
99
+ </style>
100
+
101
+ <div class="anchor"><slot></slot></div>
102
+ <div ref="panel" class="panel" part="panel" popover="auto" onbeforetoggle="closing(event)" ontoggle="toggled(event)" onpointerenter="schedule(true)" onpointerleave="schedule(false)"><slot name="content"></slot></div>
103
+ </custom-element>
104
+
105
+ <custom-element name="ui-navigation-menu-trigger" delegates-focus>
106
+ <style>
107
+ :host,
108
+ * {
109
+ touch-action: manipulation;
110
+ }
111
+ :host {
112
+ display: inline-flex;
113
+ }
114
+ button {
115
+ all: unset;
116
+ display: inline-flex;
117
+ align-items: center;
118
+ gap: calc(var(--spacing) * 1);
119
+ height: calc(var(--spacing) * 9);
120
+ padding: 0 calc(var(--spacing) * 4);
121
+ border-radius: var(--radius-md);
122
+ font-size: var(--text-sm);
123
+ font-weight: var(--font-weight-medium);
124
+ cursor: pointer;
125
+ }
126
+ button:hover,
127
+ button:focus-visible,
128
+ button[aria-expanded="true"] {
129
+ background: var(--accent);
130
+ color: var(--accent-foreground);
131
+ }
132
+ button:focus-visible {
133
+ box-shadow: var(--focus-ring);
134
+ }
135
+ svg {
136
+ width: calc(var(--spacing) * 3);
137
+ height: calc(var(--spacing) * 3);
138
+ transition: rotate var(--default-transition-duration);
139
+ }
140
+ [aria-expanded="true"] svg {
141
+ rotate: 180deg;
142
+ }
143
+ </style>
144
+
145
+ <button type="button" part="trigger" _aria-expanded="#menu.open" onclick="toggle()" onpointerenter="schedule(true)" onpointerleave="schedule(false)">
146
+ <slot></slot>
147
+ <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></svg>
148
+ </button>
149
+ </custom-element>
150
+
151
+ <custom-element name="ui-navigation-menu-content" slot="content">
152
+ <style>
153
+ :host,
154
+ * {
155
+ touch-action: manipulation;
156
+ }
157
+ :host {
158
+ display: grid;
159
+ gap: calc(var(--spacing) * 1);
160
+ --ui-navigation-link-display: flex;
161
+ --ui-navigation-link-direction: column;
162
+ --ui-navigation-link-align: stretch;
163
+ --ui-navigation-link-height: auto;
164
+ --ui-navigation-link-padding: calc(var(--spacing) * 2);
165
+ --ui-navigation-link-weight: var(--font-weight-normal);
166
+ }
167
+ :host(:state(attr-width)) {
168
+ width: calc(var(--spacing) * var(--attr-width));
169
+ }
170
+ :host(:state(attr-columns)) {
171
+ grid-template-columns: repeat(var(--attr-columns), minmax(0, 1fr));
172
+ }
173
+ </style>
174
+
175
+ <slot></slot>
176
+ </custom-element>
177
+
178
+ <custom-element name="ui-navigation-menu-link" delegates-focus>
179
+ <script>
180
+ const props = defineProps({ href: prop.string(), current: prop.boolean() });
181
+ const link = useState({
182
+ get current() {
183
+ return props.current ? "page" : null;
184
+ },
185
+ });
186
+ </script>
187
+
188
+ <style>
189
+ :host,
190
+ * {
191
+ touch-action: manipulation;
192
+ }
193
+ :host {
194
+ display: var(--ui-navigation-link-display, inline-flex);
195
+ }
196
+ a {
197
+ display: flex;
198
+ flex: 1;
199
+ flex-direction: var(--ui-navigation-link-direction, row);
200
+ align-items: var(--ui-navigation-link-align, center);
201
+ gap: calc(var(--spacing) * 1);
202
+ height: var(--ui-navigation-link-height, calc(var(--spacing) * 9));
203
+ padding: var(--ui-navigation-link-padding, 0 calc(var(--spacing) * 4));
204
+ border-radius: var(--radius-md);
205
+ color: inherit;
206
+ font-size: var(--text-sm);
207
+ font-weight: var(--ui-navigation-link-weight, var(--font-weight-medium));
208
+ text-decoration: none;
209
+ outline: none;
210
+ }
211
+ a:hover,
212
+ a:focus-visible,
213
+ a[aria-current="page"] {
214
+ background: var(--accent);
215
+ color: var(--accent-foreground);
216
+ }
217
+ a:focus-visible {
218
+ box-shadow: var(--focus-ring);
219
+ }
220
+ ::slotted(*) {
221
+ margin: 0;
222
+ }
223
+ ::slotted(:first-child:not(:last-child)) {
224
+ font-weight: var(--font-weight-medium);
225
+ }
226
+ ::slotted(:last-child:not(:first-child)) {
227
+ color: var(--muted-foreground);
228
+ line-height: 1.4;
229
+ }
230
+ </style>
231
+
232
+ <a part="link" _href="@href" _aria-current="#link.current"><slot></slot></a>
233
+ </custom-element>