Three coordinated package changes addressing Wave 1 cross-repo TODOs from the UI/UX roadmap (learning_ai_uxui_web/docs/ROADMAP_2026.md §10). ═══════════════════════════════════════════════════════════════════════ TODO #2 — @bytelyst/react-auth bump 0.1.8 → 0.2.0 ═══════════════════════════════════════════════════════════════════════ - Changes 'workspace:*' to 'workspace:^' for the @bytelyst/api-client dependency. On pnpm publish this resolves to a caret range (e.g. ^0.1.6) instead of '*', restoring installability for consumers. (The 0.1.6 tarball was published with a literal 'workspace:*' string — newer minor bump unblocks the showcase react-auth demo.) - 21 tests still passing. ═══════════════════════════════════════════════════════════════════════ TODO #3 — @bytelyst/dashboard-shell bump 0.1.7 → 0.2.0 ═══════════════════════════════════════════════════════════════════════ - Adds 'routePrefix?: string' prop to DashboardShellProps, SidebarProps, and TopBarProps. Threads through DashboardShell → Sidebar + TopBar. - Built-in /profile, /billing, /settings links now use the prefix: routePrefix="/app" → /app/profile, /app/billing, /app/settings - Defaults to '' (empty string) — fully back-compat with 0.1.x callers. - 2 new vitest cases covering both prefixed and default behavior; 43 / 43 tests passing (+2 from 41). ═══════════════════════════════════════════════════════════════════════ TODO #1 — @bytelyst/design-tokens bump 0.1.8 → 0.2.0 ═══════════════════════════════════════════════════════════════════════ - Adds a density-aware spacing tier on top of the existing raw --ml-space-* tier: --bl-space-scale: 1 (default :root) --bl-space-1..16: calc(--ml-space-N × --bl-space-scale) - Emits density selectors at the end of tokens.css: [data-density="compact"] { --bl-space-scale: 0.875; } [data-density="comfortable"] { --bl-space-scale: 1; } [data-density="spacious"] { --bl-space-scale: 1.125; } - Generator (scripts/generate.ts) emits both tiers automatically; the auto-generated per-product CSS files (lysnrai, mindlyst, etc.) gain a single blank-line diff from regeneration — no semantic change. - 11 / 11 token tests passing. ═══════════════════════════════════════════════════════════════════════ Decision doc — docs/ROADMAP_2026_DECISIONS.md ═══════════════════════════════════════════════════════════════════════ - Records pragmatic defaults for TODO ledger items #9–#13 so implementation work doesn't block: #9 Storybook hosting → self-hosted on Gitea Pages (free) #10 useChat protocol → adopt Vercel AI SDK shape, abstract transport #11 react-auth fold-in → defer to Wave 7 #12 dashboard-shell merge → defer to Wave 7 #13 mobile-native UI → out of scope (tokens-only sharing) - Each decision is reversible via RFC. ═══════════════════════════════════════════════════════════════════════ Publish flow ═══════════════════════════════════════════════════════════════════════ These three packages now require a release. The existing publish workflow (.gitea/workflows/publish-packages.yml) has PACKAGE_FILTER pinned to @bytelyst/errors and won't pick them up automatically — a manual workflow_dispatch with a broader filter (or the existing publish-all-packages.yml on workflow_dispatch) is needed to ship 0.2.0 to the Gitea npm registry. Refs: learning_ai_uxui_web/docs/ROADMAP_2026.md §10 TODOs #1, #2, #3, #9–#13
This commit is contained in:
parent
7312689376
commit
cc0bffea86
115
docs/ROADMAP_2026_DECISIONS.md
Normal file
115
docs/ROADMAP_2026_DECISIONS.md
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
# UI/UX Roadmap 2026 — Pragmatic Decisions
|
||||||
|
|
||||||
|
These default decisions are recorded so implementation work can proceed
|
||||||
|
without blocking. They are **reversible** — any of them can be revisited
|
||||||
|
via an RFC in `docs/rfc/`. They are intentionally biased toward "ship
|
||||||
|
something free and reversible now, upgrade if needed."
|
||||||
|
|
||||||
|
Companion to:
|
||||||
|
|
||||||
|
- `learning_ai_uxui_web/docs/ROADMAP_2026.md` §10 TODO ledger (items #9–#13)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## #9 — Storybook hosting: **self-hosted, free**
|
||||||
|
|
||||||
|
**Decision.** Use Storybook 8 in each `@bytelyst/*` package, deployed
|
||||||
|
to Gitea Pages from CI. Skip Chromatic for now.
|
||||||
|
|
||||||
|
**Why.**
|
||||||
|
|
||||||
|
- Chromatic is $149/mo per project for the small plan and we have ~50
|
||||||
|
packages — not justifiable until adoption + design-team usage is
|
||||||
|
proven.
|
||||||
|
- Visual regression is already covered for product surfaces by the
|
||||||
|
showcase's Playwright `toHaveScreenshot` baselines (48 today).
|
||||||
|
- Self-hosted SB on Gitea Pages costs $0 and uses infrastructure that
|
||||||
|
already runs every other ByteLyst static site.
|
||||||
|
|
||||||
|
**Reopen if.** Designers start filing issues against story flaps that
|
||||||
|
visual-regression isn't catching, or we need cross-browser/device
|
||||||
|
coverage that local Playwright Chromium misses.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## #10 — `useChat` protocol: **adopt Vercel AI SDK shape, abstract transport**
|
||||||
|
|
||||||
|
**Decision.** `@bytelyst/ai-ui` exposes a `useChat` hook with the same
|
||||||
|
return shape as Vercel AI SDK's `useChat` (messages, input,
|
||||||
|
handleSubmit, stop, regenerate, isLoading, error). The **transport** is
|
||||||
|
pluggable — products inject a `fetcher` or `streamProtocol` so we are
|
||||||
|
not locked to Vercel's SSE wire format.
|
||||||
|
|
||||||
|
**Why.**
|
||||||
|
|
||||||
|
- Vercel AI SDK is the de-facto standard. Adopting the shape means
|
||||||
|
every React engineer who has written a chat UI in 2024–2026 already
|
||||||
|
knows our API.
|
||||||
|
- Abstracting transport gives us LysnrAI / JarvisJr / NoteLett the
|
||||||
|
freedom to use OpenAI-native, Anthropic-native, or our own server's
|
||||||
|
SSE protocol without forking the component.
|
||||||
|
|
||||||
|
**Reopen if.** Vercel changes the hook shape in a breaking way, or
|
||||||
|
products start needing capabilities (e.g., tool-call streaming
|
||||||
|
semantics) that the Vercel shape can't express.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## #11 — `react-auth` vs `auth-client`: **keep both for now, fold in Wave 7**
|
||||||
|
|
||||||
|
**Decision.** Leave `@bytelyst/react-auth` and `@bytelyst/auth-client`
|
||||||
|
as separate packages through Waves 1–6. Plan to merge `react-auth` into
|
||||||
|
`auth-client` (as `auth-client/react` subpath export) in Wave 7 once
|
||||||
|
both APIs have stabilized.
|
||||||
|
|
||||||
|
**Why.**
|
||||||
|
|
||||||
|
- `auth-client` is framework-agnostic; `react-auth` is the React
|
||||||
|
binding. Today they have different version cadences and that's fine.
|
||||||
|
- Merging now means a coordinated major-bump across both packages plus
|
||||||
|
every product app — too much churn during foundational waves.
|
||||||
|
|
||||||
|
**Reopen if.** Drift between the two packages causes a real bug or a
|
||||||
|
support burden between now and Wave 7.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## #12 — `dashboard-shell` vs `dashboard-components`: **keep both, defer merge**
|
||||||
|
|
||||||
|
**Decision.** Same as #11. Both packages stay independent through
|
||||||
|
Wave 6. Wave 7 re-evaluates whether to merge `dashboard-components`
|
||||||
|
into `dashboard-shell/*` subpath exports or vice versa.
|
||||||
|
|
||||||
|
**Why.** Same reasoning — the API contract isn't stable enough yet to
|
||||||
|
justify the merge cost.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## #13 — Mobile-native UI scope: **tokens-only sharing**
|
||||||
|
|
||||||
|
**Decision.** This roadmap (`@bytelyst/*` shared **client** packages
|
||||||
|
and the web showcase) does **not** include iOS/Android UI components.
|
||||||
|
Those live in `kotlin-platform-sdk` and `swift-platform-sdk` with their
|
||||||
|
own roadmaps. **The only thing we share across web/iOS/Android is
|
||||||
|
`@bytelyst/design-tokens`** — already generating Kotlin and Swift
|
||||||
|
output from a single JSON source.
|
||||||
|
|
||||||
|
**Why.**
|
||||||
|
|
||||||
|
- Cross-platform UI frameworks (React Native, KMP-Compose) impose
|
||||||
|
architectural constraints the web platform doesn't have.
|
||||||
|
- Token-only sharing is the proven model in the industry (Adobe Spectrum,
|
||||||
|
Shopify Polaris, Salesforce Lightning).
|
||||||
|
|
||||||
|
**Reopen if.** A product needs to ship a web + native experience in
|
||||||
|
parallel and the divergence cost gets large.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Status closing TODOs
|
||||||
|
|
||||||
|
These decisions close TODO ledger items #9–#13 in
|
||||||
|
`learning_ai_uxui_web/docs/ROADMAP_2026.md`. The roadmap is updated to
|
||||||
|
strike them through with a link to this document.
|
||||||
|
|
||||||
|
Last reviewed: 2026-05-27
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@bytelyst/dashboard-shell",
|
"name": "@bytelyst/dashboard-shell",
|
||||||
"version": "0.1.7",
|
"version": "0.2.0",
|
||||||
"description": "Configurable Next.js dashboard layout with sidebar, profile, billing, and settings pages",
|
"description": "Configurable Next.js dashboard layout with sidebar, profile, billing, and settings pages",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export function DashboardShell({
|
|||||||
features = {},
|
features = {},
|
||||||
onSignOut,
|
onSignOut,
|
||||||
onNavigate,
|
onNavigate,
|
||||||
|
routePrefix = '',
|
||||||
sidebarFooter,
|
sidebarFooter,
|
||||||
topBarActions,
|
topBarActions,
|
||||||
children,
|
children,
|
||||||
@ -43,6 +44,7 @@ export function DashboardShell({
|
|||||||
footer={sidebarFooter}
|
footer={sidebarFooter}
|
||||||
collapsed={sidebarCollapsed}
|
collapsed={sidebarCollapsed}
|
||||||
onToggleCollapse={() => setSidebarCollapsed(!sidebarCollapsed)}
|
onToggleCollapse={() => setSidebarCollapsed(!sidebarCollapsed)}
|
||||||
|
routePrefix={routePrefix}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Main content area */}
|
{/* Main content area */}
|
||||||
@ -54,6 +56,7 @@ export function DashboardShell({
|
|||||||
onSignOut={onSignOut}
|
onSignOut={onSignOut}
|
||||||
onNavigate={onNavigate}
|
onNavigate={onNavigate}
|
||||||
actions={topBarActions}
|
actions={topBarActions}
|
||||||
|
routePrefix={routePrefix}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Page content */}
|
{/* Page content */}
|
||||||
|
|||||||
@ -85,7 +85,9 @@ export function Sidebar({
|
|||||||
footer,
|
footer,
|
||||||
collapsed = false,
|
collapsed = false,
|
||||||
onToggleCollapse,
|
onToggleCollapse,
|
||||||
|
routePrefix = '',
|
||||||
}: SidebarProps): ReactNode {
|
}: SidebarProps): ReactNode {
|
||||||
|
const settingsHref = `${routePrefix}/settings`;
|
||||||
const sections: NavSection[] = isNavSections(nav) ? nav : [{ items: nav }];
|
const sections: NavSection[] = isNavSections(nav) ? nav : [{ items: nav }];
|
||||||
|
|
||||||
const isActive = (href: string) => pathname === href || pathname.startsWith(href + '/');
|
const isActive = (href: string) => pathname === href || pathname.startsWith(href + '/');
|
||||||
@ -93,7 +95,7 @@ export function Sidebar({
|
|||||||
// Add built-in settings nav if enabled and not already present
|
// Add built-in settings nav if enabled and not already present
|
||||||
const hasSettings = features.settings !== false;
|
const hasSettings = features.settings !== false;
|
||||||
const allItems = sections.flatMap(s => s.items);
|
const allItems = sections.flatMap(s => s.items);
|
||||||
const settingsExists = allItems.some(i => i.href === '/settings');
|
const settingsExists = allItems.some(i => i.href === settingsHref || i.href === '/settings');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside
|
<aside
|
||||||
@ -202,8 +204,8 @@ export function Sidebar({
|
|||||||
{hasSettings && !settingsExists && (
|
{hasSettings && !settingsExists && (
|
||||||
<div style={{ marginTop: 'auto' }}>
|
<div style={{ marginTop: 'auto' }}>
|
||||||
<NavLink
|
<NavLink
|
||||||
item={{ href: '/settings', label: 'Settings', icon: '⚙' }}
|
item={{ href: settingsHref, label: 'Settings', icon: '⚙' }}
|
||||||
active={isActive('/settings')}
|
active={isActive(settingsHref)}
|
||||||
collapsed={collapsed}
|
collapsed={collapsed}
|
||||||
onNavigate={onNavigate}
|
onNavigate={onNavigate}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -8,7 +8,11 @@ export function TopBar({
|
|||||||
onNavigate,
|
onNavigate,
|
||||||
actions,
|
actions,
|
||||||
onToggleSidebar,
|
onToggleSidebar,
|
||||||
|
routePrefix = '',
|
||||||
}: TopBarProps): ReactNode {
|
}: TopBarProps): ReactNode {
|
||||||
|
const profileHref = `${routePrefix}/profile`;
|
||||||
|
const billingHref = `${routePrefix}/billing`;
|
||||||
|
const settingsHref = `${routePrefix}/settings`;
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
|
||||||
const handleNav = (href: string) => {
|
const handleNav = (href: string) => {
|
||||||
@ -165,10 +169,10 @@ export function TopBar({
|
|||||||
{features.profile !== false && (
|
{features.profile !== false && (
|
||||||
<a
|
<a
|
||||||
data-testid="bl-shell-menu-profile"
|
data-testid="bl-shell-menu-profile"
|
||||||
href="/profile"
|
href={profileHref}
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleNav('/profile');
|
handleNav(profileHref);
|
||||||
}}
|
}}
|
||||||
style={menuItemStyle}
|
style={menuItemStyle}
|
||||||
>
|
>
|
||||||
@ -178,10 +182,10 @@ export function TopBar({
|
|||||||
{features.billing && (
|
{features.billing && (
|
||||||
<a
|
<a
|
||||||
data-testid="bl-shell-menu-billing"
|
data-testid="bl-shell-menu-billing"
|
||||||
href="/billing"
|
href={billingHref}
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleNav('/billing');
|
handleNav(billingHref);
|
||||||
}}
|
}}
|
||||||
style={menuItemStyle}
|
style={menuItemStyle}
|
||||||
>
|
>
|
||||||
@ -191,10 +195,10 @@ export function TopBar({
|
|||||||
{features.settings !== false && (
|
{features.settings !== false && (
|
||||||
<a
|
<a
|
||||||
data-testid="bl-shell-menu-settings"
|
data-testid="bl-shell-menu-settings"
|
||||||
href="/settings"
|
href={settingsHref}
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleNav('/settings');
|
handleNav(settingsHref);
|
||||||
}}
|
}}
|
||||||
style={menuItemStyle}
|
style={menuItemStyle}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -255,6 +255,38 @@ describe('TopBar', () => {
|
|||||||
fireEvent.click(screen.getByTestId('bl-shell-hamburger'));
|
fireEvent.click(screen.getByTestId('bl-shell-hamburger'));
|
||||||
expect(toggle).toHaveBeenCalledOnce();
|
expect(toggle).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── routePrefix (added in 0.2.0) ──────────────────────────────────────────
|
||||||
|
it('routePrefix prefixes profile/billing/settings hrefs', () => {
|
||||||
|
const onNavigate = vi.fn();
|
||||||
|
render(
|
||||||
|
<TopBar
|
||||||
|
user={USER}
|
||||||
|
onNavigate={onNavigate}
|
||||||
|
features={{ profile: true, billing: true, settings: true }}
|
||||||
|
routePrefix="/app"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
fireEvent.click(screen.getByTestId('bl-shell-user-menu-trigger'));
|
||||||
|
|
||||||
|
const profile = screen.getByTestId('bl-shell-menu-profile') as HTMLAnchorElement;
|
||||||
|
const billing = screen.getByTestId('bl-shell-menu-billing') as HTMLAnchorElement;
|
||||||
|
const settings = screen.getByTestId('bl-shell-menu-settings') as HTMLAnchorElement;
|
||||||
|
expect(profile.getAttribute('href')).toBe('/app/profile');
|
||||||
|
expect(billing.getAttribute('href')).toBe('/app/billing');
|
||||||
|
expect(settings.getAttribute('href')).toBe('/app/settings');
|
||||||
|
|
||||||
|
fireEvent.click(profile);
|
||||||
|
expect(onNavigate).toHaveBeenCalledWith('/app/profile');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('routePrefix defaults to empty string (back-compat with pre-0.2.0)', () => {
|
||||||
|
const onNavigate = vi.fn();
|
||||||
|
render(<TopBar user={USER} onNavigate={onNavigate} />);
|
||||||
|
fireEvent.click(screen.getByTestId('bl-shell-user-menu-trigger'));
|
||||||
|
fireEvent.click(screen.getByTestId('bl-shell-menu-profile'));
|
||||||
|
expect(onNavigate).toHaveBeenCalledWith('/profile');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── ProfilePage ──────────────────────────────────────────────────────────────
|
// ── ProfilePage ──────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@ -62,6 +62,15 @@ export interface DashboardShellProps {
|
|||||||
onSignOut?: () => void;
|
onSignOut?: () => void;
|
||||||
/** Called when a nav item is clicked (for SPA routers) */
|
/** Called when a nav item is clicked (for SPA routers) */
|
||||||
onNavigate?: (href: string) => void;
|
onNavigate?: (href: string) => void;
|
||||||
|
/**
|
||||||
|
* Prefix applied to all built-in route hrefs (`/profile`, `/billing`,
|
||||||
|
* `/settings`). For example, `routePrefix="/app"` produces
|
||||||
|
* `/app/profile`, `/app/billing`, `/app/settings`. Defaults to `""`
|
||||||
|
* (no prefix — preserves pre-0.2.0 behavior).
|
||||||
|
*
|
||||||
|
* @since 0.2.0
|
||||||
|
*/
|
||||||
|
routePrefix?: string;
|
||||||
/** Sidebar footer content (replaces default) */
|
/** Sidebar footer content (replaces default) */
|
||||||
sidebarFooter?: ReactNode;
|
sidebarFooter?: ReactNode;
|
||||||
/** Content to render in the top bar (right side) */
|
/** Content to render in the top bar (right side) */
|
||||||
@ -83,6 +92,8 @@ export interface SidebarProps {
|
|||||||
footer?: ReactNode;
|
footer?: ReactNode;
|
||||||
collapsed?: boolean;
|
collapsed?: boolean;
|
||||||
onToggleCollapse?: () => void;
|
onToggleCollapse?: () => void;
|
||||||
|
/** See {@link DashboardShellProps.routePrefix}. @since 0.2.0 */
|
||||||
|
routePrefix?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Top Bar Props ────────────────────────────────────────────────────────────
|
// ── Top Bar Props ────────────────────────────────────────────────────────────
|
||||||
@ -94,6 +105,8 @@ export interface TopBarProps {
|
|||||||
onNavigate?: (href: string) => void;
|
onNavigate?: (href: string) => void;
|
||||||
actions?: ReactNode;
|
actions?: ReactNode;
|
||||||
onToggleSidebar?: () => void;
|
onToggleSidebar?: () => void;
|
||||||
|
/** See {@link DashboardShellProps.routePrefix}. @since 0.2.0 */
|
||||||
|
routePrefix?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Built-in Page Props ──────────────────────────────────────────────────────
|
// ── Built-in Page Props ──────────────────────────────────────────────────────
|
||||||
|
|||||||
@ -78,6 +78,7 @@
|
|||||||
--at-motion-base: 220ms;
|
--at-motion-base: 220ms;
|
||||||
--at-motion-slow: 320ms;
|
--at-motion-slow: 320ms;
|
||||||
--at-easing-standard: cubic-bezier(0.2, 0.0, 0.2, 1);
|
--at-easing-standard: cubic-bezier(0.2, 0.0, 0.2, 1);
|
||||||
|
|
||||||
/* ── @bytelyst/ui alias layer (--bl-* → --at-*) ──────────────── */
|
/* ── @bytelyst/ui alias layer (--bl-* → --at-*) ──────────────── */
|
||||||
--bl-bg-canvas: var(--at-bg-canvas);
|
--bl-bg-canvas: var(--at-bg-canvas);
|
||||||
--bl-bg-elevated: var(--at-bg-elevated);
|
--bl-bg-elevated: var(--at-bg-elevated);
|
||||||
|
|||||||
@ -80,6 +80,7 @@
|
|||||||
--fm-motion-base: 220ms;
|
--fm-motion-base: 220ms;
|
||||||
--fm-motion-slow: 320ms;
|
--fm-motion-slow: 320ms;
|
||||||
--fm-easing-standard: cubic-bezier(0.2, 0.0, 0.2, 1);
|
--fm-easing-standard: cubic-bezier(0.2, 0.0, 0.2, 1);
|
||||||
|
|
||||||
/* ── @bytelyst/ui alias layer (--bl-* → --fm-*) ──────────────── */
|
/* ── @bytelyst/ui alias layer (--bl-* → --fm-*) ──────────────── */
|
||||||
--bl-bg-canvas: var(--fm-bg-canvas);
|
--bl-bg-canvas: var(--fm-bg-canvas);
|
||||||
--bl-bg-elevated: var(--fm-bg-elevated);
|
--bl-bg-elevated: var(--fm-bg-elevated);
|
||||||
|
|||||||
@ -69,6 +69,7 @@
|
|||||||
--jj-motion-base: 220ms;
|
--jj-motion-base: 220ms;
|
||||||
--jj-motion-slow: 320ms;
|
--jj-motion-slow: 320ms;
|
||||||
--jj-easing-standard: cubic-bezier(0.2, 0.0, 0.2, 1);
|
--jj-easing-standard: cubic-bezier(0.2, 0.0, 0.2, 1);
|
||||||
|
|
||||||
/* ── @bytelyst/ui alias layer (--bl-* → --jj-*) ──────────────── */
|
/* ── @bytelyst/ui alias layer (--bl-* → --jj-*) ──────────────── */
|
||||||
--bl-bg-canvas: var(--jj-bg-canvas);
|
--bl-bg-canvas: var(--jj-bg-canvas);
|
||||||
--bl-bg-elevated: var(--jj-bg-elevated);
|
--bl-bg-elevated: var(--jj-bg-elevated);
|
||||||
|
|||||||
@ -75,6 +75,7 @@
|
|||||||
--llm-motion-base: 220ms;
|
--llm-motion-base: 220ms;
|
||||||
--llm-motion-slow: 320ms;
|
--llm-motion-slow: 320ms;
|
||||||
--llm-easing-standard: cubic-bezier(0.2, 0.0, 0.2, 1);
|
--llm-easing-standard: cubic-bezier(0.2, 0.0, 0.2, 1);
|
||||||
|
|
||||||
/* ── @bytelyst/ui alias layer (--bl-* → --llm-*) ──────────────── */
|
/* ── @bytelyst/ui alias layer (--bl-* → --llm-*) ──────────────── */
|
||||||
--bl-bg-canvas: var(--llm-bg-canvas);
|
--bl-bg-canvas: var(--llm-bg-canvas);
|
||||||
--bl-bg-elevated: var(--llm-bg-elevated);
|
--bl-bg-elevated: var(--llm-bg-elevated);
|
||||||
|
|||||||
@ -74,6 +74,7 @@
|
|||||||
--lmg-motion-base: 220ms;
|
--lmg-motion-base: 220ms;
|
||||||
--lmg-motion-slow: 320ms;
|
--lmg-motion-slow: 320ms;
|
||||||
--lmg-easing-standard: cubic-bezier(0.2, 0.0, 0.2, 1);
|
--lmg-easing-standard: cubic-bezier(0.2, 0.0, 0.2, 1);
|
||||||
|
|
||||||
/* ── @bytelyst/ui alias layer (--bl-* → --lmg-*) ──────────────── */
|
/* ── @bytelyst/ui alias layer (--bl-* → --lmg-*) ──────────────── */
|
||||||
--bl-bg-canvas: var(--lmg-bg-canvas);
|
--bl-bg-canvas: var(--lmg-bg-canvas);
|
||||||
--bl-bg-elevated: var(--lmg-bg-elevated);
|
--bl-bg-elevated: var(--lmg-bg-elevated);
|
||||||
|
|||||||
@ -67,6 +67,7 @@
|
|||||||
--lys-motion-base: 220ms;
|
--lys-motion-base: 220ms;
|
||||||
--lys-motion-slow: 320ms;
|
--lys-motion-slow: 320ms;
|
||||||
--lys-easing-standard: cubic-bezier(0.2, 0.0, 0.2, 1);
|
--lys-easing-standard: cubic-bezier(0.2, 0.0, 0.2, 1);
|
||||||
|
|
||||||
/* ── @bytelyst/ui alias layer (--bl-* → --lys-*) ──────────────── */
|
/* ── @bytelyst/ui alias layer (--bl-* → --lys-*) ──────────────── */
|
||||||
--bl-bg-canvas: var(--lys-bg-canvas);
|
--bl-bg-canvas: var(--lys-bg-canvas);
|
||||||
--bl-bg-elevated: var(--lys-bg-elevated);
|
--bl-bg-elevated: var(--lys-bg-elevated);
|
||||||
|
|||||||
@ -70,6 +70,7 @@
|
|||||||
--ml-motion-base: 220ms;
|
--ml-motion-base: 220ms;
|
||||||
--ml-motion-slow: 320ms;
|
--ml-motion-slow: 320ms;
|
||||||
--ml-easing-standard: cubic-bezier(0.2, 0.0, 0.2, 1);
|
--ml-easing-standard: cubic-bezier(0.2, 0.0, 0.2, 1);
|
||||||
|
|
||||||
/* ── @bytelyst/ui alias layer (--bl-* → --ml-*) ──────────────── */
|
/* ── @bytelyst/ui alias layer (--bl-* → --ml-*) ──────────────── */
|
||||||
--bl-bg-canvas: var(--ml-bg-canvas);
|
--bl-bg-canvas: var(--ml-bg-canvas);
|
||||||
--bl-bg-elevated: var(--ml-bg-elevated);
|
--bl-bg-elevated: var(--ml-bg-elevated);
|
||||||
|
|||||||
@ -70,6 +70,7 @@
|
|||||||
--ng-motion-base: 220ms;
|
--ng-motion-base: 220ms;
|
||||||
--ng-motion-slow: 320ms;
|
--ng-motion-slow: 320ms;
|
||||||
--ng-easing-standard: cubic-bezier(0.2, 0.0, 0.2, 1);
|
--ng-easing-standard: cubic-bezier(0.2, 0.0, 0.2, 1);
|
||||||
|
|
||||||
/* ── @bytelyst/ui alias layer (--bl-* → --ng-*) ──────────────── */
|
/* ── @bytelyst/ui alias layer (--bl-* → --ng-*) ──────────────── */
|
||||||
--bl-bg-canvas: var(--ng-bg-canvas);
|
--bl-bg-canvas: var(--ng-bg-canvas);
|
||||||
--bl-bg-elevated: var(--ng-bg-elevated);
|
--bl-bg-elevated: var(--ng-bg-elevated);
|
||||||
|
|||||||
@ -75,6 +75,7 @@
|
|||||||
--nl-motion-base: 220ms;
|
--nl-motion-base: 220ms;
|
||||||
--nl-motion-slow: 320ms;
|
--nl-motion-slow: 320ms;
|
||||||
--nl-easing-standard: cubic-bezier(0.2, 0.0, 0.2, 1);
|
--nl-easing-standard: cubic-bezier(0.2, 0.0, 0.2, 1);
|
||||||
|
|
||||||
/* ── @bytelyst/ui alias layer (--bl-* → --nl-*) ──────────────── */
|
/* ── @bytelyst/ui alias layer (--bl-* → --nl-*) ──────────────── */
|
||||||
--bl-bg-canvas: var(--nl-bg-canvas);
|
--bl-bg-canvas: var(--nl-bg-canvas);
|
||||||
--bl-bg-elevated: var(--nl-bg-elevated);
|
--bl-bg-elevated: var(--nl-bg-elevated);
|
||||||
|
|||||||
@ -70,6 +70,7 @@
|
|||||||
--pp-motion-base: 220ms;
|
--pp-motion-base: 220ms;
|
||||||
--pp-motion-slow: 320ms;
|
--pp-motion-slow: 320ms;
|
||||||
--pp-easing-standard: cubic-bezier(0.2, 0.0, 0.2, 1);
|
--pp-easing-standard: cubic-bezier(0.2, 0.0, 0.2, 1);
|
||||||
|
|
||||||
/* ── @bytelyst/ui alias layer (--bl-* → --pp-*) ──────────────── */
|
/* ── @bytelyst/ui alias layer (--bl-* → --pp-*) ──────────────── */
|
||||||
--bl-bg-canvas: var(--pp-bg-canvas);
|
--bl-bg-canvas: var(--pp-bg-canvas);
|
||||||
--bl-bg-elevated: var(--pp-bg-elevated);
|
--bl-bg-elevated: var(--pp-bg-elevated);
|
||||||
|
|||||||
@ -74,6 +74,21 @@
|
|||||||
--ml-space-12: 48px;
|
--ml-space-12: 48px;
|
||||||
--ml-space-16: 64px;
|
--ml-space-16: 64px;
|
||||||
|
|
||||||
|
/* Density scale — overridden by [data-density] selectors below */
|
||||||
|
--bl-space-scale: 1;
|
||||||
|
--bl-space-0: 0;
|
||||||
|
--bl-space-1: calc(var(--ml-space-1) * var(--bl-space-scale));
|
||||||
|
--bl-space-2: calc(var(--ml-space-2) * var(--bl-space-scale));
|
||||||
|
--bl-space-3: calc(var(--ml-space-3) * var(--bl-space-scale));
|
||||||
|
--bl-space-4: calc(var(--ml-space-4) * var(--bl-space-scale));
|
||||||
|
--bl-space-5: calc(var(--ml-space-5) * var(--bl-space-scale));
|
||||||
|
--bl-space-6: calc(var(--ml-space-6) * var(--bl-space-scale));
|
||||||
|
--bl-space-7: calc(var(--ml-space-7) * var(--bl-space-scale));
|
||||||
|
--bl-space-8: calc(var(--ml-space-8) * var(--bl-space-scale));
|
||||||
|
--bl-space-10: calc(var(--ml-space-10) * var(--bl-space-scale));
|
||||||
|
--bl-space-12: calc(var(--ml-space-12) * var(--bl-space-scale));
|
||||||
|
--bl-space-16: calc(var(--ml-space-16) * var(--bl-space-scale));
|
||||||
|
|
||||||
--ml-radius-xs: 8px;
|
--ml-radius-xs: 8px;
|
||||||
--ml-radius-sm: 12px;
|
--ml-radius-sm: 12px;
|
||||||
--ml-radius-md: 16px;
|
--ml-radius-md: 16px;
|
||||||
@ -114,3 +129,8 @@
|
|||||||
--ml-focus-ring: rgba(90,140,255,0.35);
|
--ml-focus-ring: rgba(90,140,255,0.35);
|
||||||
--ml-overlay-scrim: rgba(10,13,23,0.5);
|
--ml-overlay-scrim: rgba(10,13,23,0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Density tier — overrides --bl-space-scale */
|
||||||
|
[data-density="compact"] { --bl-space-scale: 0.875; }
|
||||||
|
[data-density="comfortable"] { --bl-space-scale: 1; }
|
||||||
|
[data-density="spacious"] { --bl-space-scale: 1.125; }
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@bytelyst/design-tokens",
|
"name": "@bytelyst/design-tokens",
|
||||||
"version": "0.1.8",
|
"version": "0.2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
|
|||||||
@ -132,12 +132,27 @@ function generateCSS(): string {
|
|||||||
}
|
}
|
||||||
lines.push('');
|
lines.push('');
|
||||||
|
|
||||||
// Spacing
|
// Spacing — raw (--ml-*) tier, fixed pixel values
|
||||||
for (const [key, value] of Object.entries(tokens.spacing)) {
|
for (const [key, value] of Object.entries(tokens.spacing)) {
|
||||||
lines.push(` --ml-space-${key}: ${value === 0 ? '0' : `${value}px`};`);
|
lines.push(` --ml-space-${key}: ${value === 0 ? '0' : `${value}px`};`);
|
||||||
}
|
}
|
||||||
lines.push('');
|
lines.push('');
|
||||||
|
|
||||||
|
// Density-aware spacing tier (--bl-*) — added in design-tokens 0.2.0.
|
||||||
|
// Each --bl-space-N = --ml-space-N × var(--bl-space-scale).
|
||||||
|
// Consumers override --bl-space-scale via [data-density="..."] on <html>
|
||||||
|
// (or any ancestor) to switch between compact / comfortable / spacious.
|
||||||
|
lines.push(' /* Density scale — overridden by [data-density] selectors below */');
|
||||||
|
lines.push(' --bl-space-scale: 1;');
|
||||||
|
for (const [key, value] of Object.entries(tokens.spacing)) {
|
||||||
|
if (value === 0) {
|
||||||
|
lines.push(` --bl-space-${key}: 0;`);
|
||||||
|
} else {
|
||||||
|
lines.push(` --bl-space-${key}: calc(var(--ml-space-${key}) * var(--bl-space-scale));`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
// Radius
|
// Radius
|
||||||
for (const [key, value] of Object.entries(tokens.radius)) {
|
for (const [key, value] of Object.entries(tokens.radius)) {
|
||||||
lines.push(` --ml-radius-${key}: ${value}px;`);
|
lines.push(` --ml-radius-${key}: ${value}px;`);
|
||||||
@ -179,6 +194,15 @@ function generateCSS(): string {
|
|||||||
}
|
}
|
||||||
lines.push('}', '');
|
lines.push('}', '');
|
||||||
|
|
||||||
|
// Density overrides — drive --bl-space-scale via [data-density="..."].
|
||||||
|
// Default (no attribute or `comfortable`) keeps scale = 1 from :root.
|
||||||
|
// Added in design-tokens 0.2.0.
|
||||||
|
lines.push('/* Density tier — overrides --bl-space-scale */');
|
||||||
|
lines.push('[data-density="compact"] { --bl-space-scale: 0.875; }');
|
||||||
|
lines.push('[data-density="comfortable"] { --bl-space-scale: 1; }');
|
||||||
|
lines.push('[data-density="spacious"] { --bl-space-scale: 1.125; }');
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@bytelyst/react-auth",
|
"name": "@bytelyst/react-auth",
|
||||||
"version": "0.1.8",
|
"version": "0.2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
@ -21,7 +21,7 @@
|
|||||||
"react": ">=18.0.0"
|
"react": ">=18.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bytelyst/api-client": "workspace:*"
|
"@bytelyst/api-client": "workspace:^"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user