@muten/shadcn 0.1.11 → 0.1.12

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.
package/kit.css CHANGED
@@ -432,8 +432,19 @@ html.cx-locked { overflow: hidden; }
432
432
  .cx-who b { display: block; font-weight: 500; }
433
433
  .cx-who small { color: var(--muted-foreground); font-size: 13px; }
434
434
  .cx-online { background: var(--success) !important; box-shadow: 0 0 0 2px var(--background); }
435
- .cx-tip { position: fixed; z-index: var(--z-toast); padding: 6px 10px; border-radius: calc(var(--radius) * .8); background: var(--foreground); color: var(--background); font-size: 12px; font-weight: 500; white-space: nowrap; pointer-events: none; opacity: 0; transform: translateY(2px); transition: opacity 150ms ease, transform 150ms ease; }
435
+ .cx-tip { position: fixed; z-index: var(--z-toast); padding: 6px 12px; border-radius: calc(var(--radius) - 2px); background: var(--foreground); color: var(--background); font-size: 12px; font-weight: 500; line-height: 1.35; white-space: nowrap; pointer-events: none; opacity: 0; transform: translateY(3px) scale(.96); transform-origin: var(--ax, 50%) 100%; transition: opacity 120ms ease-out, transform 160ms cubic-bezier(.23, 1, .32, 1); }
436
+ /* its arrow, toward the trigger (shadcn's TooltipArrow: the pill's own colour, a softly rounded square half out) */
437
+ .cx-tip::after { content: ""; position: absolute; left: calc(var(--ax, 50%) - 5px); bottom: -4px; width: 10px; height: 10px; border-radius: 2px; background: inherit; transform: rotate(45deg); }
438
+ .cx-tip[data-side="bottom"] { transform: translateY(-3px) scale(.96); transform-origin: var(--ax, 50%) 0; }
439
+ .cx-tip[data-side="bottom"]::after { bottom: auto; top: -4px; }
440
+ .cx-tip[data-side="right"] { transform: translateX(-3px) scale(.96); transform-origin: 0 50%; }
441
+ .cx-tip[data-side="right"]::after { left: -4px; top: calc(50% - 5px); bottom: auto; }
436
442
  .cx-tip[data-state="open"] { opacity: 1; transform: none; }
443
+ @media (prefers-reduced-motion: reduce) { .cx-tip { transition: opacity 120ms ease-out; transform: none; } }
444
+ .cx-tip[data-side="right"] { transform: translateX(-4px); }
445
+ .cx-tip[data-side="right"][data-state="open"] { transform: none; }
446
+ /* after the first one, the names follow the pointer at once: no fade, no slide */
447
+ .cx-tip[data-instant] { transition-duration: 0ms; }
437
448
 
438
449
  /* ── Fields ── */
439
450
  .cx-form { display: flex; flex-direction: column; gap: 18px; width: 100%; max-width: 380px; }
@@ -4941,6 +4952,15 @@ a.cx-bc-item:hover { color: var(--foreground); }
4941
4952
  .cx-sb-label { white-space: nowrap; overflow: hidden; }
4942
4953
  .cx-sidebar[data-collapsed="true"] .cx-sb-label, .cx-sidebar[data-collapsed="true"] .cx-sb-glabel { display: block; opacity: 0; pointer-events: none; }
4943
4954
  .cx-sidebar[data-collapsed="true"] .cx-sb-count:not(:empty) { display: inline-grid; opacity: 0; }
4955
+ /* FOLDED, A SECTION'S TITLE BECOMES A SHORT LINE between its icons and the ones above: its words fade where they are and
4956
+ its height closes with the fold (200 ms), so the icon column has no holes where the titles were. A group with no
4957
+ title (the first) takes no room at all. */
4958
+ .cx-sidebar .cx-sb-glabel { position: relative; transition: opacity .16s ease, height .2s cubic-bezier(.22, 1, .36, 1), padding .2s cubic-bezier(.22, 1, .36, 1); }
4959
+ .cx-sidebar[data-collapsed="true"] .cx-sb-glabel { height: 17px; padding-top: 0; padding-bottom: 0; overflow: hidden; color: transparent; opacity: 1; }
4960
+ .cx-sidebar[data-collapsed="true"] .cx-sb-glabel::after { content: ""; position: absolute; left: 50%; top: 8px; width: 20px; height: 1px; margin-left: -10px; background: var(--border); }
4961
+ .cx-sidebar[data-collapsed="true"] .cx-sb-glabel:empty { display: none; }
4962
+ .cx-sidebar .cx-sb-group { transition: padding .2s cubic-bezier(.22, 1, .36, 1); }
4963
+ .cx-sidebar[data-collapsed="true"] .cx-sb-group { padding-top: 2px; padding-bottom: 2px; }
4944
4964
  /* A SECTION THAT LOADS: while its page is on its way (the router marks the link with data-loading), the row's icon
4945
4965
  turns into a small ring that spins, and turns back when the page is there. */
