@alacris/ui 0.0.0 → 0.1.1

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 (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +148 -1
  3. package/package.json +75 -3
  4. package/src/components/base.js +46 -0
  5. package/src/components/ui-accordion-item.js +157 -0
  6. package/src/components/ui-accordion.js +58 -0
  7. package/src/components/ui-alert.js +168 -0
  8. package/src/components/ui-app-bar.js +119 -0
  9. package/src/components/ui-autocomplete.js +371 -0
  10. package/src/components/ui-avatar.js +104 -0
  11. package/src/components/ui-backdrop.js +66 -0
  12. package/src/components/ui-badge.js +95 -0
  13. package/src/components/ui-bottom-app-bar.js +78 -0
  14. package/src/components/ui-bottom-nav.js +83 -0
  15. package/src/components/ui-breadcrumbs.js +100 -0
  16. package/src/components/ui-button-group.js +49 -0
  17. package/src/components/ui-button.js +143 -0
  18. package/src/components/ui-card.js +86 -0
  19. package/src/components/ui-carousel-item.js +53 -0
  20. package/src/components/ui-carousel.js +301 -0
  21. package/src/components/ui-checkbox.js +165 -0
  22. package/src/components/ui-chip-set.js +90 -0
  23. package/src/components/ui-chip.js +226 -0
  24. package/src/components/ui-container.js +59 -0
  25. package/src/components/ui-date-picker.js +774 -0
  26. package/src/components/ui-dialog.js +179 -0
  27. package/src/components/ui-divider.js +58 -0
  28. package/src/components/ui-drawer.js +169 -0
  29. package/src/components/ui-fab-menu.js +122 -0
  30. package/src/components/ui-fab.js +117 -0
  31. package/src/components/ui-icon-button.js +112 -0
  32. package/src/components/ui-icon.js +64 -0
  33. package/src/components/ui-list-item.js +166 -0
  34. package/src/components/ui-list.js +38 -0
  35. package/src/components/ui-loading-indicator.js +74 -0
  36. package/src/components/ui-menu-item.js +117 -0
  37. package/src/components/ui-menu.js +192 -0
  38. package/src/components/ui-nav-item.js +138 -0
  39. package/src/components/ui-nav-rail.js +111 -0
  40. package/src/components/ui-option.js +85 -0
  41. package/src/components/ui-pagination.js +173 -0
  42. package/src/components/ui-progress.js +100 -0
  43. package/src/components/ui-radio-group.js +104 -0
  44. package/src/components/ui-radio.js +143 -0
  45. package/src/components/ui-rating.js +126 -0
  46. package/src/components/ui-search.js +318 -0
  47. package/src/components/ui-select.js +418 -0
  48. package/src/components/ui-sheet.js +205 -0
  49. package/src/components/ui-side-sheet.js +221 -0
  50. package/src/components/ui-skeleton.js +89 -0
  51. package/src/components/ui-slider.js +266 -0
  52. package/src/components/ui-snackbar.js +233 -0
  53. package/src/components/ui-spinner.js +101 -0
  54. package/src/components/ui-split-button.js +158 -0
  55. package/src/components/ui-stack.js +37 -0
  56. package/src/components/ui-step.js +91 -0
  57. package/src/components/ui-stepper.js +86 -0
  58. package/src/components/ui-surface.js +51 -0
  59. package/src/components/ui-switch.js +153 -0
  60. package/src/components/ui-tab-panel.js +54 -0
  61. package/src/components/ui-tab.js +116 -0
  62. package/src/components/ui-table-footer.js +141 -0
  63. package/src/components/ui-table-toolbar.js +219 -0
  64. package/src/components/ui-table.js +735 -0
  65. package/src/components/ui-tabs.js +178 -0
  66. package/src/components/ui-text-field.js +319 -0
  67. package/src/components/ui-text.js +36 -0
  68. package/src/components/ui-time-picker.js +753 -0
  69. package/src/components/ui-toggle-button.js +170 -0
  70. package/src/components/ui-toggle-group.js +111 -0
  71. package/src/components/ui-toolbar.js +64 -0
  72. package/src/components/ui-tooltip.js +148 -0
  73. package/src/index.js +121 -0
  74. package/src/motion/animate.js +145 -0
  75. package/src/motion/flip.js +46 -0
  76. package/src/motion/index.js +4 -0
  77. package/src/motion/presence.js +104 -0
  78. package/src/motion/ripple.js +108 -0
  79. package/src/theme/apply-theme.js +142 -0
  80. package/src/theme/create-theme.js +70 -0
  81. package/src/theme/index.js +5 -0
  82. package/src/tokens/color.js +237 -0
  83. package/src/tokens/index.js +14 -0
  84. package/src/tokens/sys.js +76 -0
  85. package/src/tokens/system.js +114 -0
  86. package/src/tokens/typography.js +189 -0
  87. package/src/util/focus.js +89 -0
  88. package/src/util/form.js +69 -0
  89. package/src/util/icons.js +161 -0
  90. package/src/util/keys.js +110 -0
  91. package/src/util/position.js +102 -0
  92. package/src/util/table.js +230 -0
  93. package/types/elements.d.ts +78 -0
  94. package/types/index.d.ts +168 -0
@@ -0,0 +1,226 @@
1
+ // <ui-chip> — Material chip: assist, filter, input, and suggestion.
2
+ //
3
+ // The HOST is the interactive element (focusable, role="button" — or
4
+ // role="option" with aria-selected for filter chips) so that <ui-chip-set>'s
5
+ // roving tabindex can move focus across plain light-DOM chips.
6
+ //
7
+ // Filter chips toggle `selected` on click/Enter/Space and emit `change`;
8
+ // assist/suggestion/input chips just let the native click bubble. A
9
+ // dismissible chip animates the host collapsing, then emits `dismiss` — the
10
+ // PARENT owns the list and removes the chip from the DOM; the chip never
11
+ // removes itself.
12
+ //
13
+ // @prop {string} variant='assist' — assist | filter | input | suggestion
14
+ // @prop {boolean} selected=false — filter chips only
15
+ // @prop {boolean} disabled=false
16
+ // @prop {string} icon='' — leading icon name (check replaces it while
17
+ // a filter chip is selected)
18
+ // @prop {boolean} dismissible=false — trailing remove button
19
+ // @prop {string} value='' — identity within a <ui-chip-set>
20
+ // @event change — filter chip toggled; detail: { selected }
21
+ // @event dismiss — remove requested (after the collapse animation)
22
+ // @slot (default) — the chip label
23
+ // @part control — the chip surface
24
+ // @vars see `t` below (`themeVars.names`)
25
+
26
+ import { define, html, css, vars, computed, effect } from '@alacris/core';
27
+ import { sys } from '../tokens/sys.js';
28
+ import { base, focusRingOn } from './base.js';
29
+ import { ripple } from '../motion/ripple.js';
30
+ import { animate, settled, fx } from '../motion/animate.js';
31
+ import { presence } from '../motion/presence.js';
32
+ import './ui-icon.js';
33
+ import './ui-icon-button.js';
34
+
35
+ const t = vars('ui-chip', {
36
+ height: '32px',
37
+ radius: sys.radius.sm,
38
+ fg: sys.color.onSurfaceVariant,
39
+ labelFg: sys.color.onSurface,
40
+ outlineColor: sys.color.outlineVariant,
41
+ selectedBg: sys.color.secondaryContainer,
42
+ selectedFg: sys.color.onSecondaryContainer,
43
+ font: sys.type.labelLg,
44
+ });
45
+
46
+ const styles = css`
47
+ :host {
48
+ display: inline-flex;
49
+ vertical-align: middle;
50
+ border-radius: ${t.radius};
51
+ outline: none;
52
+ }
53
+ ${focusRingOn(':host')}
54
+ .control {
55
+ position: relative;
56
+ isolation: isolate;
57
+ display: inline-flex;
58
+ align-items: center;
59
+ gap: 0;
60
+ block-size: calc(${t.height} + var(--ui-density, 0) * 4px);
61
+ padding-inline: ${sys.space(3)};
62
+ border: 1px solid ${t.outlineColor};
63
+ border-radius: ${t.radius};
64
+ background: transparent;
65
+ color: ${t.fg};
66
+ font: ${t.font};
67
+ letter-spacing: ${sys.tracking.labelLg};
68
+ cursor: pointer;
69
+ user-select: none;
70
+ white-space: nowrap;
71
+ --ui-icon-size: 1.125rem;
72
+ transition: background-color ${sys.duration.short2} ${sys.easing.standard},
73
+ border-color ${sys.duration.short2} ${sys.easing.standard},
74
+ padding-inline-start ${sys.duration.short4} ${sys.easing.emphasized};
75
+ }
76
+ .selected .control { padding-inline-start: ${sys.space(2)}; }
77
+ .lead {
78
+ display: inline-flex;
79
+ align-items: center;
80
+ justify-content: center;
81
+ inline-size: 0;
82
+ min-inline-size: 0;
83
+ overflow: hidden;
84
+ opacity: 0;
85
+ pointer-events: none;
86
+ transition: inline-size ${sys.duration.short4} ${sys.easing.emphasized},
87
+ margin-inline-end ${sys.duration.short4} ${sys.easing.emphasized},
88
+ opacity ${sys.duration.short2} ${sys.easing.standard};
89
+ }
90
+ .lead.on {
91
+ inline-size: 1.125rem;
92
+ margin-inline-end: ${sys.space(2)};
93
+ opacity: 1;
94
+ pointer-events: auto;
95
+ }
96
+ .layer {
97
+ position: absolute; inset: 0; z-index: -1;
98
+ border-radius: inherit;
99
+ background: currentColor;
100
+ opacity: 0;
101
+ transition: opacity ${sys.duration.short2} ${sys.easing.standard};
102
+ }
103
+ .control:hover .layer { opacity: ${sys.state.hover}; }
104
+ :host(:focus-visible) .layer { opacity: ${sys.state.focus}; }
105
+ .control:active .layer { opacity: ${sys.state.pressed}; }
106
+
107
+ .label { color: ${t.labelFg}; }
108
+ .with-lead .control { padding-inline-start: ${sys.space(2)}; }
109
+ .with-dismiss .control { padding-inline-end: ${sys.space(2)}; }
110
+ .selected .control, :host([aria-selected="true"]) .control {
111
+ background: ${t.selectedBg};
112
+ border-color: transparent;
113
+ color: ${t.selectedFg};
114
+ }
115
+ .selected .label, :host([aria-selected="true"]) .label { color: ${t.selectedFg}; }
116
+
117
+ .dismiss {
118
+ margin-inline-start: ${sys.space(2)};
119
+ --ui-icon-button-size: 18px;
120
+ --ui-icon-size: 1.125rem;
121
+ }
122
+
123
+ :host([aria-disabled="true"]) { pointer-events: none; }
124
+ :host([aria-disabled="true"]) .control {
125
+ cursor: default;
126
+ color: color-mix(in srgb, ${sys.color.onSurface} calc(${sys.state.disabledContent} * 100%), transparent);
127
+ border-color: color-mix(in srgb, ${sys.color.onSurface} calc(${sys.state.disabledContainer} * 100%), transparent);
128
+ }
129
+ :host([aria-disabled="true"]) .label {
130
+ color: color-mix(in srgb, ${sys.color.onSurface} calc(${sys.state.disabledContent} * 100%), transparent);
131
+ }
132
+ `;
133
+
134
+ define('ui-chip', {
135
+ props: {
136
+ variant: 'assist', selected: false, disabled: false,
137
+ icon: '', dismissible: false, value: '',
138
+ },
139
+ styles: [base, styles],
140
+ setup({ variant, selected, disabled, icon, dismissible }, host) {
141
+ // The host is the control: role, selection, disabled and focusability.
142
+ effect(() => {
143
+ if (variant() === 'filter') {
144
+ host.setAttribute('role', 'option');
145
+ host.setAttribute('aria-selected', String(selected()));
146
+ } else {
147
+ host.setAttribute('role', 'button');
148
+ host.removeAttribute('aria-selected');
149
+ }
150
+ });
151
+ effect(() => {
152
+ if (disabled()) {
153
+ host.setAttribute('aria-disabled', 'true');
154
+ host.tabIndex = -1;
155
+ } else {
156
+ host.removeAttribute('aria-disabled');
157
+ if (host.tabIndex < 0) host.tabIndex = 0;
158
+ }
159
+ });
160
+
161
+ const checkWhen = computed(() => variant() === 'filter' && selected());
162
+ const rootCls = computed(() =>
163
+ [
164
+ checkWhen() ? 'selected' : '',
165
+ icon() ? 'with-lead' : '',
166
+ dismissible() ? 'with-dismiss' : '',
167
+ ].filter(Boolean).join(' '));
168
+
169
+ const activate = () => {
170
+ if (disabled()) return;
171
+ if (variant() !== 'filter') return; // assist/suggestion/input: native click is the event
172
+ selected.set(!selected());
173
+ host.emit('change', { selected: selected() });
174
+ };
175
+ host.addEventListener('keydown', (e) => {
176
+ if (e.key !== 'Enter' && e.key !== ' ') return;
177
+ if (e.target !== host) return; // let the dismiss button handle its own keys
178
+ e.preventDefault();
179
+ activate();
180
+ if (variant() !== 'filter') host.click();
181
+ });
182
+
183
+ const dismiss = (e) => {
184
+ e.stopPropagation();
185
+ if (disabled()) return;
186
+ const w = host.getBoundingClientRect().width;
187
+ const anim = animate(host, [
188
+ { inlineSize: `${w}px`, opacity: 1 },
189
+ { inlineSize: '0px', opacity: 0 },
190
+ ], { duration: 'short4', easing: 'emphasizedAccelerate' });
191
+ settled(anim).then(() => host.emit('dismiss'));
192
+ };
193
+
194
+ // Filter check: replaces the leading icon while selected, scaling in/out
195
+ // inside a width-animated slot so the chip grows instead of jumping.
196
+ const lead = presence(checkWhen, () => html`<ui-icon name="check"></ui-icon>`, {
197
+ enter: fx.scaleIn,
198
+ exit: fx.scaleOut,
199
+ enterDuration: 'short3',
200
+ exitDuration: 'short2',
201
+ });
202
+ const showLead = computed(() => variant() === 'filter' || !!icon());
203
+
204
+ return html`
205
+ <span class=${rootCls}>
206
+ <span class="control" part="control" @click=${activate}
207
+ ref=${(el) => ripple(el, { disabled })}>
208
+ <span class="layer" aria-hidden="true"></span>
209
+ ${() => (showLead()
210
+ ? html`<span class=${() => `lead${checkWhen() || icon() ? ' on' : ''}`}>
211
+ ${lead}
212
+ ${() => (icon() && !checkWhen() ? html`<ui-icon name=${icon}></ui-icon>` : null)}
213
+ </span>`
214
+ : null)}
215
+ <span class="label"><slot></slot></span>
216
+ ${() => (dismissible()
217
+ ? html`<ui-icon-button class="dismiss" icon="close" label="Remove"
218
+ ?disabled=${disabled} @click=${dismiss}></ui-icon-button>`
219
+ : null)}
220
+ </span>
221
+ </span>`;
222
+ },
223
+ });
224
+
225
+ export const tag = 'ui-chip';
226
+ export const themeVars = t;
@@ -0,0 +1,59 @@
1
+ // <ui-container> — a centered max-width content wrapper.
2
+ //
3
+ // <ui-container size="lg">…</ui-container>
4
+ //
5
+ // @prop {string} size='md' — sm (640px) | md (960px) | lg (1280px) |
6
+ // xl (1536px) | full (no max width)
7
+ // @prop {boolean} gutters=true — inline padding: space(6), space(4) under 600px
8
+ // @slot (default)
9
+ // @vars see `t` below (`themeVars.names`)
10
+
11
+ import { define, html, css, vars, effect } from '@alacris/core';
12
+ import { sys } from '../tokens/sys.js';
13
+ import { base } from './base.js';
14
+
15
+ const t = vars('ui-container', {
16
+ sm: '640px',
17
+ md: '960px',
18
+ lg: '1280px',
19
+ xl: '1536px',
20
+ gutter: sys.space(6),
21
+ gutterNarrow: sys.space(4),
22
+ });
23
+
24
+ const styles = css`
25
+ :host {
26
+ display: block;
27
+ inline-size: 100%;
28
+ margin-inline: auto;
29
+ max-inline-size: var(--_ui-container-max, none);
30
+ padding-inline: var(--_ui-container-pad, 0);
31
+ }
32
+ @media (max-width: 600px) {
33
+ :host { padding-inline: var(--_ui-container-pad-narrow, 0); }
34
+ }
35
+ `;
36
+
37
+ define('ui-container', {
38
+ props: { size: 'md', gutters: true },
39
+ styles: [base, styles],
40
+ setup({ size, gutters }, host) {
41
+ const max = { sm: t.sm, md: t.md, lg: t.lg, xl: t.xl, full: 'none' };
42
+ effect(() => {
43
+ host.style.setProperty('--_ui-container-max', max[size()] || t.md);
44
+ });
45
+ effect(() => {
46
+ if (gutters()) {
47
+ host.style.setProperty('--_ui-container-pad', t.gutter);
48
+ host.style.setProperty('--_ui-container-pad-narrow', t.gutterNarrow);
49
+ } else {
50
+ host.style.removeProperty('--_ui-container-pad');
51
+ host.style.removeProperty('--_ui-container-pad-narrow');
52
+ }
53
+ });
54
+ return html`<slot></slot>`;
55
+ },
56
+ });
57
+
58
+ export const tag = 'ui-container';
59
+ export const themeVars = t;