@arclux/arc-ui 1.0.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 (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -0
  3. package/package.json +186 -0
  4. package/src/content/accordion-item.js +27 -0
  5. package/src/content/accordion.js +151 -0
  6. package/src/content/animated-number.js +160 -0
  7. package/src/content/aspect-ratio.js +78 -0
  8. package/src/content/avatar-group.js +101 -0
  9. package/src/content/avatar.js +92 -0
  10. package/src/content/badge.js +98 -0
  11. package/src/content/callout.js +141 -0
  12. package/src/content/card.js +75 -0
  13. package/src/content/carousel.js +300 -0
  14. package/src/content/code-block.js +152 -0
  15. package/src/content/collapsible.js +142 -0
  16. package/src/content/color-swatch.js +86 -0
  17. package/src/content/column.js +28 -0
  18. package/src/content/data-table.js +332 -0
  19. package/src/content/divider.js +153 -0
  20. package/src/content/empty-state.js +78 -0
  21. package/src/content/feature-card.js +142 -0
  22. package/src/content/highlight.js +63 -0
  23. package/src/content/icon-library.js +30 -0
  24. package/src/content/icon-registry.js +39 -0
  25. package/src/content/icon.js +95 -0
  26. package/src/content/index.js +44 -0
  27. package/src/content/infinite-scroll.js +144 -0
  28. package/src/content/kbd.js +40 -0
  29. package/src/content/markdown.js +294 -0
  30. package/src/content/marquee.js +166 -0
  31. package/src/content/meter.js +167 -0
  32. package/src/content/scroll-area.js +107 -0
  33. package/src/content/skeleton.js +85 -0
  34. package/src/content/spinner.js +77 -0
  35. package/src/content/stack.js +68 -0
  36. package/src/content/stat.js +72 -0
  37. package/src/content/step.js +22 -0
  38. package/src/content/stepper.js +202 -0
  39. package/src/content/table.js +134 -0
  40. package/src/content/tag.js +156 -0
  41. package/src/content/text.js +111 -0
  42. package/src/content/timeline-item.js +29 -0
  43. package/src/content/timeline.js +170 -0
  44. package/src/content/truncate.js +161 -0
  45. package/src/content/value-card.js +94 -0
  46. package/src/feedback/alert.js +187 -0
  47. package/src/feedback/command-item.js +28 -0
  48. package/src/feedback/command-palette.js +346 -0
  49. package/src/feedback/context-menu.js +299 -0
  50. package/src/feedback/dialog.js +298 -0
  51. package/src/feedback/dropdown-menu.js +259 -0
  52. package/src/feedback/hover-card.js +186 -0
  53. package/src/feedback/index.js +17 -0
  54. package/src/feedback/modal.js +226 -0
  55. package/src/feedback/notification-panel.js +196 -0
  56. package/src/feedback/popover.js +184 -0
  57. package/src/feedback/progress.js +169 -0
  58. package/src/feedback/sheet.js +249 -0
  59. package/src/feedback/toast.js +207 -0
  60. package/src/feedback/tooltip.js +189 -0
  61. package/src/icons/lucide.d.ts +1915 -0
  62. package/src/icons/lucide.js +1915 -0
  63. package/src/icons/phosphor.d.ts +1517 -0
  64. package/src/icons/phosphor.js +1517 -0
  65. package/src/icons/types.d.ts +8 -0
  66. package/src/index.js +9 -0
  67. package/src/input/button.js +127 -0
  68. package/src/input/calendar.js +340 -0
  69. package/src/input/checkbox.js +159 -0
  70. package/src/input/chip.js +120 -0
  71. package/src/input/color-picker.js +461 -0
  72. package/src/input/combobox.js +295 -0
  73. package/src/input/copy-button.js +144 -0
  74. package/src/input/date-picker.js +534 -0
  75. package/src/input/file-upload.js +333 -0
  76. package/src/input/form.js +179 -0
  77. package/src/input/icon-button.js +179 -0
  78. package/src/input/index.js +31 -0
  79. package/src/input/input.js +158 -0
  80. package/src/input/multi-select.js +392 -0
  81. package/src/input/number-input.js +239 -0
  82. package/src/input/otp-input.js +221 -0
  83. package/src/input/pin-input.js +294 -0
  84. package/src/input/radio-group.js +177 -0
  85. package/src/input/radio.js +28 -0
  86. package/src/input/rating.js +195 -0
  87. package/src/input/search.js +371 -0
  88. package/src/input/segmented-control.js +174 -0
  89. package/src/input/select.js +267 -0
  90. package/src/input/slider.js +217 -0
  91. package/src/input/sortable-list.js +345 -0
  92. package/src/input/suggestion.js +26 -0
  93. package/src/input/textarea.js +203 -0
  94. package/src/input/theme-toggle.js +196 -0
  95. package/src/input/toggle.js +166 -0
  96. package/src/layout/app-shell.js +266 -0
  97. package/src/layout/auth-shell.js +153 -0
  98. package/src/layout/container.js +37 -0
  99. package/src/layout/dashboard-grid.js +62 -0
  100. package/src/layout/index.js +15 -0
  101. package/src/layout/page-header.js +100 -0
  102. package/src/layout/page-layout.js +112 -0
  103. package/src/layout/resizable.js +221 -0
  104. package/src/layout/section.js +54 -0
  105. package/src/layout/settings-layout.js +91 -0
  106. package/src/layout/split-pane.js +172 -0
  107. package/src/layout/status-bar.js +84 -0
  108. package/src/layout/toolbar.js +92 -0
  109. package/src/navigation/breadcrumb-item.js +26 -0
  110. package/src/navigation/breadcrumb.js +129 -0
  111. package/src/navigation/drawer.js +183 -0
  112. package/src/navigation/footer.js +99 -0
  113. package/src/navigation/index.js +22 -0
  114. package/src/navigation/link.js +120 -0
  115. package/src/navigation/nav-item.js +46 -0
  116. package/src/navigation/navigation-menu.js +687 -0
  117. package/src/navigation/pagination.js +186 -0
  118. package/src/navigation/scroll-spy.js +198 -0
  119. package/src/navigation/scroll-to-top.js +163 -0
  120. package/src/navigation/sidebar-link.js +28 -0
  121. package/src/navigation/sidebar-section.js +45 -0
  122. package/src/navigation/sidebar.js +336 -0
  123. package/src/navigation/spy-link.js +26 -0
  124. package/src/navigation/tab.js +26 -0
  125. package/src/navigation/tabs.js +202 -0
  126. package/src/navigation/top-bar.js +263 -0
  127. package/src/navigation/tree-item.js +38 -0
  128. package/src/navigation/tree-view.js +255 -0
  129. package/src/shared/index.js +6 -0
  130. package/src/shared/menu-divider.js +9 -0
  131. package/src/shared/menu-item.js +30 -0
  132. package/src/shared/option.js +31 -0
  133. package/src/shared-styles.js +81 -0
  134. package/src/tokens.js +445 -0
  135. package/types/index.d.ts +973 -0
@@ -0,0 +1,239 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { tokenStyles } from '../shared-styles.js';
3
+
4
+ let numberInputIdCounter = 0;
5
+
6
+ export class ArcNumberInput extends LitElement {
7
+ static properties = {
8
+ value: { type: Number, reflect: true },
9
+ min: { type: Number },
10
+ max: { type: Number },
11
+ step: { type: Number },
12
+ label: { type: String },
13
+ disabled: { type: Boolean, reflect: true },
14
+ };
15
+
16
+ static styles = [
17
+ tokenStyles,
18
+ css`
19
+ :host { display: block; }
20
+ :host([disabled]) { pointer-events: none; opacity: 0.4; }
21
+
22
+ .number-input {
23
+ display: flex;
24
+ flex-direction: column;
25
+ gap: var(--space-xs);
26
+ }
27
+
28
+ .number-input__label {
29
+ font-family: var(--font-accent);
30
+ font-weight: 600;
31
+ font-size: var(--label-inline-size);
32
+ letter-spacing: var(--label-inline-spacing);
33
+ text-transform: uppercase;
34
+ color: var(--text-muted);
35
+ }
36
+
37
+ .number-input__controls {
38
+ display: inline-flex;
39
+ align-items: center;
40
+ border: 1px solid var(--border-default);
41
+ border-radius: var(--radius-md);
42
+ background: var(--bg-surface);
43
+ overflow: hidden;
44
+ transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
45
+ }
46
+
47
+ .number-input__controls:focus-within {
48
+ border-color: rgba(var(--accent-primary-rgb), 0.4);
49
+ box-shadow: var(--focus-glow);
50
+ }
51
+
52
+ .number-input__btn {
53
+ display: inline-flex;
54
+ align-items: center;
55
+ justify-content: center;
56
+ width: 36px;
57
+ height: 36px;
58
+ padding: 0;
59
+ background: transparent;
60
+ border: none;
61
+ color: var(--text-muted);
62
+ cursor: pointer;
63
+ font-size: var(--text-md);
64
+ font-family: var(--font-body);
65
+ font-weight: 600;
66
+ line-height: 1;
67
+ transition:
68
+ background var(--transition-fast),
69
+ color var(--transition-fast);
70
+ flex-shrink: 0;
71
+ }
72
+
73
+ .number-input__btn:hover:not(:disabled) {
74
+ background: var(--bg-hover);
75
+ color: var(--text-primary);
76
+ }
77
+
78
+ .number-input__btn:active:not(:disabled) {
79
+ background: var(--bg-elevated);
80
+ }
81
+
82
+ .number-input__btn:disabled {
83
+ color: var(--text-ghost);
84
+ cursor: not-allowed;
85
+ }
86
+
87
+ .number-input__btn:focus-visible {
88
+ outline: none;
89
+ box-shadow: var(--focus-glow);
90
+ z-index: 1;
91
+ position: relative;
92
+ }
93
+
94
+ .number-input__field {
95
+ width: 56px;
96
+ text-align: center;
97
+ font-family: var(--font-body);
98
+ font-size: var(--body-size);
99
+ font-weight: 500;
100
+ color: var(--text-primary);
101
+ background: transparent;
102
+ border: none;
103
+ border-left: 1px solid var(--border-subtle);
104
+ border-right: 1px solid var(--border-subtle);
105
+ padding: var(--space-sm) var(--space-xs);
106
+ box-sizing: border-box;
107
+ -moz-appearance: textfield;
108
+ }
109
+
110
+ .number-input__field::-webkit-inner-spin-button,
111
+ .number-input__field::-webkit-outer-spin-button {
112
+ -webkit-appearance: none;
113
+ margin: 0;
114
+ }
115
+
116
+ .number-input__field:focus {
117
+ outline: none;
118
+ border-color: var(--accent-primary);
119
+ box-shadow: var(--focus-glow);
120
+ }
121
+
122
+ @media (prefers-reduced-motion: reduce) {
123
+ .number-input__btn,
124
+ .number-input__controls { transition: none; }
125
+ }
126
+ `,
127
+ ];
128
+
129
+ constructor() {
130
+ super();
131
+ this.value = 0;
132
+ this.min = undefined;
133
+ this.max = undefined;
134
+ this.step = 1;
135
+ this.label = '';
136
+ this.disabled = false;
137
+ this._fieldId = `arc-number-input-${++numberInputIdCounter}`;
138
+ }
139
+
140
+ get _atMin() {
141
+ return this.min !== undefined && this.min !== null && this.value <= this.min;
142
+ }
143
+
144
+ get _atMax() {
145
+ return this.max !== undefined && this.max !== null && this.value >= this.max;
146
+ }
147
+
148
+ _clamp(val) {
149
+ let clamped = val;
150
+ if (this.min !== undefined && this.min !== null) clamped = Math.max(this.min, clamped);
151
+ if (this.max !== undefined && this.max !== null) clamped = Math.min(this.max, clamped);
152
+ return clamped;
153
+ }
154
+
155
+ _setValue(newValue) {
156
+ const clamped = this._clamp(newValue);
157
+ if (clamped === this.value) return;
158
+ this.value = clamped;
159
+ this.dispatchEvent(new CustomEvent('arc-change', {
160
+ detail: { value: this.value },
161
+ bubbles: true,
162
+ composed: true,
163
+ }));
164
+ }
165
+
166
+ _decrement() {
167
+ this._setValue(this.value - this.step);
168
+ }
169
+
170
+ _increment() {
171
+ this._setValue(this.value + this.step);
172
+ }
173
+
174
+ _handleInput(e) {
175
+ const parsed = parseFloat(e.target.value);
176
+ if (!isNaN(parsed)) {
177
+ this._setValue(parsed);
178
+ }
179
+ }
180
+
181
+ _handleKeydown(e) {
182
+ const multiplier = e.shiftKey ? 10 : 1;
183
+
184
+ if (e.key === 'ArrowUp') {
185
+ e.preventDefault();
186
+ this._setValue(this.value + this.step * multiplier);
187
+ } else if (e.key === 'ArrowDown') {
188
+ e.preventDefault();
189
+ this._setValue(this.value - this.step * multiplier);
190
+ }
191
+ }
192
+
193
+ render() {
194
+ return html`
195
+ <div class="number-input" part="wrapper">
196
+ ${this.label ? html`
197
+ <label class="number-input__label" for=${this._fieldId} part="label">${this.label}</label>
198
+ ` : ''}
199
+ <div class="number-input__controls" part="controls">
200
+ <button
201
+ class="number-input__btn"
202
+ type="button"
203
+ tabindex="-1"
204
+ aria-label="Decrease"
205
+ ?disabled=${this.disabled || this._atMin}
206
+ @click=${this._decrement}
207
+ part="decrement"
208
+ >&minus;</button>
209
+ <input
210
+ class="number-input__field"
211
+ id=${this._fieldId}
212
+ type="number"
213
+ role="spinbutton"
214
+ aria-valuemin=${this.min ?? ''}
215
+ aria-valuemax=${this.max ?? ''}
216
+ aria-valuenow=${this.value}
217
+ aria-label=${this.label || 'Number'}
218
+ .value=${String(this.value)}
219
+ ?disabled=${this.disabled}
220
+ @change=${this._handleInput}
221
+ @keydown=${this._handleKeydown}
222
+ part="field"
223
+ />
224
+ <button
225
+ class="number-input__btn"
226
+ type="button"
227
+ tabindex="-1"
228
+ aria-label="Increase"
229
+ ?disabled=${this.disabled || this._atMax}
230
+ @click=${this._increment}
231
+ part="increment"
232
+ >&plus;</button>
233
+ </div>
234
+ </div>
235
+ `;
236
+ }
237
+ }
238
+
239
+ customElements.define('arc-number-input', ArcNumberInput);
@@ -0,0 +1,221 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { tokenStyles } from '../shared-styles.js';
3
+
4
+ export class ArcOtpInput extends LitElement {
5
+ static properties = {
6
+ length: { type: Number, reflect: true },
7
+ value: { type: String, reflect: true },
8
+ disabled: { type: Boolean, reflect: true },
9
+ type: { type: String },
10
+ };
11
+
12
+ static styles = [
13
+ tokenStyles,
14
+ css`
15
+ :host { display: inline-block; }
16
+ :host([disabled]) { pointer-events: none; opacity: 0.4; }
17
+
18
+ .otp {
19
+ display: inline-flex;
20
+ align-items: center;
21
+ gap: var(--space-sm);
22
+ }
23
+
24
+ .otp__box {
25
+ width: 44px;
26
+ height: 52px;
27
+ text-align: center;
28
+ font-family: var(--font-mono);
29
+ font-size: var(--text-md);
30
+ font-weight: 600;
31
+ color: var(--text-primary);
32
+ background: var(--bg-card);
33
+ border: 1px solid var(--border-default);
34
+ border-radius: var(--radius-md);
35
+ padding: 0;
36
+ caret-color: var(--accent-primary);
37
+ transition:
38
+ border-color var(--transition-fast),
39
+ box-shadow var(--transition-fast),
40
+ background var(--transition-fast);
41
+ box-sizing: border-box;
42
+ }
43
+
44
+ .otp__box::placeholder {
45
+ color: var(--text-ghost);
46
+ }
47
+
48
+ .otp__box:hover:not(:focus) {
49
+ border-color: var(--border-bright);
50
+ }
51
+
52
+ .otp__box:focus {
53
+ outline: none;
54
+ border-color: rgba(var(--accent-primary-rgb), 0.4);
55
+ box-shadow: var(--focus-glow);
56
+ background: var(--bg-surface);
57
+ }
58
+
59
+ .otp__box:disabled {
60
+ cursor: not-allowed;
61
+ }
62
+
63
+ @media (prefers-reduced-motion: reduce) {
64
+ :host *,
65
+ :host *::before,
66
+ :host *::after {
67
+ animation-duration: 0.01ms !important;
68
+ animation-iteration-count: 1 !important;
69
+ transition-duration: 0.01ms !important;
70
+ }
71
+ }
72
+ `,
73
+ ];
74
+
75
+ constructor() {
76
+ super();
77
+ this.length = 6;
78
+ this.value = '';
79
+ this.disabled = false;
80
+ this.type = 'number';
81
+ }
82
+
83
+ get _chars() {
84
+ const chars = (this.value || '').split('');
85
+ while (chars.length < this.length) chars.push('');
86
+ return chars.slice(0, this.length);
87
+ }
88
+
89
+ _getInputs() {
90
+ return this.shadowRoot.querySelectorAll('.otp__box');
91
+ }
92
+
93
+ _focusBox(index) {
94
+ const inputs = this._getInputs();
95
+ if (index >= 0 && index < inputs.length) {
96
+ inputs[index].focus();
97
+ inputs[index].select();
98
+ }
99
+ }
100
+
101
+ _updateValue() {
102
+ const inputs = this._getInputs();
103
+ const chars = [];
104
+ inputs.forEach(input => chars.push(input.value));
105
+ this.value = chars.join('');
106
+ this.dispatchEvent(new CustomEvent('arc-change', {
107
+ detail: { value: this.value },
108
+ bubbles: true,
109
+ composed: true,
110
+ }));
111
+ }
112
+
113
+ _onInput(e, index) {
114
+ const input = e.target;
115
+ let char = input.value;
116
+
117
+ // For number type, filter non-digits
118
+ if (this.type === 'number') {
119
+ char = char.replace(/[^0-9]/g, '');
120
+ }
121
+
122
+ // Take only the last character typed
123
+ if (char.length > 1) {
124
+ char = char.slice(-1);
125
+ }
126
+
127
+ input.value = char;
128
+ this._updateValue();
129
+
130
+ // Auto-advance to next box
131
+ if (char && index < this.length - 1) {
132
+ this._focusBox(index + 1);
133
+ }
134
+ }
135
+
136
+ _onKeydown(e, index) {
137
+ switch (e.key) {
138
+ case 'Backspace':
139
+ if (!e.target.value && index > 0) {
140
+ e.preventDefault();
141
+ this._focusBox(index - 1);
142
+ const inputs = this._getInputs();
143
+ inputs[index - 1].value = '';
144
+ this._updateValue();
145
+ }
146
+ break;
147
+ case 'ArrowLeft':
148
+ e.preventDefault();
149
+ if (index > 0) this._focusBox(index - 1);
150
+ break;
151
+ case 'ArrowRight':
152
+ e.preventDefault();
153
+ if (index < this.length - 1) this._focusBox(index + 1);
154
+ break;
155
+ case 'Home':
156
+ e.preventDefault();
157
+ this._focusBox(0);
158
+ break;
159
+ case 'End':
160
+ e.preventDefault();
161
+ this._focusBox(this.length - 1);
162
+ break;
163
+ }
164
+ }
165
+
166
+ _onPaste(e, index) {
167
+ e.preventDefault();
168
+ const pasted = (e.clipboardData.getData('text') || '').trim();
169
+ let chars = pasted.split('');
170
+
171
+ if (this.type === 'number') {
172
+ chars = chars.filter(c => /[0-9]/.test(c));
173
+ }
174
+
175
+ const inputs = this._getInputs();
176
+ for (let i = 0; i < chars.length && (index + i) < this.length; i++) {
177
+ inputs[index + i].value = chars[i];
178
+ }
179
+
180
+ this._updateValue();
181
+
182
+ // Focus the box after the last pasted character, or the last box
183
+ const nextIndex = Math.min(index + chars.length, this.length - 1);
184
+ this._focusBox(nextIndex);
185
+ }
186
+
187
+ _onFocus(e) {
188
+ e.target.select();
189
+ }
190
+
191
+ render() {
192
+ const chars = this._chars;
193
+ const inputMode = this.type === 'number' ? 'numeric' : 'text';
194
+ const pattern = this.type === 'number' ? '[0-9]*' : undefined;
195
+
196
+ return html`
197
+ <div class="otp" role="group" aria-label="One-time password input" part="otp">
198
+ ${chars.map((char, i) => html`
199
+ <input
200
+ class="otp__box"
201
+ type="text"
202
+ inputmode=${inputMode}
203
+ pattern=${pattern || '.*'}
204
+ maxlength="2"
205
+ autocomplete="one-time-code"
206
+ .value=${char}
207
+ ?disabled=${this.disabled}
208
+ aria-label=${`Digit ${i + 1} of ${this.length}`}
209
+ @input=${(e) => this._onInput(e, i)}
210
+ @keydown=${(e) => this._onKeydown(e, i)}
211
+ @paste=${(e) => this._onPaste(e, i)}
212
+ @focus=${this._onFocus}
213
+ part="box"
214
+ />
215
+ `)}
216
+ </div>
217
+ `;
218
+ }
219
+ }
220
+
221
+ customElements.define('arc-otp-input', ArcOtpInput);