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

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 +121 -0
  4. package/components/alert-dialog.html +196 -0
  5. package/components/alert.html +69 -0
  6. package/components/aspect-ratio.html +23 -0
  7. package/components/attachment.html +84 -0
  8. package/components/avatar.html +127 -0
  9. package/components/badge.html +56 -0
  10. package/components/breadcrumb.html +129 -0
  11. package/components/bubble.html +67 -0
  12. package/components/button-group.html +84 -0
  13. package/components/button.html +188 -0
  14. package/components/calendar.html +170 -0
  15. package/components/card.html +113 -0
  16. package/components/carousel.html +186 -0
  17. package/components/chart.html +162 -0
  18. package/components/checkbox.html +93 -0
  19. package/components/code-block.html +58 -0
  20. package/components/collapsible.html +76 -0
  21. package/components/combobox.html +224 -0
  22. package/components/command.html +189 -0
  23. package/components/context-menu.html +67 -0
  24. package/components/dialog.html +281 -0
  25. package/components/direction.html +17 -0
  26. package/components/drawer.html +224 -0
  27. package/components/dropdown-menu.html +372 -0
  28. package/components/empty.html +106 -0
  29. package/components/field.html +117 -0
  30. package/components/hover-card.html +94 -0
  31. package/components/input-group.html +75 -0
  32. package/components/input-otp.html +139 -0
  33. package/components/input.html +82 -0
  34. package/components/item.html +170 -0
  35. package/components/kbd.html +35 -0
  36. package/components/label.html +40 -0
  37. package/components/marker.html +35 -0
  38. package/components/menubar.html +116 -0
  39. package/components/message-scroller.html +62 -0
  40. package/components/message.html +77 -0
  41. package/components/navigation-menu.html +204 -0
  42. package/components/pagination.html +154 -0
  43. package/components/popover.html +133 -0
  44. package/components/progress.html +39 -0
  45. package/components/radio-group.html +114 -0
  46. package/components/resizable.html +156 -0
  47. package/components/scroll-area.html +50 -0
  48. package/components/select.html +91 -0
  49. package/components/separator.html +25 -0
  50. package/components/sheet.html +143 -0
  51. package/components/sidebar.html +343 -0
  52. package/components/skeleton.html +30 -0
  53. package/components/slider.html +81 -0
  54. package/components/spinner.html +47 -0
  55. package/components/switch.html +89 -0
  56. package/components/table.html +75 -0
  57. package/components/tabs.html +164 -0
  58. package/components/textarea.html +68 -0
  59. package/components/toaster.html +128 -0
  60. package/components/toggle-group.html +65 -0
  61. package/components/toggle.html +81 -0
  62. package/components/tooltip.html +97 -0
  63. package/dist/theme.css +145 -0
  64. package/dist/ui.html +6944 -0
  65. package/package.json +40 -3
  66. package/theme.css +145 -0