4946
4966
  @keyframes cx-sb-spin { to { transform: rotate(360deg); } }
@@ -4948,7 +4968,7 @@ a.cx-bc-item:hover { color: var(--foreground); }
4948
4968
  .cx-sb-item[data-loading] > .mu-icon { opacity: 0; transform: scale(.4) rotate(-90deg); }
4949
4969
  .cx-sb-item[data-loading]::before { content: ""; position: absolute; left: var(--sb-pad, 8px); top: 50%; width: 16px; height: 16px; margin-top: -8px; box-sizing: border-box; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: cx-sb-spin .7s linear infinite; }
4950
4970
  @media (prefers-reduced-motion: reduce) {
4951
- .cx-sidebar .cx-sb-item, .cx-sb-label, .cx-sb-glabel, .cx-sb-count, .cx-sb-item > .mu-icon { transition: none; }
4971
+ .cx-sidebar .cx-sb-item, .cx-sb-label, .cx-sb-glabel, .cx-sidebar .cx-sb-glabel, .cx-sb-count, .cx-sb-item > .mu-icon, .cx-tip { transition: none; }
4952
4972
  .cx-sb-item[data-loading]::before { animation-duration: 1.6s; }
4953
4973
  }
4954
4974
  /* THE MONOGRAM: a soft tile in the name's own hue, the letters in a deep shade of the same hue, a hairline ring of it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@muten/shadcn",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "shadcn-style component registry for muten - own-the-source parts you add with `muten add`.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -182,10 +182,37 @@ watchReveal();
182
182
  };
183
183
 
184
184
  // ── tooltip on any [data-name] ──
185
+ // shadcn's: the foreground pill with its arrow, above the trigger (beside it in a rail, below when there is no room).
186
+ // It holds while the pointer travels inside its trigger (icon to label is not leaving, so it never blinks), it goes on
187
+ // a press, and in a sidebar it only speaks while the rail is folded to icons: open, the label is already on screen.
185
188
  const tip = document.createElement('div'); tip.className = 'cx-tip'; tip.setAttribute('role', 'tooltip'); tip.hidden = true; lib.appendChild(tip);
186
- const tipOn = (e) => { const w = e.target.closest?.('[data-name]'); if (!w || !w.dataset.name) return; /* an empty name (no data yet) says nothing */ tip.textContent = w.dataset.name; tip.hidden = false; const r = w.getBoundingClientRect(); if (w.dataset.side === 'right' || w.closest('[data-slot="sidebar"]')) { tip.style.left = (r.right + 8) + 'px'; tip.style.top = (r.top + r.height / 2 - tip.offsetHeight / 2) + 'px'; } else { place(tip, w, 'center', 6); tip.style.top = (r.top - tip.offsetHeight - 6) + 'px'; } requestAnimationFrame(() => tip.dataset.state = 'open'); };
187
- const tipOff = (e) => { if (e.target.closest?.('[data-name]')) { tip.dataset.state = 'closed'; tip.hidden = true; } };
189
+ let tipFor = null;
190
+ const tipHide = () => { tipFor = null; tip.dataset.state = 'closed'; tip.hidden = true; };
191
+ const tipOn = (e) => {
192
+ const w = e.target.closest?.('[data-name]');
193
+ if (!w || !w.dataset.name || w === tipFor) return; /* an empty name (no data yet) says nothing */
194
+ const rail = w.closest('[data-slot="sidebar"]');
195
+ if (rail && rail.dataset.collapsed !== 'true' && w.dataset.slot !== 'tooltip-trigger') return;
196
+ tipFor = w; tip.textContent = w.dataset.name; tip.hidden = false; tip.dataset.state = 'closed';
197
+ const r = w.getBoundingClientRect();
198
+ if (w.dataset.side === 'right' || rail) {
199
+ tip.dataset.side = 'right'; tip.style.left = (r.right + 8) + 'px'; tip.style.top = (r.top + r.height / 2 - tip.offsetHeight / 2) + 'px';
200
+ } else {
201
+ place(tip, w, 'center', 8);
202
+ const above = r.top - tip.offsetHeight - 8 > 8;
203
+ tip.dataset.side = above ? 'top' : 'bottom';
204
+ tip.style.top = (above ? r.top - tip.offsetHeight - 8 : r.bottom + 8) + 'px';
205
+ tip.style.setProperty('--ax', (r.left + r.width / 2 - parseFloat(tip.style.left)) + 'px');
206
+ }
207
+ requestAnimationFrame(() => { if (tipFor === w) tip.dataset.state = 'open'; });
208
+ };
209
+ const tipOff = (e) => {
210
+ const w = e.target.closest?.('[data-name]');
211
+ if (!w || w !== tipFor || (e.relatedTarget && w.contains(e.relatedTarget))) return;
212
+ tipHide();
213
+ };
188
214
  ['pointerover', 'focusin'].forEach((t) => document.addEventListener(t, tipOn)); ['pointerout', 'focusout'].forEach((t) => document.addEventListener(t, tipOff));
