@oneli8/core 1.0.0-beta.4 → 1.0.0-beta.6

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 (81) hide show
  1. package/README.md +51 -13
  2. package/ai-context.json +168 -1
  3. package/package.json +18 -3
  4. package/src/atoms/index.d.ts +1 -0
  5. package/src/atoms/index.js +1 -0
  6. package/src/atoms/navigation-badge/index.d.ts +1 -0
  7. package/src/atoms/navigation-badge/index.js +1 -0
  8. package/src/atoms/navigation-badge/navigation-badge.css +35 -0
  9. package/src/atoms/navigation-badge/navigation-badge.d.ts +5 -0
  10. package/src/atoms/navigation-badge/navigation-badge.js +21 -0
  11. package/src/atoms/selection-indicator/selection-indicator.css +13 -4
  12. package/src/materials/gem/gem.css +52 -5
  13. package/src/molecules/choice-chip/choice-chip.css +214 -0
  14. package/src/molecules/choice-chip/choice-chip.d.ts +27 -0
  15. package/src/molecules/choice-chip/choice-chip.js +170 -0
  16. package/src/molecules/choice-chip/index.d.ts +2 -0
  17. package/src/molecules/choice-chip/index.js +10 -0
  18. package/src/molecules/form-message/form-message.css +46 -0
  19. package/src/molecules/form-message/form-message.d.ts +23 -0
  20. package/src/molecules/form-message/form-message.js +73 -0
  21. package/src/molecules/form-message/index.d.ts +1 -0
  22. package/src/molecules/form-message/index.js +1 -0
  23. package/src/molecules/index.d.ts +6 -0
  24. package/src/molecules/index.js +6 -0
  25. package/src/molecules/link/index.d.ts +1 -0
  26. package/src/molecules/link/index.js +1 -0
  27. package/src/molecules/link/link.css +114 -0
  28. package/src/molecules/link/link.d.ts +27 -0
  29. package/src/molecules/link/link.js +76 -0
  30. package/src/molecules/navigation-item/index.js +1 -0
  31. package/src/molecules/navigation-item/navigation-item.css +153 -0
  32. package/src/molecules/navigation-item/navigation-item.js +95 -0
  33. package/src/molecules/select/index.js +1 -0
  34. package/src/molecules/select/select.css +31 -0
  35. package/src/molecules/select/select.js +127 -0
  36. package/src/molecules/selection-option/selection-option.js +4 -4
  37. package/src/molecules/text-field/index.d.ts +1 -0
  38. package/src/molecules/text-field/index.js +5 -0
  39. package/src/molecules/text-field/text-field.css +266 -0
  40. package/src/molecules/text-field/text-field.d.ts +69 -0
  41. package/src/molecules/text-field/text-field.js +234 -0
  42. package/src/molecules/token/index.d.ts +2 -0
  43. package/src/molecules/token/index.js +1 -0
  44. package/src/molecules/token/token.css +63 -0
  45. package/src/molecules/token/token.d.ts +35 -0
  46. package/src/molecules/token/token.js +83 -0
  47. package/src/organisms/choice-picker/choice-picker.css +98 -0
  48. package/src/organisms/choice-picker/choice-picker.d.ts +62 -0
  49. package/src/organisms/choice-picker/choice-picker.js +201 -0
  50. package/src/organisms/choice-picker/index.d.ts +1 -0
  51. package/src/organisms/choice-picker/index.js +1 -0
  52. package/src/organisms/combobox/combobox.css +40 -0
  53. package/src/organisms/combobox/combobox.js +309 -0
  54. package/src/organisms/combobox/index.js +5 -0
  55. package/src/organisms/index.d.ts +7 -0
  56. package/src/organisms/index.js +7 -0
  57. package/src/organisms/multi-select-field/index.d.ts +1 -0
  58. package/src/organisms/multi-select-field/index.js +1 -0
  59. package/src/organisms/multi-select-field/multi-select-field.css +76 -0
  60. package/src/organisms/multi-select-field/multi-select-field.d.ts +64 -0
  61. package/src/organisms/multi-select-field/multi-select-field.js +317 -0
  62. package/src/organisms/navigation-keys.js +58 -0
  63. package/src/organisms/segmented-control/index.d.ts +1 -0
  64. package/src/organisms/segmented-control/index.js +1 -0
  65. package/src/organisms/segmented-control/segmented-control.css +113 -0
  66. package/src/organisms/segmented-control/segmented-control.d.ts +43 -0
  67. package/src/organisms/segmented-control/segmented-control.js +163 -0
  68. package/src/organisms/selection-popup/index.js +1 -0
  69. package/src/organisms/selection-popup/selection-popup.css +90 -0
  70. package/src/organisms/selection-popup/selection-popup.js +100 -0
  71. package/src/organisms/tab-bar/index.d.ts +1 -0
  72. package/src/organisms/tab-bar/index.js +1 -0
  73. package/src/organisms/tab-bar/tab-bar.css +45 -0
  74. package/src/organisms/tab-bar/tab-bar.d.ts +37 -0
  75. package/src/organisms/tab-bar/tab-bar.js +104 -0
  76. package/src/organisms/tabs/index.d.ts +1 -0
  77. package/src/organisms/tabs/index.js +1 -0
  78. package/src/organisms/tabs/tabs.css +53 -0
  79. package/src/organisms/tabs/tabs.d.ts +48 -0
  80. package/src/organisms/tabs/tabs.js +152 -0
  81. package/styles.css +18 -2
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Oneli8 · Tabs — headless behavior.
3
+ *
4
+ * Figma (693:171): "Production emits tablist, tab and tabpanel, requires a non
5
+ * empty accessible region name, retains one enabled Tab stop, mirrors
6
+ * horizontal Arrow keys in RTL, and supports horizontal/vertical orientation
7
+ * plus automatic/manual activation."
8
+ *
9
+ * "Every consuming composition must pair each item with exactly one owned
10
+ * panel", so a tab without a panel is refused rather than rendered.
11
+ */
12
+ import { renderNavigationItem } from '../../molecules/navigation-item/index.js';
13
+ import { resolveNavigationKey, isRtl } from '../navigation-keys.js';
14
+
15
+ export const OL8_TABS_HIERARCHIES = ['primary', 'secondary', 'tertiary'];
16
+ export const OL8_TABS_ORIENTATIONS = ['horizontal', 'vertical'];
17
+ export const OL8_TABS_ACTIVATIONS = ['automatic', 'manual'];
18
+
19
+ let sequence = 0;
20
+
21
+ /**
22
+ * @param {{
23
+ * label:string, items:Array<{id:string,label?:string,icon?:string,badge?:any,disabled?:boolean,ariaLabel?:string}>,
24
+ * selected?:string, hierarchy?:string, size?:string, material?:string,
25
+ * orientation?:string, activation?:string, idPrefix?:string, className?:string,
26
+ * }} options
27
+ */
28
+ export function renderTabs(options) {
29
+ const {
30
+ label, items = [], selected, hierarchy = 'primary', size = 'standard',
31
+ material = 'regular', orientation = 'horizontal', activation = 'automatic',
32
+ idPrefix = `ol8-tabs-${++sequence}`, className,
33
+ } = options ?? {};
34
+
35
+ assertTabs({ label, items, hierarchy, size, orientation, activation });
36
+
37
+ const current = items.find(i => i.id === selected && !i.disabled)
38
+ ?? items.find(i => !i.disabled);
39
+
40
+ const buttons = items.map(item => renderNavigationItem({
41
+ kind: 'tab',
42
+ label: item.label, icon: item.icon, badge: item.badge,
43
+ selected: item.id === current?.id,
44
+ disabled: item.disabled,
45
+ ariaLabel: item.ariaLabel,
46
+ id: `${idPrefix}-tab-${item.id}`,
47
+ controls: `${idPrefix}-panel-${item.id}`,
48
+ // One enabled tab stop. Arrow keys move focus inside the list.
49
+ tabIndex: item.id === current?.id ? 0 : -1,
50
+ extraAttrs: `data-ol8-size="${size}" data-ol8-value="${escapeAttr(item.id)}"`,
51
+ })).join('');
52
+
53
+ const attrs = [
54
+ `class="ol8-tabs${className ? ` ${className}` : ''}"`,
55
+ 'role="tablist"',
56
+ `aria-label="${escapeAttr(label)}"`,
57
+ `aria-orientation="${orientation}"`,
58
+ `data-ol8-hierarchy="${hierarchy}"`,
59
+ `data-ol8-size="${size}"`,
60
+ `data-ol8-orientation="${orientation}"`,
61
+ `data-ol8-activation="${activation}"`,
62
+ material === 'gem' ? 'data-ol8-material="gem"' : '',
63
+ ].filter(Boolean).join(' ');
64
+
65
+ return `<div ${attrs}>${buttons}</div>`;
66
+ }
67
+
68
+ /** The panel that belongs to one tab. Figma: exactly one owned panel each. */
69
+ export function renderTabPanel(tabId, content, options = {}) {
70
+ const { idPrefix, hidden = false } = options;
71
+ if (!idPrefix) throw new Error('[ol8] a tab panel needs the same idPrefix as its tab list, or nothing owns it.');
72
+ return `<div class="ol8-tabs__panel" role="tabpanel" id="${escapeAttr(idPrefix)}-panel-${escapeAttr(tabId)}"` +
73
+ ` aria-labelledby="${escapeAttr(idPrefix)}-tab-${escapeAttr(tabId)}" tabindex="0"${hidden ? ' hidden' : ''}>` +
74
+ `${content}</div>`;
75
+ }
76
+
77
+ /**
78
+ * Wires an existing `.ol8-tabs` list. Idempotent.
79
+ * Returns a small controller so a product can drive selection itself.
80
+ */
81
+ export function hydrateTabs(root = document) {
82
+ const lists = [];
83
+ for (const list of root.querySelectorAll('.ol8-tabs')) {
84
+ if (list.dataset.ol8Wired === 'true') continue;
85
+ list.dataset.ol8Wired = 'true';
86
+
87
+ const tabsOf = () => [...list.querySelectorAll('.ol8-nav-item--tab')];
88
+ const select = (tab) => {
89
+ for (const other of tabsOf()) {
90
+ const on = other === tab;
91
+ other.setAttribute('aria-selected', String(on));
92
+ other.tabIndex = on ? 0 : -1;
93
+ const panel = document.getElementById(other.getAttribute('aria-controls') ?? '');
94
+ if (panel) panel.hidden = !on;
95
+ }
96
+ tab.dispatchEvent(new CustomEvent('ol8:tabchange', {
97
+ bubbles: true, detail: { value: tab.dataset.ol8Value },
98
+ }));
99
+ };
100
+
101
+ list.addEventListener('keydown', (event) => {
102
+ const tabs = tabsOf();
103
+ const items = tabs.map(t => ({ disabled: t.disabled }));
104
+ const activeIndex = tabs.indexOf(document.activeElement);
105
+ if (activeIndex === -1) return;
106
+
107
+ if (event.key === 'Enter' || event.key === ' ') {
108
+ if (list.dataset.ol8Activation === 'manual') { event.preventDefault(); select(tabs[activeIndex]); }
109
+ return;
110
+ }
111
+ const next = resolveNavigationKey(event, items, activeIndex, {
112
+ orientation: list.dataset.ol8Orientation ?? 'horizontal',
113
+ rtl: isRtl(list),
114
+ });
115
+ if (next === -1 || next === activeIndex) return;
116
+ event.preventDefault();
117
+ tabs[next].focus();
118
+ if (list.dataset.ol8Activation !== 'manual') select(tabs[next]);
119
+ });
120
+
121
+ list.addEventListener('click', (event) => {
122
+ const tab = event.target.closest('.ol8-nav-item--tab');
123
+ if (tab && !tab.disabled) select(tab);
124
+ });
125
+
126
+ lists.push(list);
127
+ }
128
+ return lists.length;
129
+ }
130
+
131
+ function assertTabs({ label, items, hierarchy, size, orientation, activation }) {
132
+ if (typeof label !== 'string' || label === '') {
133
+ throw new Error('[ol8] a tab list requires an accessible name. A region nobody can name is a region nobody can reach.');
134
+ }
135
+ if (!Array.isArray(items) || items.length === 0) {
136
+ throw new Error('[ol8] a tab list needs at least one tab.');
137
+ }
138
+ if (!OL8_TABS_HIERARCHIES.includes(hierarchy)) throw new Error(`[ol8] unknown tabs hierarchy "${hierarchy}"`);
139
+ if (!OL8_TABS_ORIENTATIONS.includes(orientation)) throw new Error(`[ol8] unknown tabs orientation "${orientation}"`);
140
+ if (!OL8_TABS_ACTIVATIONS.includes(activation)) throw new Error(`[ol8] unknown tabs activation "${activation}"`);
141
+ if (items.every(i => i.disabled)) {
142
+ throw new Error('[ol8] a tab list must keep one enabled tab stop.');
143
+ }
144
+ const seen = new Set();
145
+ for (const item of items) {
146
+ if (!item.id) throw new Error('[ol8] every tab needs an id, because exactly one panel is paired with it.');
147
+ if (seen.has(item.id)) throw new Error(`[ol8] two tabs share the id "${item.id}"`);
148
+ seen.add(item.id);
149
+ }
150
+ }
151
+
152
+ function escapeAttr(v) { return String(v).replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;'); }
package/styles.css CHANGED
@@ -1,13 +1,29 @@
1
- /* OneLi8 · complete stylesheet for @oneli8/core.
1
+ /* GENERATED by packages/sync/src/build-styles.mjs do not edit.
2
+ OneLi8 · complete stylesheet for @oneli8/core.
2
3
  Load order matters: tokens define the custom properties every rule reads,
3
4
  atoms before the molecules that compose them, and the Gem material last
4
5
  because it adapts surfaces the component rules have already painted. */
5
6
  @import "@oneli8/tokens/css";
6
7
  @import "./src/atoms/icon/icon.css";
8
+ @import "./src/atoms/navigation-badge/navigation-badge.css";
7
9
  @import "./src/atoms/selection-indicator/selection-indicator.css";
8
10
  @import "./src/molecules/button/button.css";
9
- @import "./src/molecules/icon-button/icon-button.css";
11
+ @import "./src/molecules/choice-chip/choice-chip.css";
10
12
  @import "./src/molecules/choice-item/choice-item.css";
13
+ @import "./src/molecules/form-message/form-message.css";
14
+ @import "./src/molecules/icon-button/icon-button.css";
15
+ @import "./src/molecules/link/link.css";
16
+ @import "./src/molecules/navigation-item/navigation-item.css";
17
+ @import "./src/molecules/select/select.css";
11
18
  @import "./src/molecules/selection-option/selection-option.css";
19
+ @import "./src/molecules/text-field/text-field.css";
20
+ @import "./src/molecules/token/token.css";
12
21
  @import "./src/organisms/choice-group/choice-group.css";
22
+ @import "./src/organisms/choice-picker/choice-picker.css";
23
+ @import "./src/organisms/combobox/combobox.css";
24
+ @import "./src/organisms/multi-select-field/multi-select-field.css";
25
+ @import "./src/organisms/segmented-control/segmented-control.css";
26
+ @import "./src/organisms/selection-popup/selection-popup.css";
27
+ @import "./src/organisms/tab-bar/tab-bar.css";
28
+ @import "./src/organisms/tabs/tabs.css";
13
29
  @import "./src/materials/gem/gem.css";