@@ -0,0 +1,114 @@
1
+ <custom-element name="ui-radio-group" form-associated>
2
+ <script>
3
+ const props = defineProps({
4
+ value: prop.string(),
5
+ orientation: prop.enum(["vertical", "horizontal"]).default("vertical"),
6
+ disabled: prop.boolean(),
7
+ required: prop.boolean(),
8
+ });
9
+
10
+ const options = useChildren("option", ["value", "label", "disabled"]);
11
+ const group = useState({
12
+ get options() {
13
+ return options.map((option, index) => ({
14
+ id: `${index}:${option.value}`,
15
+ value: option.value,
16
+ label: option.label,
17
+ disabled: props.disabled || option.disabled,
18
+ checked: option.value === props.value,
19
+ }));
20
+ },
21
+ });
22
+
23
+ function choose(option) {
24
+ props.value = option.value;
25
+ dispatch("change");
26
+ }
27
+
28
+ onConnected(() => {
29
+ internals.role = "radiogroup";
30
+ });
31
+
32
+ observe(() => {
33
+ const missing = props.required && !props.value;
34
+ const anchor = refs.list?.querySelector("input:not(:disabled)") ?? undefined;
35
+ internals.setValidity(missing ? { valueMissing: true } : {}, missing ? "Please select an option." : "", anchor);
36
+ });
37
+ </script>
38
+
39
+ <style>
40
+ :host {
41
+ display: grid;
42
+ gap: calc(var(--spacing) * 3);
43
+ }
44
+ :host(:state(attr-orientation-is-horizontal)) {
45
+ grid-auto-flow: column;
46
+ justify-content: start;
47
+ }
48
+ label {
49
+ display: inline-flex;
50
+ align-items: center;
51
+ gap: calc(var(--spacing) * 2);
52
+ font-size: var(--text-sm);
53
+ font-weight: var(--font-weight-medium);
54
+ cursor: pointer;
55
+ }
56
+ input {
57
+ appearance: none;
58
+ display: grid;
59
+ place-content: center;
60
+ width: calc(var(--spacing) * 4);
61
+ height: calc(var(--spacing) * 4);
62
+ margin: 0;
63
+ border: 1px solid var(--input);
64
+ border-radius: var(--radius-full);
65
+ box-shadow: var(--shadow-xs);
66
+ cursor: pointer;
67
+ outline: none;
68
+ }
69
+ input::before {
70
+ content: "";
71
+ width: calc(var(--spacing) * 2);
72
+ height: calc(var(--spacing) * 2);
73
+ border-radius: var(--radius-full);
74
+ background: var(--primary);
75
+ transform: scale(0);
76
+ transition: transform 0.1s;
77
+ }
78
+ input:checked::before {
79
+ transform: scale(1);
80
+ }
81
+ input:focus-visible {
82
+ border-color: var(--ring);
83
+ box-shadow: var(--focus-ring);
84
+ }
85
+ input:disabled,
86
+ input:disabled + span {
87
+ cursor: not-allowed;
88
+ opacity: 0.5;
89
+ }
90
+ .list {
91
+ display: contents;
92
+ }
93
+ .options {
94
+ display: none;
95
+ }
96
+ </style>
97
+
98
+ <div class="list" ref="list">
99
+ <repeat-list of="#group.options" as="option" key="id">
100
+ <label part="item">
101
+ <input
102
+ type="radio"
103
+ name="group"
104
+ part="control"
105
+ _value="#option.value"
106
+ _checked="#option.checked"
107
+ _disabled="#option.disabled"
108
+ onchange="choose(option)">
109
+ <span><slot name="#option.label"></slot></span>
110
+ </label>
111
+ </repeat-list>
112
+ </div>
113
+ <div class="options"><slot></slot></div>
114
+ </custom-element>
@@ -0,0 +1,156 @@
1
+ <custom-element name="ui-resizable">
2
+ <script>
3
+ defineProps({
4
+ direction: prop.enum(["horizontal", "vertical"]).default("horizontal"),
5
+ variant: prop.enum(["default", "outline"]).default("default"),
6
+ });
7
+ </script>
8
+
9
+ <style>
10
+ :host {
11
+ display: flex;
12
+ width: 100%;
13
+ height: 100%;
14
+ overflow: hidden;
15
+ }
16
+ :host(:state(attr-height)) {
17
+ height: calc(var(--spacing) * var(--attr-height));
18
+ }
19
+ :host(:state(attr-variant-is-outline)) {
20
+ box-sizing: border-box;
21
+ border: 1px solid var(--border);
22
+ border-radius: var(--radius-lg);
23
+ }
24
+ :host(:state(attr-direction-is-vertical)) {
25
+ flex-direction: column;
26
+ }
27
+ </style>
28
+
29
+ <slot></slot>
30
+ </custom-element>
31
+
32
+ <custom-element name="ui-resizable-panel">
33
+ <script>
34
+ defineProps({ size: prop.number().min(0).max(100).default(50), min: prop.number().min(0).default(10) });
35
+ </script>
36
+
37
+ <style>
38
+ :host {
39
+ display: block;
40
+ flex: var(--attr-size) 1 0;
41
+ min-width: 0;
42
+ min-height: 0;
43
+ overflow: auto;
44
+ }
45
+ </style>
46
+
47
+ <slot></slot>
48
+ </custom-element>
49
+
50
+ <custom-element name="ui-resizable-handle" role="separator">
51
+ <script>
52
+ const props = defineProps({ handle: prop.boolean() });
53
+
54
+ const drag = useState({ active: false });
55
+ const KEYS = { ArrowLeft: -5, ArrowUp: -5, ArrowRight: 5, ArrowDown: 5 };
56
+
57
+ function vertical() {
58
+ return host.parentElement?.getAttribute("direction") === "vertical";
59
+ }
60
+
61
+ function resize(delta) {
62
+ const before = host.previousElementSibling;
63
+ const after = host.nextElementSibling;
64
+ if (!before || !after) return;
65
+ const total = before.size + after.size;
66
+ const size = Math.min(Math.max(before.size + delta, before.min), total - after.min);
67
+ before.size = size;
68
+ after.size = total - size;
69
+ internals.ariaValueNow = String(Math.round(size));
70
+ }
71
+
72
+ function start(event) {
73
+ drag.active = true;
74
+ if (event.isTrusted) host.setPointerCapture(event.pointerId);
75
+ }
76
+
77
+ function move(event) {
78
+ if (!drag.active) return;
79
+ const parent = host.parentElement.getBoundingClientRect();
80
+ const length = vertical() ? parent.height : parent.width;
81
+ const pixels = vertical() ? event.movementY : event.movementX;
82
+ const before = host.previousElementSibling;
83
+ const after = host.nextElementSibling;
84
+ resize(((before.size + after.size) * pixels) / length);
85
+ }
86
+
87
+ function stop() {
88
+ drag.active = false;
89
+ }
90
+
91
+ function keys(event) {
92
+ const step = KEYS[event.key];
93
+ if (!step) return;
94
+ event.preventDefault();
95
+ resize(step);
96
+ }
97
+
98
+ attrs.tabindex ??= 0;
99
+ listen("pointerdown", start);
100
+ listen("pointermove", move);
101
+ listen("pointerup", stop);
102
+ listen("keydown", keys);
103
+
104
+ onConnected(() => {
105
+ if (vertical()) internals.states.add("vertical");
106
+ internals.ariaOrientation = vertical() ? "horizontal" : "vertical";
107
+ internals.ariaValueMin = "0";
108
+ internals.ariaValueMax = "100";
109
+ });
110
+ </script>
111
+
112
+ <style>
113
+ :host {
114
+ position: relative;
115
+ display: flex;
116
+ flex-shrink: 0;
117
+ align-items: center;
118
+ justify-content: center;
119
+ width: 1px;
120
+ background: var(--border);
121
+ cursor: col-resize;
122
+ outline: none;
123
+ touch-action: none;
124
+ }
125
+ :host(:state(vertical)) {
126
+ width: auto;
127
+ height: 1px;
128
+ cursor: row-resize;
129
+ }
130
+ :host(:state(vertical))::after {
131
+ inset: calc(var(--spacing) * -1) 0;
132
+ }
133
+ :host::after {
134
+ content: "";
135
+ position: absolute;
136
+ inset: 0 calc(var(--spacing) * -1);
137
+ }
138
+ :host(:focus-visible) {
139
+ box-shadow: 0 0 0 1px var(--ring);
140
+ }
141
+ .grip {
142
+ display: none;
143
+ z-index: 1;
144
+ width: calc(var(--spacing) * 3);
145
+ height: calc(var(--spacing) * 4);
146
+ border: 1px solid var(--border);
147
+ border-radius: var(--radius-xs);
148
+ background: var(--border);
149
+ }
150
+ :host(:state(attr-handle)) .grip {
151
+ display: block;
152
+ }
153
+ </style>
154
+
155
+ <div class="grip" part="grip"></div>
156
+ </custom-element>
@@ -0,0 +1,50 @@
1
+ <custom-element name="ui-scroll-area">
2
+ <script>
3
+ defineProps({
4
+ orientation: prop.enum(["vertical", "horizontal", "both"]).default("vertical"),
5
+ variant: prop.enum(["default", "outline"]).default("default"),
6
+ });
7
+ </script>
8
+
9
+ <style>
10
+ :host {
11
+ display: block;
12
+ position: relative;
13
+ box-sizing: border-box;
14
+ overflow: hidden;
15
+ border-radius: inherit;
16
+ }
17
+ :host(:state(attr-width)) {
18
+ width: calc(var(--spacing) * var(--attr-width));
19
+ }
20
+ :host(:state(attr-height)) {
21
+ height: calc(var(--spacing) * var(--attr-height));
22
+ }
23
+ :host(:state(attr-variant-is-outline)) {
24
+ border: 1px solid var(--border);
25
+ border-radius: var(--radius-md);
26
+ }
27
+ .viewport {
28
+ width: 100%;
29
+ height: 100%;
30
+ overflow-y: auto;
31
+ overflow-x: hidden;
32
+ scrollbar-width: thin;
33
+ scrollbar-color: var(--border) transparent;
34
+ border-radius: inherit;
35
+ outline: none;
36
+ }
37
+ :host(:state(attr-orientation-is-horizontal)) .viewport {
38
+ overflow-x: auto;
39
+ overflow-y: hidden;
40
+ }
41
+ :host(:state(attr-orientation-is-both)) .viewport {
42
+ overflow: auto;
43
+ }
44
+ .viewport:focus-visible {
45
+ box-shadow: var(--focus-ring);
46
+ }
47
+ </style>
48
+
49
+ <div class="viewport" part="viewport" tabindex="0"><slot></slot></div>
50
+ </custom-element>
@@ -0,0 +1,91 @@
1
+ <custom-element name="ui-select" form-associated delegates-focus>
2
+ <script>
3
+ const props = defineProps({
4
+ value: prop.string().default(""),
5
+ placeholder: prop.string(),
6
+ size: prop.enum(["default", "sm"]).default("default"),
7
+ disabled: prop.boolean(),
8
+ required: prop.boolean(),
9
+ label: prop.string().attribute("aria-label"),
10
+ });
11
+
12
+ const options = useChildren("option", ["value", "label", "disabled", "selected"]);
13
+ const menu = useState({
14
+ get empty() {
15
+ return !props.value;
16
+ },
17
+ get options() {
18
+ return options.map((option, index) => ({
19
+ id: `${index}:${option.value}`,
20
+ value: option.value,
21
+ label: option.label,
22
+ disabled: option.disabled,
23
+ selected: option.value === props.value,
24
+ }));
25
+ },
26
+ });
27
+
28
+ observe(() => {
29
+ if (props.placeholder || !options.length || menu.options.some((option) => option.selected)) return;
30
+ props.value = (options.find((option) => option.hasAttribute("selected")) ?? options[0]).value;
31
+ });
32
+ </script>
33
+
34
+ <style>
35
+ :host {
36
+ display: block;
37
+ min-width: calc(var(--spacing) * 32);
38
+ }
39
+ :host(:state(attr-width)) {
40
+ width: calc(var(--spacing) * var(--attr-width));
41
+ }
42
+ select {
43
+ box-sizing: border-box;
44
+ width: 100%;
45
+ height: calc(var(--spacing) * 9);
46
+ padding: 0 calc(var(--spacing) * 8) 0 calc(var(--spacing) * 3);
47
+ border: 1px solid var(--input);
48
+ border-radius: var(--radius-md);
49
+ background: transparent
50
+ url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' stroke='%23888' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E")
51
+ no-repeat right calc(var(--spacing) * 2) center;
52
+ color: inherit;
53
+ font: inherit;
54
+ font-size: var(--text-sm);
55
+ box-shadow: var(--shadow-xs);
56
+ appearance: none;
57
+ outline: none;
58
+ cursor: pointer;
59
+ }
60
+ :host(:state(attr-size-is-sm)) select {
61
+ height: calc(var(--spacing) * 8);
62
+ }
63
+ select:focus-visible {
64
+ border-color: var(--ring);
65
+ box-shadow: var(--focus-ring);
66
+ }
67
+ select:disabled {
68
+ cursor: not-allowed;
69
+ opacity: 0.5;
70
+ }
71
+ option {
72
+ background: var(--popover);
73
+ color: var(--popover-foreground);
74
+ }
75
+ .source {
76
+ display: none;
77
+ }
78
+ </style>
79
+
80
+ <select
81
+ ref="control"
82
+ _aria-label="@label"
83
+ part="control"
84
+ _disabled="@disabled"
85
+ _required="@required"
86
+ >
87
+ <option value="" disabled _hidden="!@placeholder" _label="@placeholder" _selected="#menu.empty"></option>
88
+ <option repeat="#menu.options" as="option" key="id" _value="#option.value" _label="#option.label" _selected="#option.selected" _disabled="#option.disabled"></option>
89
+ </select>
90
+ <div class="source"><slot></slot></div>
91
+ </custom-element>
@@ -0,0 +1,25 @@
1
+ <custom-element name="ui-separator" role="separator">
2
+ <script>
3
+ const props = defineProps({
4
+ orientation: prop.enum(["horizontal", "vertical"]).default("horizontal"),
5
+ });
6
+ observe(() => {
7
+ internals.ariaOrientation = props.orientation;
8
+ });
9
+ </script>
10
+
11
+ <style>
12
+ :host {
13
+ display: block;
14
+ flex-shrink: 0;
15
+ height: 1px;
16
+ width: 100%;
17
+ background: var(--border);
18
+ }
19
+ :host(:state(attr-orientation-is-vertical)) {
20
+ height: auto;
21
+ width: 1px;
22
+ align-self: stretch;
23
+ }
24
+ </style>
25
+ </custom-element>
@@ -0,0 +1,143 @@
1
+ <import-element from="./dialog.html"></import-element>
2
+
3
+ <custom-element name="ui-sheet">
4
+ <script>
5
+ const props = defineProps({
6
+ open: prop.boolean().reflect(),
7
+ side: prop.enum(["top", "right", "bottom", "left"]).default("right"),
8
+ });
9
+
10
+ const parts = useChildren("ui-sheet-header, ui-sheet-title, ui-sheet-description");
11
+ const sheet = useState({
12
+ get label() {
13
+ return text("ui-sheet-title");
14
+ },
15
+ get description() {
16
+ return text("ui-sheet-description");
17
+ },
18
+ });
19
+
20
+ function text(selector) {
21
+ const found = parts.flatMap((part) => (part.matches(selector) ? [part] : [...part.querySelectorAll(selector)]));
22
+ return found.map((part) => part.textContent.trim()).join(" ") || null;
23
+ }
24
+
25
+ function show() {
26
+ props.open = true;
27
+ }
28
+
29
+ function showFrom(trigger) {
30
+ trigger.firstElementChild?.focus();
31
+ show();
32
+ }
33
+
34
+ function close() {
35
+ props.open = false;
36
+ }
37
+
38
+ function requestClose() {
39
+ refs.dialog.requestClose();
40
+ }
41
+
42
+ function closed() {
43
+ props.open = false;
44
+ dispatch("close", undefined, { bubbles: false, composed: false });
45
+ }
46
+
47
+ const COMMANDS = { "show-modal": show, close, "request-close": requestClose };
48
+
49
+ defineMethods({ showModal: show, show, close, requestClose });
50
+
51
+ listen("command", (event) => COMMANDS[event.command.replace(/^--/, "")]?.());
52
+ </script>
53
+
54
+ <style>
55
+ :host {
56
+ display: contents;
57
+ }
58
+ </style>
59
+
60
+ <slot name="trigger"></slot>
61
+ <ui-dialog ref="dialog" part="dialog" _side="@side" _open="@open" _label="#sheet.label" _description="#sheet.description" onclose="closed()">
62
+ <slot name="header" slot="header"></slot>
63
+ <slot></slot>
64
+ <slot name="footer" slot="footer"></slot>
65
+ </ui-dialog>
66
+ </custom-element>
67
+
68
+ <custom-element name="ui-sheet-trigger" slot="trigger">
69
+ <style>
70
+ :host,
71
+ span {
72
+ display: contents;
73
+ }
74
+ </style>
75
+
76
+ <span part="trigger" onclick="showFrom(host)"><slot></slot></span>
77
+ </custom-element>
78
+
79
+ <custom-element name="ui-sheet-close">
80
+ <style>
81
+ :host,
82
+ span {
83
+ display: contents;
84
+ }
85
+ </style>
86
+
87
+ <span part="close" onclick="close()"><slot></slot></span>
88
+ </custom-element>
89
+
90
+ <custom-element name="ui-sheet-header" slot="header">
91
+ <style>
92
+ :host {
93
+ display: grid;
94
+ gap: calc(var(--spacing) * 2);
95
+ }
96
+ </style>
97
+
98
+ <slot></slot>
99
+ </custom-element>
100
+
101
+ <custom-element name="ui-sheet-title">
102
+ <style>
103
+ :host {
104
+ display: block;
105
+ }
106
+ h2 {
107
+ margin: 0;
108
+ font-size: var(--text-lg);
109
+ font-weight: var(--font-weight-semibold);
110
+ line-height: 1;
111
+ }
112
+ </style>
113
+
114
+ <h2 part="title"><slot></slot></h2>
115
+ </custom-element>
116
+
117
+ <custom-element name="ui-sheet-description">
118
+ <style>
119
+ :host {
120
+ display: block;
121
+ }
122
+ p {
123
+ margin: 0;
124
+ font-size: var(--text-sm);
125
+ color: var(--muted-foreground);
126
+ }
127
+ </style>
128
+
129
+ <p part="description"><slot></slot></p>
130
+ </custom-element>
131
+
132
+ <custom-element name="ui-sheet-footer" slot="footer">
133
+ <style>
134
+ :host {
135
+ display: flex;
136
+ flex-wrap: wrap;
137
+ justify-content: flex-end;
138
+ gap: calc(var(--spacing) * 2);
139
+ }
140
+ </style>
141
+
142
+ <slot></slot>
143
+ </custom-element>