@collectivetrait/lineaui 0.0.0-stage → 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 (130) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/LICENSE +21 -0
  3. package/README.md +113 -2
  4. package/config.json +3 -0
  5. package/dist/blocks.css +5042 -0
  6. package/dist/blocks.css.map +1 -0
  7. package/dist/bundle.js +873 -0
  8. package/dist/core.css +1641 -0
  9. package/dist/core.css.map +1 -0
  10. package/dist/icons-manifest.json +1 -0
  11. package/dist/icons.css +158 -0
  12. package/dist/styles.css +10546 -0
  13. package/dist/styles.css.map +1 -0
  14. package/dist/theme-init.js +8 -0
  15. package/dist/utilities.css +3865 -0
  16. package/dist/utilities.css.map +1 -0
  17. package/humans.txt +8 -0
  18. package/js/config.js +3 -0
  19. package/js/index.js +45 -0
  20. package/js/modules/copy.js +64 -0
  21. package/js/modules/dialog.js +55 -0
  22. package/js/modules/dismiss.js +43 -0
  23. package/js/modules/dropdown.js +108 -0
  24. package/js/modules/gallery.js +84 -0
  25. package/js/modules/highlight.js +89 -0
  26. package/js/modules/navigation.js +104 -0
  27. package/js/modules/quantity.js +63 -0
  28. package/js/modules/rhythm.js +21 -0
  29. package/js/modules/table.js +109 -0
  30. package/js/modules/tabs.js +112 -0
  31. package/js/modules/toast.js +97 -0
  32. package/js/modules/tooltip.js +106 -0
  33. package/js/theme-init.js +8 -0
  34. package/package.json +71 -4
  35. package/scss/_blocks-only.scss +4 -0
  36. package/scss/_core.scss +7 -0
  37. package/scss/_index.scss +12 -0
  38. package/scss/_layers.scss +2 -0
  39. package/scss/_utilities-only.scss +4 -0
  40. package/scss/base/_code.scss +50 -0
  41. package/scss/base/_forms.scss +110 -0
  42. package/scss/base/_index.scss +7 -0
  43. package/scss/base/_interactive.scss +25 -0
  44. package/scss/base/_media.scss +28 -0
  45. package/scss/base/_reset.scss +53 -0
  46. package/scss/base/_tables.scss +49 -0
  47. package/scss/base/_typography.scss +232 -0
  48. package/scss/blocks/_accordion.scss +88 -0
  49. package/scss/blocks/_alert.scss +101 -0
  50. package/scss/blocks/_avatar.scss +104 -0
  51. package/scss/blocks/_badge.scss +72 -0
  52. package/scss/blocks/_breadcrumb.scss +65 -0
  53. package/scss/blocks/_button-group.scss +73 -0
  54. package/scss/blocks/_button.scss +212 -0
  55. package/scss/blocks/_card.scss +146 -0
  56. package/scss/blocks/_choice.scss +163 -0
  57. package/scss/blocks/_code.scss +157 -0
  58. package/scss/blocks/_data-list.scss +92 -0
  59. package/scss/blocks/_divider.scss +54 -0
  60. package/scss/blocks/_drawer.scss +160 -0
  61. package/scss/blocks/_dropdown.scss +167 -0
  62. package/scss/blocks/_empty-state.scss +61 -0
  63. package/scss/blocks/_field.scss +146 -0
  64. package/scss/blocks/_form.scss +87 -0
  65. package/scss/blocks/_gallery.scss +120 -0
  66. package/scss/blocks/_index.scss +49 -0
  67. package/scss/blocks/_input-group.scss +95 -0
  68. package/scss/blocks/_line-item.scss +105 -0
  69. package/scss/blocks/_list.scss +117 -0
  70. package/scss/blocks/_media.scss +105 -0
  71. package/scss/blocks/_menu.scss +191 -0
  72. package/scss/blocks/_meta.scss +59 -0
  73. package/scss/blocks/_modal.scss +156 -0
  74. package/scss/blocks/_navbar.scss +227 -0
  75. package/scss/blocks/_order-summary.scss +92 -0
  76. package/scss/blocks/_page-header.scss +88 -0
  77. package/scss/blocks/_pagination.scss +110 -0
  78. package/scss/blocks/_price.scss +88 -0
  79. package/scss/blocks/_product-card.scss +138 -0
  80. package/scss/blocks/_progress.scss +68 -0
  81. package/scss/blocks/_prose.scss +165 -0
  82. package/scss/blocks/_quantity.scss +73 -0
  83. package/scss/blocks/_quote.scss +71 -0
  84. package/scss/blocks/_rating.scss +50 -0
  85. package/scss/blocks/_site-footer.scss +135 -0
  86. package/scss/blocks/_skeleton.scss +63 -0
  87. package/scss/blocks/_spinner.scss +44 -0
  88. package/scss/blocks/_stat.scss +111 -0
  89. package/scss/blocks/_steps.scss +147 -0
  90. package/scss/blocks/_swatch.scss +117 -0
  91. package/scss/blocks/_table.scss +242 -0
  92. package/scss/blocks/_tabs.scss +168 -0
  93. package/scss/blocks/_tag.scss +72 -0
  94. package/scss/blocks/_timeline.scss +84 -0
  95. package/scss/blocks/_toast.scss +146 -0
  96. package/scss/blocks/_toolbar.scss +67 -0
  97. package/scss/blocks/_tooltip.scss +63 -0
  98. package/scss/helpers/_index.scss +2 -0
  99. package/scss/helpers/_rhythm.scss +64 -0
  100. package/scss/helpers/_typography.scss +4 -0
  101. package/scss/layout/_cluster.scss +43 -0
  102. package/scss/layout/_container.scss +26 -0
  103. package/scss/layout/_grid.scss +90 -0
  104. package/scss/layout/_index.scss +5 -0
  105. package/scss/layout/_section.scss +78 -0
  106. package/scss/layout/_shell.scss +87 -0
  107. package/scss/settings/_blocks.scss +3 -0
  108. package/scss/settings/_breakpoints.scss +7 -0
  109. package/scss/settings/_fonts.scss +5 -0
  110. package/scss/settings/_index.scss +7 -0
  111. package/scss/settings/_layout.scss +5 -0
  112. package/scss/settings/_prefix.scss +1 -0
  113. package/scss/settings/_rhythm.scss +6 -0
  114. package/scss/settings/_utilities.scss +2 -0
  115. package/scss/tokens/_borders.scss +16 -0
  116. package/scss/tokens/_elevation.scss +37 -0
  117. package/scss/tokens/_index.scss +8 -0
  118. package/scss/tokens/_layout.scss +17 -0
  119. package/scss/tokens/_motion.scss +17 -0
  120. package/scss/tokens/_palette.scss +50 -0
  121. package/scss/tokens/_roles.scss +90 -0
  122. package/scss/tokens/_space.scss +9 -0
  123. package/scss/tokens/_type.scss +57 -0
  124. package/scss/tools/_bem.scss +22 -0
  125. package/scss/tools/_index.scss +4 -0
  126. package/scss/tools/_rhythm.scss +26 -0
  127. package/scss/tools/_role-engine.scss +16 -0
  128. package/scss/tools/_utility-generator.scss +74 -0
  129. package/scss/utilities/_index.scss +8 -0
  130. package/scss/utilities/_map.scss +321 -0