215
+ document.addEventListener('pointerdown', () => { if (tipFor) tipHide(); }, true);
189
216
 
190
217
  // ── island: one floating notice for the whole app; never stacks, morphs its width to the next text ──
191
218
  const isl = document.createElement('div'); isl.className = 'cx-island cx-island-app'; isl.setAttribute('aria-live', 'polite'); isl.dataset.state = 'hidden';
@@ -1324,7 +1351,7 @@ let wheelsNow = [];
1324
1351
  if (periods) { const tl = $('.cx-tabs', root); initTabs(tl); tl.addEventListener('pick', (e) => { const d = periods[e.detail][1]; paint(d); const t2 = $$('.cx-tab', root); t2.forEach((x, i) => x.setAttribute('aria-selected', i === e.detail)); }); }
1325
1352
  $$('.cx-chart-bar', root).forEach((b, i) => b.animate([{ transform: 'scaleY(0)' }, { transform: 'scaleY(1)' }], { duration: 420, delay: i * 18, easing: 'cubic-bezier(.22,1,.36,1)', fill: 'backwards' }));
1326
1353
  };
1327
- root.addEventListener('pointerover', (e) => { const b = e.target.closest('.cx-chart-bar'); if (b) { tip.textContent = b.dataset.tipV; tip.hidden = false; const r = b.getBoundingClientRect(); tip.style.left = (r.left + r.width / 2 - tip.offsetWidth / 2) + 'px'; tip.style.top = (r.top + (1 - parseFloat(getComputedStyle(b).getPropertyValue('--v')) / 100) * r.height - tip.offsetHeight - 6) + 'px'; requestAnimationFrame(() => tip.dataset.state = 'open'); } });
1354
+ root.addEventListener('pointerover', (e) => { const b = e.target.closest('.cx-chart-bar'); if (b) { tip.textContent = b.dataset.tipV; tip.hidden = false; tip.dataset.side = 'top'; tip.style.removeProperty('--ax'); const r = b.getBoundingClientRect(); tip.style.left = (r.left + r.width / 2 - tip.offsetWidth / 2) + 'px'; tip.style.top = (r.top + (1 - parseFloat(getComputedStyle(b).getPropertyValue('--v')) / 100) * r.height - tip.offsetHeight - 6) + 'px'; requestAnimationFrame(() => tip.dataset.state = 'open'); } });
1328
1355
  root.addEventListener('pointerout', (e) => { if (e.target.closest('.cx-chart-bar')) { tip.dataset.state = 'closed'; tip.hidden = true; } });
1329
1356
  root.addEventListener('focusin', (e) => e.target.closest('.cx-chart-bar')?.dispatchEvent(new PointerEvent('pointerover', { bubbles: true })));
1330
1357
  paint(data);
package/registry.json CHANGED
@@ -579,6 +579,14 @@
579
579
  "component": "Mascot",
580
580
  "description": "Mascot - the brand's mascot (in Storio, Storio): an AssistantOrb that acts. Its gestures are hand-set keyframes that"
581
581
  },
582
+ {
583
+ "name": "media-query",
584
+ "deps": [],
585
+ "part": "MediaQuery",
586
+ "file": "registry/media-query.muten",
587
+ "component": "MediaQuery",
588
+ "description": "MediaQuery(query, onChange): says \"yes\"/\"no\" for a CSS media query at mount and on every change, so a page draws one version (phone or desktop) instead of both."
589
+ },
582
590
  {
583
591
  "name": "member-list",
584
592
  "deps": [
@@ -1198,14 +1206,6 @@
1198
1206
  "component": "VirtualList",
1199
1207
  "description": "VirtualList(rows, total, rowHeight, label, jump): only visible rows in the DOM."
1200
1208
  },
1201
- {
1202
- "name": "media-query",
1203
- "deps": [],
1204
- "part": "MediaQuery",
1205
- "file": "registry/media-query.muten",
1206
- "component": "MediaQuery",
1207
- "description": "MediaQuery(query, onChange): says \"yes\"/\"no\" for a CSS media query at mount and on every change, so a page draws one version (phone or desktop) instead of both."
1208
- },
1209
1209
  {
1210
1210
  "name": "virtual-window",
1211
1211
  "deps": [],