{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "settings", "type": "registry:item", "title": "aesthetic Settings", "description": "Aesthetic Settings primitive, v0.25.0. Installs ae-menu, ae-setting, ae-setting-panel, ae-setting-val, ae-settings without the full stylesheet. Recipes: pop.js, settings.js.", "registryDependencies": [ "https://aesthetic.mistystep.io/r/aesthetic-base.json" ], "meta": { "version": "0.25.0", "tier": "primitive", "route": "settings", "classes": [ "ae-menu", "ae-setting", "ae-setting-panel", "ae-setting-val", "ae-settings" ], "attributes": [], "tokens": [ "--ae-surface", "--ae-wash", "--ae-ink", "--ae-ink-faint", "--ae-accent", "--ae-surface-dark", "--ae-wash-dark", "--ae-ink-dark", "--ae-ink-faint-dark", "--ae-accent-dark", "--ae-font", "--ae-font-mono", "--ae-w-regular", "--ae-ease", "--ae-quick", "--ae-ink-muted", "--ae-line", "--ae-ink-muted-dark", "--ae-line-dark", "--ae-w-medium", "--ae-radius", "--ae-space-5", "--ae-soft" ], "recipes": ["pop.js", "settings.js"] }, "files": [ { "path": "registry/settings/aesthetic.settings.css", "type": "registry:file", "target": "~/aesthetic.settings.css", "content": "/* aesthetic settings v0.25.0 — generated by scripts/build-registry.mjs. */\n@import './aesthetic.base.css';\n\n:root {\n --ae-ink-muted: #737373;\n --ae-line: #e9e9e9;\n --ae-ink-muted-dark: #8f8f8f;\n --ae-line-dark: #262626;\n --ae-w-medium: 550;\n --ae-radius: 0;\n --ae-space-5: 1.5em;\n --ae-soft: 240ms;\n}\n\n@media (prefers-color-scheme: dark) {\n :root:not(.light):not([data-ae-mode='light']) {\n --ae-ink-muted: var(--ae-ink-muted-dark);\n --ae-line: var(--ae-line-dark);\n }\n}\n\n.light,\n[data-ae-mode='light'] {\n color-scheme: light;\n}\n\n:root.dark,\n:root[data-ae-mode='dark'] {\n color-scheme: dark;\n --ae-ink-muted: var(--ae-ink-muted-dark);\n --ae-line: var(--ae-line-dark);\n}\n\n.ae-settings {\nborder-bottom: 1px solid var(--ae-line);\n}\n\n.ae-setting {\ndisplay: flex;\n width: 100%;\n justify-content: space-between;\n align-items: baseline;\n gap: var(--ae-space-5);\n background: transparent;\n border: 0;\n border-top: 1px solid var(--ae-line);\n border-radius: var(--ae-radius);\n padding: 0.8em 0;\n margin: 0;\n font: inherit;\n line-height: inherit;\n color: var(--ae-ink);\n font-weight: var(--ae-w-medium);\n text-align: left;\n cursor: pointer;\n}\n\n.ae-setting-val {\ncolor: var(--ae-ink-muted);\n font-weight: var(--ae-w-regular);\n transition: color var(--ae-quick) var(--ae-ease);\n}\n\n.ae-setting:hover .ae-setting-val {\ncolor: var(--ae-ink);\n}\n\n.ae-setting-panel {\ndisplay: grid;\n grid-template-rows: 0fr;\n transition: grid-template-rows var(--ae-soft) var(--ae-ease);\n}\n\n.ae-setting-panel.is-open {\ngrid-template-rows: 1fr;\n}\n\n.ae-setting-panel > * {\noverflow: hidden;\n min-height: 0;\n\n /* the child's border and padding outlive a 0fr row; stop painting\n them once the fold completes */\n visibility: hidden;\n transition: visibility 0s var(--ae-soft);\n}\n\n.ae-setting-panel.is-open > * {\nvisibility: visible;\n transition: visibility 0s;\n}\n\n.ae-menu {\nmargin: 0 0 0.9em;\n padding: 0.3em 0;\n border: 1px solid var(--ae-line);\n background: var(--ae-surface);\n max-height: 14em;\n overflow-y: auto;\n}\n\n.ae-menu button {\ndisplay: block;\n width: 100%;\n text-align: left;\n background: transparent;\n border: 0;\n border-radius: var(--ae-radius);\n font: inherit;\n font-size: 13px;\n line-height: 1.7;\n color: var(--ae-ink-muted);\n padding: 0.4em 1.1em;\n cursor: pointer;\n transition:\n color var(--ae-quick) var(--ae-ease),\n background-color var(--ae-quick) var(--ae-ease);\n}\n\n.ae-menu button:hover {\nbackground: var(--ae-wash);\n color: var(--ae-ink);\n}\n\n.ae-menu button.is-sel {\ncolor: var(--ae-ink);\n font-weight: var(--ae-w-medium);\n}\n" }, { "path": "recipes/pop.js", "type": "registry:file", "target": "~/aesthetic.pop.js", "content": "/* the popover: geometry for the slip + roving tabindex for its menu.\n Native [popover] owns the top layer, light dismiss, and Escape; this\n recipe places the open slip by its invoker and wires arrow-key\n navigation for any .ae-menu inside.\n\n \n
*/\n(() => {\n if (window.aePop) return;\n window.aePop = true;\n document.addEventListener(\n 'toggle',\n (e) => {\n const pop = e.target;\n if (!(pop instanceof HTMLElement)) return;\n if (!pop.classList.contains('ae-pop')) return;\n if (e.newState !== 'open' || !pop.id) return;\n const inv = document.querySelector(`[popovertarget=\"${pop.id}\"]`);\n if (!inv) return;\n const r = inv.getBoundingClientRect();\n const pr = pop.getBoundingClientRect();\n let top = r.bottom + 6;\n if (top + pr.height > innerHeight - 8) top = r.top - pr.height - 6;\n const left = Math.min(r.left, innerWidth - pr.width - 8);\n pop.style.top = `${Math.max(8, top)}px`;\n pop.style.left = `${Math.max(8, left)}px`;\n\n /* roving tabindex: focus the selected item or the first item on\n open, then arrow-key through the menu */\n const menu = pop.querySelector('.ae-menu');\n if (!menu) return;\n const items = [...menu.querySelectorAll('button')].filter(\n (b) => !b.disabled,\n );\n if (items.length === 0) return;\n items.forEach((b) => (b.tabIndex = -1));\n const sel = menu.querySelector('button.is-sel') || items[0];\n sel.tabIndex = 0;\n sel.focus();\n },\n true,\n );\n\n /* arrow-key roving for .ae-menu inside popovers */\n document.addEventListener('keydown', (e) => {\n if (!['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(e.key)) return;\n const menu = e.target.closest('.ae-menu');\n if (!menu) return;\n const items = [...menu.querySelectorAll('button')].filter(\n (b) => !b.disabled,\n );\n if (items.length === 0) return;\n const i = items.indexOf(e.target);\n let next;\n if (e.key === 'ArrowDown') next = items[(i + 1) % items.length];\n else if (e.key === 'ArrowUp')\n next = items[(i - 1 + items.length) % items.length];\n else if (e.key === 'Home') next = items[0];\n else if (e.key === 'End') next = items[items.length - 1];\n if (!next) return;\n e.preventDefault();\n items.forEach((b) => (b.tabIndex = -1));\n next.tabIndex = 0;\n next.focus();\n });\n})();\n" }, { "path": "recipes/settings.js", "type": "registry:file", "target": "~/aesthetic.settings.js", "content": "/* settings rows that open: each .ae-setting button toggles the\n .ae-setting-panel that follows it; choosing an option writes the\n row's value and folds the chooser. One row open at a time; Escape\n folds and returns focus. */\n(() => {\n const closeSetting = (btn) => {\n const panel = btn.nextElementSibling;\n if (panel) panel.classList.remove('is-open');\n btn.setAttribute('aria-expanded', 'false');\n };\n document.querySelectorAll('.ae-setting').forEach((btn) => {\n const panel = btn.nextElementSibling;\n if (!panel || !panel.classList.contains('ae-setting-panel')) return;\n btn.setAttribute('aria-expanded', 'false');\n btn.addEventListener('click', () => {\n const open = panel.classList.contains('is-open');\n document.querySelectorAll('.ae-setting').forEach(closeSetting);\n if (!open) {\n panel.classList.add('is-open');\n btn.setAttribute('aria-expanded', 'true');\n }\n });\n panel.querySelectorAll('.ae-menu button').forEach((opt) => {\n opt.addEventListener('click', () => {\n panel\n .querySelectorAll('.ae-menu button')\n .forEach((b) => b.classList.toggle('is-sel', b === opt));\n const val = btn.querySelector('.ae-setting-val');\n if (val) val.textContent = opt.dataset.value || opt.textContent.trim();\n closeSetting(btn);\n btn.focus();\n });\n });\n panel.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') {\n closeSetting(btn);\n btn.focus();\n }\n });\n });\n})();\n" } ] }