@@ -0,0 +1,63 @@
1
+ import { prefix } from '../config.js';
2
+
3
+ const ATTR = `data-${prefix}quantity`;
4
+ const DEC = `${prefix}quantity__decrement`;
5
+ const INC = `${prefix}quantity__increment`;
6
+ const INPUT = `${prefix}quantity__input`;
7
+
8
+ export function initQuantity() {
9
+ document.querySelectorAll(`[${ATTR}]`).forEach((root) => {
10
+ if (root._lnQuantity) return;
11
+ const q = new Quantity(root);
12
+ q.init();
13
+ });
14
+ }
15
+
16
+ export class Quantity {
17
+ constructor(root) {
18
+ this.root = root;
19
+ this.input = root.querySelector(`.${INPUT}`);
20
+ this.decBtn = root.querySelector(`.${DEC}`);
21
+ this.incBtn = root.querySelector(`.${INC}`);
22
+ }
23
+
24
+ init() {
25
+ if (!this.input) return;
26
+ this.root._lnQuantity = true;
27
+
28
+ this.decBtn?.addEventListener('click', () => this.step(-1));
29
+ this.incBtn?.addEventListener('click', () => this.step(1));
30
+ this.input.addEventListener('change', () => this.clamp());
31
+
32
+ this.updateButtons();
33
+ }
34
+
35
+ get min() { return Number(this.input.min) || 0; }
36
+ get max() { return Number(this.input.max) || Infinity; }
37
+ get stepVal() { return Number(this.input.step) || 1; }
38
+ get value() { return Number(this.input.value) || 0; }
39
+
40
+ step(direction) {
41
+ const next = this.value + (this.stepVal * direction);
42
+ this.setValue(next);
43
+ }
44
+
45
+ clamp() {
46
+ let val = this.value;
47
+ if (val < this.min) val = this.min;
48
+ if (val > this.max) val = this.max;
49
+ this.setValue(val);
50
+ }
51
+
52
+ setValue(val) {
53
+ val = Math.max(this.min, Math.min(val, this.max));
54
+ this.input.value = val;
55
+ this.input.dispatchEvent(new Event('change', { bubbles: true }));
56
+ this.updateButtons();
57
+ }
58
+
59
+ updateButtons() {
60
+ if (this.decBtn) this.decBtn.disabled = this.value <= this.min;
61
+ if (this.incBtn) this.incBtn.disabled = this.value >= this.max;
62
+ }
63
+ }
@@ -0,0 +1,21 @@
1
+ import { prefix } from '../config.js';
2
+
3
+ const ATTR = `data-${prefix}grid-toggle`;
4
+
5
+ export function initRhythm() {
6
+ document.querySelectorAll(`[${ATTR}]`).forEach((trigger) => {
7
+ if (trigger._lnGridToggle) return;
8
+ trigger._lnGridToggle = true;
9
+ trigger.addEventListener('click', () => {
10
+ const selector = trigger.getAttribute(ATTR);
11
+ const target = selector
12
+ ? document.querySelector(selector)
13
+ : trigger.parentElement;
14
+
15
+ if (!target) return;
16
+
17
+ const active = target.classList.toggle('show-grid');
18
+ trigger.setAttribute('aria-pressed', String(active));
19
+ });
20
+ });
21
+ }
@@ -0,0 +1,109 @@
1
+ import { prefix } from '../config.js';
2
+
3
+ const ATTR = `data-${prefix}table`;
4
+ const SORT = `${prefix}table__sort`;
5
+ const SELECT = `${prefix}table__select`;
6
+
7
+ export function initTable() {
8
+ document.querySelectorAll(`[${ATTR}]`).forEach((root) => {
9
+ if (root._lnTable) return;
10
+ const t = new Table(root);
11
+ t.init();
12
+ });
13
+ }
14
+
15
+ export class Table {
16
+ constructor(root) {
17
+ this.root = root;
18
+ this.table = root.querySelector('table') || root;
19
+ this.tbody = this.table.querySelector('tbody');
20
+ }
21
+
22
+ init() {
23
+ this.root._lnTable = true;
24
+ this.initSort();
25
+ this.initSelectAll();
26
+ }
27
+
28
+ initSort() {
29
+ this.root.querySelectorAll(`.${SORT}`).forEach((btn) => {
30
+ btn.addEventListener('click', () => {
31
+ const key = btn.getAttribute('data-sort-key');
32
+ if (!key || !this.tbody) return;
33
+
34
+ const current = btn.getAttribute('aria-sort');
35
+ const direction = current === 'ascending' ? 'descending' : 'ascending';
36
+
37
+ this.root.querySelectorAll(`.${SORT}`).forEach((b) => {
38
+ b.removeAttribute('aria-sort');
39
+ });
40
+
41
+ btn.setAttribute('aria-sort', direction);
42
+
43
+ const colIndex = this.getColumnIndex(btn);
44
+ if (colIndex === -1) return;
45
+
46
+ const rows = [...this.tbody.querySelectorAll('tr')];
47
+ rows.sort((a, b) => {
48
+ const aVal = a.cells[colIndex]?.textContent.trim() ?? '';
49
+ const bVal = b.cells[colIndex]?.textContent.trim() ?? '';
50
+ const aNum = parseFloat(aVal);
51
+ const bNum = parseFloat(bVal);
52
+
53
+ let cmp;
54
+ if (!isNaN(aNum) && !isNaN(bNum)) {
55
+ cmp = aNum - bNum;
56
+ } else {
57
+ cmp = aVal.localeCompare(bVal);
58
+ }
59
+
60
+ return direction === 'ascending' ? cmp : -cmp;
61
+ });
62
+
63
+ rows.forEach((row) => this.tbody.appendChild(row));
64
+
65
+ this.root.dispatchEvent(
66
+ new CustomEvent(`${prefix}table:sort`, {
67
+ bubbles: true,
68
+ detail: { key, direction }
69
+ })
70
+ );
71
+ });
72
+ });
73
+ }
74
+
75
+ getColumnIndex(btn) {
76
+ const th = btn.closest('th');
77
+ if (!th) return -1;
78
+ return [...th.parentElement.children].indexOf(th);
79
+ }
80
+
81
+ initSelectAll() {
82
+ const selectAll = this.root.querySelector(`th .${SELECT}`);
83
+ if (!selectAll || !this.tbody) return;
84
+
85
+ const rowCheckboxes = () => this.tbody.querySelectorAll(`.${SELECT}`);
86
+
87
+ selectAll.addEventListener('change', () => {
88
+ rowCheckboxes().forEach((cb) => {
89
+ cb.checked = selectAll.checked;
90
+ });
91
+
92
+ this.root.dispatchEvent(
93
+ new CustomEvent(`${prefix}table:select`, {
94
+ bubbles: true,
95
+ detail: { all: selectAll.checked }
96
+ })
97
+ );
98
+ });
99
+
100
+ this.tbody.addEventListener('change', (e) => {
101
+ if (!e.target.classList.contains(SELECT)) return;
102
+ const boxes = [...rowCheckboxes()];
103
+ const allChecked = boxes.every((cb) => cb.checked);
104
+ const someChecked = boxes.some((cb) => cb.checked);
105
+ selectAll.checked = allChecked;
106
+ selectAll.indeterminate = someChecked && !allChecked;
107
+ });
108
+ }
109
+ }
@@ -0,0 +1,112 @@
1
+ import { prefix } from '../config.js';
2
+
3
+ const TAB = `${prefix}tabs__tab`;
4
+ const PANEL = `${prefix}tabs__panel`;
5
+ const LIST = `${prefix}tabs__list`;
6
+
7
+ export function initTabs() {
8
+ document.querySelectorAll(`[data-${prefix}tabs]`).forEach((root) => {
9
+ if (root._lnTabs) return;
10
+ root._lnTabs = true;
11
+ const tabs = new Tabs(root);
12
+ tabs.init();
13
+ });
14
+ }
15
+
16
+ export class Tabs {
17
+ constructor(root) {
18
+ this.root = root;
19
+ this.tablist = root.querySelector(`.${LIST}`);
20
+ this.tabs = [...root.querySelectorAll(`.${TAB}`)];
21
+ this.panels = [...root.querySelectorAll(`.${PANEL}`)];
22
+ this.manual = root.dataset[`${prefix}tabs`] === 'manual' ||
23
+ root.getAttribute(`data-${prefix}tabs`) === 'manual';
24
+ }
25
+
26
+ init() {
27
+ if (!this.tablist || this.tabs.length === 0) return;
28
+
29
+ this.tablist.setAttribute('role', 'tablist');
30
+ this.tabs.forEach((tab, i) => {
31
+ tab.setAttribute('role', 'tab');
32
+ const panelId = tab.getAttribute('aria-controls');
33
+ const panel = panelId ? document.getElementById(panelId) : this.panels[i];
34
+ if (panel) panel.setAttribute('role', 'tabpanel');
35
+
36
+ if (tab.getAttribute('aria-selected') !== 'true') {
37
+ tab.setAttribute('aria-selected', 'false');
38
+ tab.setAttribute('tabindex', '-1');
39
+ if (panel) panel.hidden = true;
40
+ } else {
41
+ tab.setAttribute('tabindex', '0');
42
+ if (panel) panel.hidden = false;
43
+ }
44
+ });
45
+
46
+ if (!this.tabs.some(t => t.getAttribute('aria-selected') === 'true')) {
47
+ this.activate(this.tabs[0]);
48
+ }
49
+
50
+ this.tablist.addEventListener('keydown', (e) => this.onKeydown(e));
51
+ this.tablist.addEventListener('click', (e) => {
52
+ const tab = e.target.closest(`.${TAB}`);
53
+ if (tab) this.activate(tab);
54
+ });
55
+
56
+ const hashAttr = `data-${prefix}tabs-hash`;
57
+ if (this.root.hasAttribute(hashAttr)) {
58
+ this.syncFromHash();
59
+ window.addEventListener('hashchange', () => this.syncFromHash());
60
+ }
61
+ }
62
+
63
+ activate(tab) {
64
+ this.tabs.forEach((t, i) => {
65
+ const isActive = t === tab;
66
+ t.setAttribute('aria-selected', isActive ? 'true' : 'false');
67
+ t.setAttribute('tabindex', isActive ? '0' : '-1');
68
+ const panelId = t.getAttribute('aria-controls');
69
+ const panel = panelId ? document.getElementById(panelId) : this.panels[i];
70
+ if (panel) panel.hidden = !isActive;
71
+ });
72
+ tab.focus();
73
+ }
74
+
75
+ onKeydown(e) {
76
+ const idx = this.tabs.indexOf(e.target);
77
+ if (idx === -1) return;
78
+ let next;
79
+ switch (e.key) {
80
+ case 'ArrowRight':
81
+ case 'ArrowDown':
82
+ next = this.tabs[(idx + 1) % this.tabs.length];
83
+ break;
84
+ case 'ArrowLeft':
85
+ case 'ArrowUp':
86
+ next = this.tabs[(idx - 1 + this.tabs.length) % this.tabs.length];
87
+ break;
88
+ case 'Home':
89
+ next = this.tabs[0];
90
+ break;
91
+ case 'End':
92
+ next = this.tabs[this.tabs.length - 1];
93
+ break;
94
+ default:
95
+ return;
96
+ }
97
+ e.preventDefault();
98
+ if (this.manual) {
99
+ next.focus();
100
+ next.setAttribute('tabindex', '0');
101
+ e.target.setAttribute('tabindex', '-1');
102
+ } else {
103
+ this.activate(next);
104
+ }
105
+ }
106
+
107
+ syncFromHash() {
108
+ const hash = location.hash.slice(1);
109
+ const tab = this.tabs.find(t => t.getAttribute('aria-controls') === hash);
110
+ if (tab) this.activate(tab);
111
+ }
112
+ }
@@ -0,0 +1,97 @@
1
+ import { prefix } from '../config.js';
2
+
3
+ const BLOCK = `${prefix}toast`;
4
+ const REGION = `${prefix}toast-region`;
5
+ let region = null;
6
+
7
+ export function initToast() {
8
+ if (!region) {
9
+ region = document.createElement('div');
10
+ region.className = `${REGION} ${REGION}--bottom-end`;
11
+ region.setAttribute('aria-live', 'polite');
12
+ region.setAttribute('aria-relevant', 'additions');
13
+ document.body.appendChild(region);
14
+ }
15
+ }
16
+
17
+ export class Toast {
18
+ static show({ title, body, role, duration = 5000, solid = false, position } = {}) {
19
+ if (!region) initToast();
20
+
21
+ if (position) {
22
+ region.className = `${REGION} ${REGION}--${position}`;
23
+ }
24
+
25
+ const toast = document.createElement('div');
26
+ const classes = [BLOCK];
27
+ if (role) classes.push(`${BLOCK}--${role}`);
28
+ if (solid) classes.push(`${BLOCK}--solid`);
29
+ toast.className = classes.join(' ');
30
+ toast.setAttribute('role', 'alert');
31
+ toast.setAttribute(`data-${prefix}dismissible`, '');
32
+
33
+ let html = '';
34
+
35
+ html += `<div class="${BLOCK}__content">`;
36
+ if (title) html += `<div class="${BLOCK}__title">${escapeHtml(title)}</div>`;
37
+ if (body) html += `<div class="${BLOCK}__body">${escapeHtml(body)}</div>`;
38
+ html += '</div>';
39
+
40
+ html += `<button class="${BLOCK}__close" data-${prefix}dismiss aria-label="Close">&times;</button>`;
41
+
42
+ if (duration > 0) {
43
+ html += `<div class="${BLOCK}__timer" style="width: 100%"></div>`;
44
+ }
45
+
46
+ toast.innerHTML = html;
47
+ region.appendChild(toast);
48
+
49
+ const closeBtn = toast.querySelector(`.${BLOCK}__close`);
50
+ closeBtn.addEventListener('click', () => remove(toast));
51
+
52
+ if (duration > 0) {
53
+ const timer = toast.querySelector(`.${BLOCK}__timer`);
54
+ let remaining = duration;
55
+ let startTime = Date.now();
56
+ let paused = false;
57
+
58
+ const updateTimer = () => {
59
+ if (paused) return;
60
+ const elapsed = Date.now() - startTime;
61
+ const pct = Math.max(0, 1 - elapsed / duration);
62
+ if (timer) timer.style.width = `${pct * 100}%`;
63
+
64
+ if (pct <= 0) {
65
+ remove(toast);
66
+ } else {
67
+ requestAnimationFrame(updateTimer);
68
+ }
69
+ };
70
+
71
+ requestAnimationFrame(updateTimer);
72
+
73
+ toast.addEventListener('mouseenter', () => {
74
+ paused = true;
75
+ remaining = Math.max(0, duration - (Date.now() - startTime));
76
+ });
77
+
78
+ toast.addEventListener('mouseleave', () => {
79
+ paused = false;
80
+ startTime = Date.now() - (duration - remaining);
81
+ requestAnimationFrame(updateTimer);
82
+ });
83
+ }
84
+
85
+ return toast;
86
+ }
87
+ }
88
+
89
+ function remove(toast) {
90
+ toast.remove();
91
+ }
92
+
93
+ function escapeHtml(str) {
94
+ const div = document.createElement('div');
95
+ div.textContent = str;
96
+ return div.innerHTML;
97
+ }
@@ -0,0 +1,106 @@
1
+ import { prefix } from '../config.js';
2
+
3
+ const ATTR = `data-${prefix}tooltip`;
4
+ const BLOCK = `${prefix}tooltip`;
5
+ let idCounter = 0;
6
+
7
+ export function initTooltip() {
8
+ document.querySelectorAll(`[${ATTR}]`).forEach((trigger) => {
9
+ if (trigger._lnTooltip) return;
10
+ const t = new Tooltip(trigger);
11
+ t.init();
12
+ });
13
+ }
14
+
15
+ export class Tooltip {
16
+ constructor(trigger) {
17
+ this.trigger = trigger;
18
+ this.tooltip = null;
19
+ this.showTimeout = null;
20
+ this.id = `${BLOCK}-${++idCounter}`;
21
+ }
22
+
23
+ init() {
24
+ this.trigger._lnTooltip = true;
25
+ this.trigger.addEventListener('mouseenter', () => this.scheduleShow());
26
+ this.trigger.addEventListener('focusin', () => this.scheduleShow());
27
+ this.trigger.addEventListener('mouseleave', () => this.hide());
28
+ this.trigger.addEventListener('focusout', () => this.hide());
29
+ this.trigger.addEventListener('keydown', (e) => {
30
+ if (e.key === 'Escape') this.hide();
31
+ });
32
+ }
33
+
34
+ scheduleShow() {
35
+ if (this.showTimeout) return;
36
+ this.showTimeout = setTimeout(() => this.show(), 200);
37
+ }
38
+
39
+ show() {
40
+ if (this.tooltip) return;
41
+
42
+ const text = this.trigger.getAttribute(ATTR);
43
+ if (!text) return;
44
+
45
+ const position = this.trigger.getAttribute(`${ATTR}-position`) || 'top';
46
+
47
+ this.tooltip = document.createElement('div');
48
+ this.tooltip.className = `${BLOCK} ${BLOCK}--${position}`;
49
+ this.tooltip.id = this.id;
50
+ this.tooltip.setAttribute('role', 'tooltip');
51
+ this.tooltip.textContent = text;
52
+
53
+ const arrow = document.createElement('div');
54
+ arrow.className = `${BLOCK}__arrow`;
55
+ this.tooltip.appendChild(arrow);
56
+
57
+ document.body.appendChild(this.tooltip);
58
+ this.trigger.setAttribute('aria-describedby', this.id);
59
+
60
+ this.position(position);
61
+ }
62
+
63
+ hide() {
64
+ if (this.showTimeout) {
65
+ clearTimeout(this.showTimeout);
66
+ this.showTimeout = null;
67
+ }
68
+
69
+ if (this.tooltip) {
70
+ this.tooltip.remove();
71
+ this.tooltip = null;
72
+ this.trigger.removeAttribute('aria-describedby');
73
+ }
74
+ }
75
+
76
+ position(placement) {
77
+ if (!this.tooltip) return;
78
+
79
+ const triggerRect = this.trigger.getBoundingClientRect();
80
+ const tooltipRect = this.tooltip.getBoundingClientRect();
81
+ const gap = 8;
82
+
83
+ let top, left;
84
+
85
+ switch (placement) {
86
+ case 'bottom':
87
+ top = triggerRect.bottom + gap;
88
+ left = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;
89
+ break;
90
+ case 'start':
91
+ top = triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;
92
+ left = triggerRect.left - tooltipRect.width - gap;
93
+ break;
94
+ case 'end':
95
+ top = triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;
96
+ left = triggerRect.right + gap;
97
+ break;
98
+ default: // top
99
+ top = triggerRect.top - tooltipRect.height - gap;
100
+ left = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;
101
+ }
102
+
103
+ this.tooltip.style.top = `${top + window.scrollY}px`;
104
+ this.tooltip.style.left = `${left + window.scrollX}px`;
105
+ }
106
+ }
@@ -0,0 +1,8 @@
1
+ (function() {
2
+ try {
3
+ var t = localStorage.getItem("ln:theme");
4
+ if (t === "light" || t === "dark") {
5
+ document.documentElement.dataset.theme = t;
6
+ }
7
+ } catch(e) {}
8
+ })();
package/package.json CHANGED
@@ -1,6 +1,73 @@
1
1
  {
2
2
  "name": "@collectivetrait/lineaui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "A lightweight SCSS and JavaScript component library built on BEM naming, an 8px vertical rhythm, and cascade layers.",
5
+ "keywords": [
6
+ "SCSS library",
7
+ "BEM",
8
+ "component library",
9
+ "vertical rhythm",
10
+ "SCSS framework",
11
+ "UI components",
12
+ "accessible CSS",
13
+ "lightweight CSS",
14
+ "cascade layers",
15
+ "SCSS"
16
+ ],
17
+ "author": "Andrés Villarreal",
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/collectivetrait/lineaui.git"
22
+ },
23
+ "homepage": "https://lineaui.com",
24
+ "bugs": {
25
+ "url": "https://github.com/collectivetrait/lineaui/issues"
26
+ },
27
+ "type": "module",
28
+ "main": "dist/bundle.js",
29
+ "style": "dist/styles.css",
30
+ "sideEffects": [
31
+ "*.css",
32
+ "*.scss"
33
+ ],
34
+ "files": [
35
+ "dist",
36
+ "scss",
37
+ "js",
38
+ "config.json",
39
+ "LICENSE",
40
+ "README.md",
41
+ "CHANGELOG.md",
42
+ "humans.txt"
43
+ ],
44
+ "exports": {
45
+ ".": "./dist/bundle.js",
46
+ "./dist/styles.css": "./dist/styles.css",
47
+ "./dist/core.css": "./dist/core.css",
48
+ "./dist/blocks.css": "./dist/blocks.css",
49
+ "./dist/utilities.css": "./dist/utilities.css",
50
+ "./dist/icons.css": "./dist/icons.css",
51
+ "./dist/icons-manifest.json": "./dist/icons-manifest.json",
52
+ "./dist/theme-init.js": "./dist/theme-init.js",
53
+ "./scss": "./scss/_index.scss"
54
+ },
55
+ "scripts": {
56
+ "build": "node build.mjs",
57
+ "dev": "node build.mjs --watch",
58
+ "lint": "eslint && stylelint \"scss/**/*.scss\"",
59
+ "lint:js": "eslint",
60
+ "lint:scss": "stylelint \"scss/**/*.scss\"",
61
+ "prepublishOnly": "npm run build"
62
+ },
63
+ "devDependencies": {
64
+ "@eslint/js": "^10.0.1",
65
+ "cssnano": "^7.0.0",
66
+ "esbuild": "^0.24.0",
67
+ "eslint": "^10.12.0",
68
+ "postcss": "^8.4.0",
69
+ "sass": "^1.69.5",
70
+ "stylelint": "^17.16.0",
71
+ "stylelint-config-standard-scss": "^17.0.0"
72
+ }
73
+ }
@@ -0,0 +1,4 @@
1
+ @use 'settings' as s;
2
+
3
+ @forward 'layers';
4
+ @forward 'blocks';
@@ -0,0 +1,7 @@
1
+ @use 'settings' as s;
2
+
3
+ @forward 'layers';
4
+ @forward 'tokens';
5
+ @forward 'base';
6
+ @forward 'layout';
7
+ @forward 'helpers';
@@ -0,0 +1,12 @@
1
+ // @use rules must be first in Sass, then @forward, then CSS rules.
2
+ @use 'sass:map';
3
+ @use 'settings' as s;
4
+
5
+ // _layers.scss emits the @layer order declaration first in the CSS output.
6
+ @forward 'layers';
7
+ @forward 'tokens';
8
+ @forward 'base';
9
+ @forward 'layout';
10
+ @forward 'blocks';
11
+ @forward 'helpers';
12
+ @forward 'utilities';
@@ -0,0 +1,2 @@
1
+ // Cascade layer order — must appear before any @layer blocks in the CSS output.
2
+ @layer external, reset, tokens, base, layout, blocks, helpers, utilities;
@@ -0,0 +1,4 @@
1
+ @use 'settings' as s;
2
+
3
+ @forward 'layers';
4
+ @forward 'utilities';
@@ -0,0 +1,50 @@
1
+ @use '../tools/rhythm' as r;
2
+
3
+ @layer base {
4
+ :is(code, kbd, samp, var) {
5
+ // Relative to parent so it scales in prose.
6
+ font: 0.875em var(--f-mono);
7
+ }
8
+
9
+ // Inline code
10
+ :not(pre) > code {
11
+ background-color: var(--surface-sunken);
12
+ border-radius: var(--r-sm);
13
+ padding-inline: var(--s-h);
14
+ font-size: var(--t-code-s);
15
+ line-height: 1rem;
16
+ }
17
+
18
+ kbd {
19
+ background-color: var(--surface-raised);
20
+ border: 1px solid var(--border);
21
+ border-radius: var(--r-sm);
22
+ padding: 0.1em var(--s-h);
23
+ box-shadow: 0 1px 0 var(--border);
24
+ font-size: var(--t-body-sm-s);
25
+ }
26
+
27
+ pre {
28
+ font: var(--t-code-s)/var(--t-code-lh) var(--f-mono);
29
+ background-color: var(--surface-sunken);
30
+ border-radius: var(--r-xs);
31
+ overflow-x: auto;
32
+ tab-size: 2;
33
+ margin-bottom: r.lines(1);
34
+
35
+ // Padding lives on code, not pre.
36
+ // This keeps highlighted code (where .hljs is on <code>) and plain
37
+ // specimen code visually identical.
38
+ code {
39
+ display: block;
40
+ padding: var(--s3);
41
+ font: inherit;
42
+ background: none;
43
+ border-radius: 0;
44
+ }
45
+ }
46
+
47
+ pre:last-child {
48
+ margin-bottom: 0;
49
+ }
50
+ }