diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..30872034 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,65 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.1.0] - 2026-05-02 + +### Breaking Changes + +**Major Theming System Refactor** + +- Removed all shadcn-style theme variables in favor of pure LSD design tokens + - Removed: `--background`, `--foreground`, `--primary`, `--primary-foreground`, `--secondary`, `--secondary-foreground`, `--accent`, `--accent-foreground`, `--destructive`, `--destructive-foreground`, `--muted`, `--muted-foreground`, `--border`, `--input`, `--ring` +- Renamed design tokens for semantic clarity + - Double-prefix pattern (e.g., `text-lsd-text-secondary`) standardized to single prefix (e.g., `text-secondary`) + - Updated all component styling to use new variable naming convention +- Theme structure reorganized to support nested light/dark variants with accent themes +- Custom theme creation now requires using LSD-specific variable names + +### Migration + +If you have custom themes or override theme variables, you'll need to update them: + +1. Replace shadcn-style variables with LSD tokens +2. Update variable names to match the new single-prefix pattern +3. Review custom theme definitions against the new theme structure + +### Features + +- **Improved Theme System**: Redesigned CSS custom properties for better semantic clarity and consistency +- **Enhanced Theming Architecture**: Better support for nested light/dark variants with accent theme inheritance + +### Improvements + +- Standardized variable naming conventions across all 40+ components +- Improved theme inheritance and scoping behavior +- Enhanced accessibility with consistent semantic token names +- Better separation of concerns between color modes and accent themes + +### Deprecations + +- None removed (shadcn-style variables were fully removed, not deprecated) + +--- + +## Previous Versions + +### [1.0.0] - Initial Release + +- Initial release of LSD component library +- 38+ accessible UI components +- Built on React 19+ with Radix UI +- Support for light/dark modes and 4 accent themes (Teal, Nord, Terracotta, Catppuccin) +- Monochrome color palette with semantic styling +- Comprehensive documentation site +- Full TypeScript support + +--- + +## Version Reference + +- **1.1.0** - Current release (theming refactor) +- **1.0.0** - Initial release diff --git a/package.json b/package.json index 8eb3b358..22efb997 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nipsys/lsd-monorepo", - "version": "1.0.1", + "version": "1.1.0", "private": true, "description": "Monorepo for the @nipsys/lsd component library and documentation", "license": "MIT", diff --git a/packages/lsd-docs/app/components/SidebarWrapper.tsx b/packages/lsd-docs/app/components/SidebarWrapper.tsx index 93678c15..7add2ef5 100644 --- a/packages/lsd-docs/app/components/SidebarWrapper.tsx +++ b/packages/lsd-docs/app/components/SidebarWrapper.tsx @@ -28,7 +28,7 @@ interface SidebarWrapperProps { export default function SidebarWrapper({ currentPath, children }: SidebarWrapperProps) { return ( - + @@ -39,7 +39,7 @@ export default function SidebarWrapper({ currentPath, children }: SidebarWrapper
@nipsys/lsd - v1.0.1 + v1.1.0
diff --git a/packages/lsd-docs/app/components/docs/IframeExample.tsx b/packages/lsd-docs/app/components/docs/IframeExample.tsx index 644e8762..50012136 100644 --- a/packages/lsd-docs/app/components/docs/IframeExample.tsx +++ b/packages/lsd-docs/app/components/docs/IframeExample.tsx @@ -13,7 +13,7 @@ export const IframeExample = forwardRef( ({ src, title, size = 'md', className, ...props }, ref) => { const heightClass = { sm: 'min-h-[128px]', - md: 'min-h-[256px]', + md: 'min-h-[280px]', lg: 'min-h-[450px]', xl: 'min-h-[600px]', }[size]; diff --git a/packages/lsd-docs/app/components/docs/useSendIframes.ts b/packages/lsd-docs/app/components/docs/useSendIframes.ts index fd4b8863..c6b94c4b 100644 --- a/packages/lsd-docs/app/components/docs/useSendIframes.ts +++ b/packages/lsd-docs/app/components/docs/useSendIframes.ts @@ -22,12 +22,6 @@ export function useSendThemeToIframes(...iframeRefs: React.RefObject { const params: ExampleParams = { theme, accent, font }; - console.log( - '[useSendThemeToIframes] Sending params on change:', - params, - 'to iframes:', - iframeRefs.length - ); iframeRefs.forEach(ref => void sendToIframe(ref.current, 'example-params', params)); }, [theme, accent, font, iframeRefs]); diff --git a/packages/lsd-docs/app/components/docs/useSendThemeToIframes.ts b/packages/lsd-docs/app/components/docs/useSendThemeToIframes.ts index 0bcd4909..9b36cfd9 100644 --- a/packages/lsd-docs/app/components/docs/useSendThemeToIframes.ts +++ b/packages/lsd-docs/app/components/docs/useSendThemeToIframes.ts @@ -4,11 +4,9 @@ import { type ExampleParams, useIframeMessageListener } from './useIframeSync'; export function useSendThemeToIframes() { useIframeMessageListener('example-params', (params: ExampleParams) => { - console.log('[IFRAME] Received theme params:', params); const root = document.documentElement; if (params.theme) { - console.log('[IFRAME] Applying theme:', params.theme); if (params.theme === 'dark') { root.classList.add('dark'); } else { @@ -17,7 +15,6 @@ export function useSendThemeToIframes() { } if (params.accent) { - console.log('[IFRAME] Applying accent:', params.accent); if (params.accent === 'monochrome') { root.removeAttribute('data-theme'); } else { @@ -26,7 +23,6 @@ export function useSendThemeToIframes() { } if (params.font) { - console.log('[IFRAME] Applying font:', params.font); root.classList.remove('font-serif', 'font-sans', 'font-mono'); if (params.font === 'serif') { root.classList.add('font-serif'); diff --git a/packages/lsd-docs/app/config/navigation.ts b/packages/lsd-docs/app/config/navigation.ts index 7ab17a89..d22102ce 100644 --- a/packages/lsd-docs/app/config/navigation.ts +++ b/packages/lsd-docs/app/config/navigation.ts @@ -32,7 +32,7 @@ export const navItems: NavGroup[] = [ { title: 'Design Tokens', items: [ - { title: 'Colours', url: '/design-tokens/colours', icon: PaletteIcon }, + { title: 'Colors', url: '/design-tokens/colors', icon: PaletteIcon }, { title: 'Typography', url: '/design-tokens/typography', diff --git a/packages/lsd-docs/app/design-tokens/colours/ColorValue.tsx b/packages/lsd-docs/app/design-tokens/colors/ColorValue.tsx similarity index 100% rename from packages/lsd-docs/app/design-tokens/colours/ColorValue.tsx rename to packages/lsd-docs/app/design-tokens/colors/ColorValue.tsx diff --git a/packages/lsd-docs/app/design-tokens/colours/page.tsx b/packages/lsd-docs/app/design-tokens/colors/page.tsx similarity index 74% rename from packages/lsd-docs/app/design-tokens/colours/page.tsx rename to packages/lsd-docs/app/design-tokens/colors/page.tsx index 6263c0c0..d77e8cd8 100644 --- a/packages/lsd-docs/app/design-tokens/colours/page.tsx +++ b/packages/lsd-docs/app/design-tokens/colors/page.tsx @@ -12,23 +12,12 @@ export default function ColorsPage() { return ( - - - Colours are tools for signalling, not branding. LSD's palette is chosen for maximum - contrast and accessibility across all conditions. - - - - High contrast isn't a design choice — it's a commitment to inclusivity. - - - - + Primary marks essential actions and focal points — where the user needs to look to move forward. @@ -54,22 +43,23 @@ export default function ColorsPage() {
- Primary Foreground + Primary Content - --lsd-primary-foreground: + --lsd-primary-content:
- + - Text colours for primary content and supporting information, optimized for readability. + Text colors for primary content and supporting information, optimized for readability. + These same colors are used for icons to maintain visual consistency.
@@ -77,13 +67,13 @@ export default function ColorsPage() {
- Text Primary + Text Neutral - --lsd-text-primary: + --lsd-text-neutral: @@ -105,9 +95,9 @@ export default function ColorsPage() {
- + - Surface and border colours define structure and hierarchy across the interface. + Background and border colors define structure and hierarchy across the interface.
@@ -115,51 +105,28 @@ export default function ColorsPage() {
- - Surface - - - --lsd-surface: - - - - - - -
- Border + Background - --lsd-border: + --lsd-background: -
- - - - - Icon colours maintain visual consistency across all interactive and decorative elements. - -
- Icon Primary + Foreground - --lsd-icon-primary: + --lsd-foreground: @@ -168,22 +135,22 @@ export default function ColorsPage() {
- Icon Secondary + Border - --lsd-icon-secondary: + --lsd-border:
- + - Semantic colours communicate state — success, warning, destructive actions, and + Semantic colors communicate state — success, warning, destructive actions, and information. @@ -250,9 +217,9 @@ export default function ColorsPage() {
- + - Text colours for semantic states. + Text colors for semantic states.
@@ -260,13 +227,13 @@ export default function ColorsPage() {
Success Text - --lsd-success-text: + --lsd-text-success: @@ -275,13 +242,13 @@ export default function ColorsPage() {
Destructive Text - --lsd-destructive-text: + --lsd-text-destructive: @@ -290,13 +257,13 @@ export default function ColorsPage() {
Warning Text - --lsd-warning-text: + --lsd-text-warning: @@ -305,13 +272,13 @@ export default function ColorsPage() {
Info Text - --lsd-info-text: + --lsd-text-info: diff --git a/packages/lsd-docs/app/design-tokens/typography/page.tsx b/packages/lsd-docs/app/design-tokens/typography/page.tsx index 7008414c..90503bfd 100644 --- a/packages/lsd-docs/app/design-tokens/typography/page.tsx +++ b/packages/lsd-docs/app/design-tokens/typography/page.tsx @@ -17,18 +17,6 @@ export default function TypographyPage() { /> - - - Typography is how we speak to users. LSD's system prioritises readability, - accessibility, and the hierarchy of ideas over trends. - - - - The content comes first — everything else serves it. Our scale is designed for - real‑world reading across devices and conditions. - - - LSD offers three font family stacks for different use cases. Choose what serves your @@ -249,8 +237,8 @@ function MyComponent() { ) { + return ( +
+
{children}
+
+ ); +} diff --git a/packages/lsd-docs/app/examples/progress/basic/page.tsx b/packages/lsd-docs/app/examples/progress/basic/page.tsx index bde6a3c8..7568f2b3 100644 --- a/packages/lsd-docs/app/examples/progress/basic/page.tsx +++ b/packages/lsd-docs/app/examples/progress/basic/page.tsx @@ -25,7 +25,7 @@ export function Example() { Controlled Progress
-
+
); + const button = screen.getByTestId('button'); + expect(button).toBeInTheDocument(); + expect(button).toHaveClass('lsd:bg-lsd-primary'); + }); + + it('renders in dark mode', () => { + render( +
+ +
+ ); + const button = screen.getByTestId('button'); + expect(button).toBeInTheDocument(); + expect(button).toHaveClass('lsd:bg-lsd-primary'); + }); + + it('renders with teal theme variant', () => { + render( +
+ +
+ ); + const button = screen.getByTestId('button'); + expect(button).toBeInTheDocument(); + expect(button).toHaveClass('lsd:bg-lsd-primary'); + }); + + it('renders with nord theme variant', () => { + render( +
+ +
+ ); + const button = screen.getByTestId('button'); + expect(button).toBeInTheDocument(); + expect(button).toHaveClass('lsd:bg-lsd-primary'); + }); + + it('renders with dark mode and nord theme', () => { + render( +
+ +
+ ); + const button = screen.getByTestId('button'); + expect(button).toBeInTheDocument(); + expect(button).toHaveClass('lsd:bg-lsd-primary'); + }); + + it('renders destructive variant in default theme', () => { + render( + + ); + const button = screen.getByTestId('button-destructive-default'); + expect(button).toBeInTheDocument(); + expect(button).toHaveClass('lsd:bg-lsd-destructive'); + }); + + it('renders destructive variant in teal theme', () => { + render( +
+ +
+ ); + const button = screen.getByTestId('button-destructive-teal'); + expect(button).toBeInTheDocument(); + expect(button).toHaveClass('lsd:bg-lsd-destructive'); + }); + + it('renders destructive variant in nord theme', () => { + render( +
+ +
+ ); + const button = screen.getByTestId('button-destructive-nord'); + expect(button).toBeInTheDocument(); + expect(button).toHaveClass('lsd:bg-lsd-destructive'); + }); + + it('renders success variant in default dark mode', () => { + render( +
+ +
+ ); + const button = screen.getByTestId('button-success-dark'); + expect(button).toBeInTheDocument(); + expect(button).toHaveClass('lsd:bg-lsd-success'); + }); + + it('renders success variant in teal dark mode', () => { + render( +
+ +
+ ); + const button = screen.getByTestId('button-success-teal-dark'); + expect(button).toBeInTheDocument(); + expect(button).toHaveClass('lsd:bg-lsd-success'); + }); + + it('renders success variant in nord dark mode', () => { + render( +
+ +
+ ); + const button = screen.getByTestId('button-success-nord-dark'); + expect(button).toBeInTheDocument(); + expect(button).toHaveClass('lsd:bg-lsd-success'); + }); + + it('renders success variant in slate dark mode', () => { + render( +
+ +
+ ); + const button = screen.getByTestId('button-success-slate-dark'); + expect(button).toBeInTheDocument(); + expect(button).toHaveClass('lsd:bg-lsd-success'); + }); + + it('renders filled variant in teal theme', () => { + render( +
+ +
+ ); + expect(screen.getByTestId('button-filled-teal')).toBeInTheDocument(); + }); + + it('renders outlined variant in teal theme', () => { + render( +
+ +
+ ); + expect(screen.getByTestId('button-outlined-teal')).toBeInTheDocument(); + }); + + it('renders filled-rounded variant in teal theme', () => { + render( +
+ +
+ ); + expect(screen.getByTestId('button-filled-rounded-teal')).toBeInTheDocument(); + }); + + it('renders outlined-rounded variant in teal theme', () => { + render( +
+ +
+ ); + expect(screen.getByTestId('button-outlined-rounded-teal')).toBeInTheDocument(); + }); + + it('renders link variant in teal theme', () => { + render( +
+ +
+ ); + expect(screen.getByTestId('button-link-teal')).toBeInTheDocument(); + }); + + it('renders ghost variant in teal theme', () => { + render( +
+ +
+ ); + expect(screen.getByTestId('button-ghost-teal')).toBeInTheDocument(); + }); + + it('renders ghost-rounded variant in teal theme', () => { + render( +
+ +
+ ); + expect(screen.getByTestId('button-ghost-rounded-teal')).toBeInTheDocument(); + }); + + it('renders destructive variant in nord dark mode', () => { + render( +
+ +
+ ); + const button = screen.getByTestId('button-destructive-nord-dark'); + expect(button).toBeInTheDocument(); + expect(button).toHaveClass('lsd:bg-lsd-destructive'); + }); + + it('renders success variant in nord dark mode', () => { + render( +
+ +
+ ); + const button = screen.getByTestId('button-success-nord-dark-2'); + expect(button).toBeInTheDocument(); + expect(button).toHaveClass('lsd:bg-lsd-success'); + }); + + it('renders small size in default theme', () => { + render( + + ); + expect(screen.getByTestId('button-sm-default')).toBeInTheDocument(); + }); + + it('renders small size in teal theme', () => { + render( +
+ +
+ ); + expect(screen.getByTestId('button-sm-teal')).toBeInTheDocument(); + }); + + it('renders medium size in nord theme', () => { + render( +
+ +
+ ); + expect(screen.getByTestId('button-md-nord')).toBeInTheDocument(); + }); + + it('renders large size in slate theme', () => { + render( +
+ +
+ ); + expect(screen.getByTestId('button-lg-slate')).toBeInTheDocument(); + }); + + it('renders square-sm size in teal theme', () => { + render( +
+ +
+ ); + expect(screen.getByTestId('button-square-sm-teal')).toBeInTheDocument(); + }); + + it('renders square-md size in nord theme', () => { + render( +
+ +
+ ); + expect(screen.getByTestId('button-square-md-nord')).toBeInTheDocument(); + }); + + it('renders square-lg size in slate theme', () => { + render( +
+ +
+ ); + expect(screen.getByTestId('button-square-lg-slate')).toBeInTheDocument(); + }); + }); + + describe('Card component theme switching', () => { + it('renders with default theme (monochrome light)', () => { + render( + + + Test Title + + + ); + const card = screen.getByTestId('card'); + expect(card).toBeInTheDocument(); + expect(card).toHaveClass('lsd:bg-lsd-foreground'); + expect(card).toHaveClass('lsd:border-lsd-border'); + }); + + it('renders in dark mode', () => { + render( +
+ + + Dark Mode Card + + +
+ ); + const card = screen.getByTestId('card'); + expect(card).toBeInTheDocument(); + expect(card).toHaveClass('lsd:bg-lsd-foreground'); + expect(card).toHaveClass('lsd:border-lsd-border'); + }); + + it('renders with teal theme variant', () => { + render( +
+ + Teal Theme Content + +
+ ); + const card = screen.getByTestId('card'); + expect(card).toBeInTheDocument(); + expect(card).toHaveClass('lsd:bg-lsd-foreground'); + }); + + it('renders with nord theme variant', () => { + render( +
+ + Nord Theme Content + +
+ ); + const card = screen.getByTestId('card'); + expect(card).toBeInTheDocument(); + expect(card).toHaveClass('lsd:bg-lsd-foreground'); + }); + + it('renders with slate theme variant', () => { + render( +
+ + Slate Theme Content + +
+ ); + const card = screen.getByTestId('card'); + expect(card).toBeInTheDocument(); + expect(card).toHaveClass('lsd:bg-lsd-foreground'); + }); + + it('renders with dark mode and slate theme', () => { + render( +
+ + + Dark Slate Card + Description + + Content + +
+ ); + const card = screen.getByTestId('card'); + expect(card).toBeInTheDocument(); + expect(card).toHaveClass('lsd:bg-lsd-foreground'); + }); + + it('renders complete card structure in default theme', () => { + render( + + + Card Title + Card Description + + + + + Card Content + + + + + ); + const card = screen.getByTestId('card-default'); + expect(card).toBeInTheDocument(); + expect(card).toHaveClass('lsd:bg-lsd-foreground'); + }); + + it('renders complete card structure in teal theme', () => { + render( +
+ + + Card Title + Card Description + + + + + Card Content + + + + +
+ ); + const card = screen.getByTestId('card-teal'); + expect(card).toBeInTheDocument(); + expect(card).toHaveClass('lsd:bg-lsd-foreground'); + }); + + it('renders complete card structure in nord theme', () => { + render( +
+ + + Card Title + Card Description + + Card Content + +
+ ); + const card = screen.getByTestId('card-nord'); + expect(card).toBeInTheDocument(); + expect(card).toHaveClass('lsd:bg-lsd-foreground'); + }); + + it('renders complete card structure in slate theme', () => { + render( +
+ + + Card Title + + Card Content + +
+ ); + const card = screen.getByTestId('card-slate'); + expect(card).toBeInTheDocument(); + expect(card).toHaveClass('lsd:bg-lsd-foreground'); + }); + + it('renders card subcomponents in dark mode with nord theme', () => { + render( +
+ + + Title + Description + + Content + Footer + +
+ ); + expect(screen.getByTestId('card')).toBeInTheDocument(); + expect(screen.getByTestId('header')).toBeInTheDocument(); + expect(screen.getByTestId('content')).toBeInTheDocument(); + expect(screen.getByTestId('footer')).toBeInTheDocument(); + }); + + it('renders card with custom className in teal theme', () => { + render( +
+ + Content + +
+ ); + const card = screen.getByTestId('card-teal-custom'); + expect(card).toHaveClass('custom-class'); + }); + + it('renders card with custom className in nord theme', () => { + render( +
+ + Content + +
+ ); + const card = screen.getByTestId('card-nord-custom'); + expect(card).toHaveClass('custom-class'); + }); + }); + + describe('Badge component theme switching', () => { + it('renders with default theme (monochrome light)', () => { + render(Badge); + const badge = screen.getByTestId('badge'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveClass('lsd:bg-lsd-primary'); + }); + + it('renders in dark mode', () => { + render( +
+ Dark Mode Badge +
+ ); + const badge = screen.getByTestId('badge'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveClass('lsd:bg-lsd-primary'); + }); + + it('renders with teal theme variant', () => { + render( +
+ Teal Theme Badge +
+ ); + const badge = screen.getByTestId('badge'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveClass('lsd:bg-lsd-primary'); + }); + + it('renders with nord theme variant', () => { + render( +
+ Nord Theme Badge +
+ ); + const badge = screen.getByTestId('badge'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveClass('lsd:bg-lsd-primary'); + }); + + it('renders with slate theme', () => { + render( +
+ Slate Theme Badge +
+ ); + const badge = screen.getByTestId('badge'); + expect(badge).toBeInTheDocument(); + }); + + it('renders with dark mode and teal theme', () => { + render( +
+ Dark Teal Badge +
+ ); + const badge = screen.getByTestId('badge'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveClass('lsd:bg-lsd-primary'); + }); + + it('renders filled variant in default theme', () => { + render( + + Filled + + ); + const badge = screen.getByTestId('badge-filled-default'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveClass('lsd:bg-lsd-primary'); + }); + + it('renders filled variant in teal theme', () => { + render( +
+ + Filled + +
+ ); + const badge = screen.getByTestId('badge-filled-teal'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveClass('lsd:bg-lsd-primary'); + }); + + it('renders filled variant in nord theme', () => { + render( +
+ + Filled + +
+ ); + const badge = screen.getByTestId('badge-filled-nord'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveClass('lsd:bg-lsd-primary'); + }); + + it('renders filled variant in slate theme', () => { + render( +
+ + Filled + +
+ ); + const badge = screen.getByTestId('badge-filled-slate'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveClass('lsd:bg-lsd-primary'); + }); + + it('renders outlined variant in default dark mode', () => { + render( +
+ + Outlined + +
+ ); + const badge = screen.getByTestId('badge-outlined-dark'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveClass('lsd:bg-transparent'); + }); + + it('renders outlined variant in teal dark mode', () => { + render( +
+ + Outlined + +
+ ); + const badge = screen.getByTestId('badge-outlined-teal-dark'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveClass('lsd:bg-transparent'); + }); + + it('renders outlined variant in nord dark mode', () => { + render( +
+ + Outlined + +
+ ); + const badge = screen.getByTestId('badge-outlined-nord-dark'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveClass('lsd:bg-transparent'); + }); + + it('renders small size in nord theme', () => { + render( +
+ + Small + +
+ ); + const badge = screen.getByTestId('badge-sm-nord'); + expect(badge).toBeInTheDocument(); + }); + + it('renders medium size in nord theme', () => { + render( +
+ + Medium + +
+ ); + const badge = screen.getByTestId('badge-md-nord'); + expect(badge).toBeInTheDocument(); + }); + + it('renders dot variant in default theme', () => { + render(); + const badge = screen.getByTestId('badge-dot-default'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveClass('lsd:rounded-full'); + expect(badge).toHaveClass('lsd:p-0'); + }); + + it('renders dot variant in teal theme', () => { + render( +
+ +
+ ); + const badge = screen.getByTestId('badge-dot-teal'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveClass('lsd:rounded-full'); + expect(badge).toHaveClass('lsd:p-0'); + }); + + it('renders dot variant in nord theme', () => { + render( +
+ +
+ ); + const badge = screen.getByTestId('badge-dot-nord'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveClass('lsd:rounded-full'); + expect(badge).toHaveClass('lsd:p-0'); + }); + + it('renders dot variant in slate theme', () => { + render( +
+ +
+ ); + const badge = screen.getByTestId('badge-dot-slate'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveClass('lsd:rounded-full'); + expect(badge).toHaveClass('lsd:p-0'); + }); + + it('renders badge with icon in teal dark mode', () => { + render( +
+ ★} data-testid="badge-icon-teal"> + With Icon + +
+ ); + expect(screen.getByTestId('badge-icon-teal')).toBeInTheDocument(); + expect(screen.getByTestId('icon')).toBeInTheDocument(); + }); + + it('renders badge with icon in nord dark mode', () => { + render( +
+ ★} data-testid="badge-icon-nord"> + With Icon + +
+ ); + expect(screen.getByTestId('badge-icon-nord')).toBeInTheDocument(); + expect(screen.getByTestId('icon')).toBeInTheDocument(); + }); + + it('renders badge with icon in slate dark mode', () => { + render( +
+ ★} data-testid="badge-icon-slate"> + With Icon + +
+ ); + expect(screen.getByTestId('badge-icon-slate')).toBeInTheDocument(); + expect(screen.getByTestId('icon')).toBeInTheDocument(); + }); + + it('renders filled variant in slate dark mode', () => { + render( +
+ + Filled + +
+ ); + const badge = screen.getByTestId('badge-filled-slate-dark'); + expect(badge).toBeInTheDocument(); + }); + + it('renders outlined variant in slate dark mode', () => { + render( +
+ + Outlined + +
+ ); + const badge = screen.getByTestId('badge-outlined-slate-dark'); + expect(badge).toBeInTheDocument(); + }); + + it('renders dot variant in slate dark mode', () => { + render( +
+ +
+ ); + const badge = screen.getByTestId('badge-dot-slate-dark'); + expect(badge).toBeInTheDocument(); + }); + + it('renders clickable badge in default theme', () => { + render( + {}} data-testid="badge-clickable-default"> + Clickable + + ); + const badge = screen.getByTestId('badge-clickable-default'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveAttribute('role', 'button'); + }); + + it('renders clickable badge in teal theme', () => { + render( +
+ {}} data-testid="badge-clickable-teal"> + Clickable + +
+ ); + const badge = screen.getByTestId('badge-clickable-teal'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveAttribute('role', 'button'); + }); + + it('renders clickable badge in nord theme', () => { + render( +
+ {}} data-testid="badge-clickable-nord"> + Clickable + +
+ ); + const badge = screen.getByTestId('badge-clickable-nord'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveAttribute('role', 'button'); + }); + + it('renders clickable badge in slate theme', () => { + render( +
+ {}} data-testid="badge-clickable-slate"> + Clickable + +
+ ); + const badge = screen.getByTestId('badge-clickable-slate'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveAttribute('role', 'button'); + }); + }); + + describe('Cross-component theme consistency', () => { + it('renders Button, Card, and Badge together in teal theme', () => { + render( +
+ + + Card with Components + + + + Badge + + +
+ ); + expect(screen.getByTestId('card')).toBeInTheDocument(); + expect(screen.getByTestId('button')).toBeInTheDocument(); + expect(screen.getByTestId('badge')).toBeInTheDocument(); + }); + + it('renders all components in nord dark mode', () => { + render( +
+ + + Dark Nordic Card + + + + + Outlined Badge + + + +
+ ); + expect(screen.getByTestId('card')).toBeInTheDocument(); + expect(screen.getByTestId('button')).toBeInTheDocument(); + expect(screen.getByTestId('badge')).toBeInTheDocument(); + }); + + it('renders destructive button in default theme', () => { + render( + + ); + expect(screen.getByTestId('btn-destructive')).toHaveClass('lsd:bg-lsd-destructive'); + }); + + it('renders badge in default theme', () => { + render(Badge); + expect(screen.getByTestId('badge-default')).toBeInTheDocument(); + }); + + it('renders destructive button in teal theme', () => { + render( +
+ + Badge +
+ ); + expect(screen.getByTestId('btn-destructive-teal')).toHaveClass('lsd:bg-lsd-destructive'); + expect(screen.getByTestId('badge-teal')).toBeInTheDocument(); + }); + + it('renders destructive button in nord theme', () => { + render( +
+ + Badge +
+ ); + expect(screen.getByTestId('btn-destructive-nord')).toHaveClass('lsd:bg-lsd-destructive'); + expect(screen.getByTestId('badge-nord')).toBeInTheDocument(); + }); + + it('renders destructive button in slate theme', () => { + render( +
+ + Badge +
+ ); + expect(screen.getByTestId('btn-destructive-slate')).toHaveClass('lsd:bg-lsd-destructive'); + expect(screen.getByTestId('badge-slate')).toBeInTheDocument(); + }); + + it('handles theme switching from teal to nord', () => { + const { rerender } = render( +
+ + Teal Badge +
+ ); + expect(screen.getByTestId('button')).toBeInTheDocument(); + expect(screen.getByTestId('badge')).toBeInTheDocument(); + + rerender( +
+ + Nord Badge +
+ ); + expect(screen.getByTestId('button')).toBeInTheDocument(); + expect(screen.getByTestId('badge')).toBeInTheDocument(); + }); + + it('handles theme switching from light to dark mode', () => { + const { rerender } = render( +
+ + + Content + +
+ ); + expect(screen.getByTestId('button')).toBeInTheDocument(); + expect(screen.getByTestId('card')).toBeInTheDocument(); + + rerender( +
+ + + Content + +
+ ); + expect(screen.getByTestId('button')).toBeInTheDocument(); + expect(screen.getByTestId('card')).toBeInTheDocument(); + }); + }); +}); diff --git a/packages/lsd/src/components/ui/accordion/AccordionTrigger.tsx b/packages/lsd/src/components/ui/accordion/AccordionTrigger.tsx index e5c5495c..8354aa85 100644 --- a/packages/lsd/src/components/ui/accordion/AccordionTrigger.tsx +++ b/packages/lsd/src/components/ui/accordion/AccordionTrigger.tsx @@ -65,8 +65,6 @@ function AccordionTrigger({ className, children, size = 'md', ...props }: Accord // Transitions & Animations 'lsd:transition-transform', 'lsd:duration-200', - // Colors & Backgrounds - 'lsd:text-lsd-icon-primary', // Layout & Positioning 'lsd:shrink-0', // Spacing diff --git a/packages/lsd/src/components/ui/accordion/types.ts b/packages/lsd/src/components/ui/accordion/types.ts index 13f0fbff..9f5ec814 100644 --- a/packages/lsd/src/components/ui/accordion/types.ts +++ b/packages/lsd/src/components/ui/accordion/types.ts @@ -148,9 +148,6 @@ export interface AccordionItemProps export const accordionTriggerVariants = cva( [ - // Colors & Backgrounds - 'lsd:text-lsd-text-primary', - 'lsd:bg-lsd-surface', // Layout & Positioning 'lsd:flex', 'lsd:flex-1', @@ -169,7 +166,7 @@ export const accordionTriggerVariants = cva( 'lsd:cursor-pointer', 'lsd:hover:underline', 'lsd:outline-none', - 'focus-visible:lsd:ring-lsd-text/50', + 'focus-visible:lsd:ring-lsd-text-neutral/50', 'focus-visible:lsd:ring-[3px]', 'focus-visible:lsd:border-lsd-border', 'lsd:disabled:pointer-events-none', @@ -209,9 +206,6 @@ export const accordionTriggerVariants = cva( export const accordionContentVariants = cva( [ - // Colors & Backgrounds - 'lsd:text-lsd-text-primary', - 'lsd:bg-lsd-surface', // Transitions & Animations 'lsd:data-[state=closed]:animate-accordion-up', 'lsd:data-[state=open]:animate-accordion-down', diff --git a/packages/lsd/src/components/ui/alert-dialog/AlertDialogContent.tsx b/packages/lsd/src/components/ui/alert-dialog/AlertDialogContent.tsx index 53dc5929..ab8ebfa0 100644 --- a/packages/lsd/src/components/ui/alert-dialog/AlertDialogContent.tsx +++ b/packages/lsd/src/components/ui/alert-dialog/AlertDialogContent.tsx @@ -15,7 +15,7 @@ function AlertDialogContent({ data-slot="alert-dialog-content" className={cn( // Colors & Backgrounds - 'lsd:bg-lsd-surface', + 'lsd:bg-lsd-foreground', // Layout & Positioning 'lsd:fixed', 'lsd:top-[50%]', diff --git a/packages/lsd/src/components/ui/alert-dialog/AlertDialogTitle.tsx b/packages/lsd/src/components/ui/alert-dialog/AlertDialogTitle.tsx index ec5d6085..bc7c3225 100644 --- a/packages/lsd/src/components/ui/alert-dialog/AlertDialogTitle.tsx +++ b/packages/lsd/src/components/ui/alert-dialog/AlertDialogTitle.tsx @@ -10,7 +10,7 @@ function AlertDialogTitle({ return ( ); diff --git a/packages/lsd/src/components/ui/alert-dialog/__tests__/alert-dialog-content.test.tsx b/packages/lsd/src/components/ui/alert-dialog/__tests__/alert-dialog-content.test.tsx index 302ee7bd..cbad1490 100644 --- a/packages/lsd/src/components/ui/alert-dialog/__tests__/alert-dialog-content.test.tsx +++ b/packages/lsd/src/components/ui/alert-dialog/__tests__/alert-dialog-content.test.tsx @@ -23,7 +23,7 @@ describe('AlertDialogContent', () => { ); const content = document.querySelector('[data-slot="alert-dialog-content"]'); - expect(content).toHaveClass('lsd:bg-lsd-surface'); + expect(content).toHaveClass('lsd:bg-lsd-foreground'); expect(content).toHaveClass('lsd:fixed'); expect(content).toHaveClass('lsd:top-[50%]'); expect(content).toHaveClass('lsd:left-[50%]'); diff --git a/packages/lsd/src/components/ui/alert/Alert.tsx b/packages/lsd/src/components/ui/alert/Alert.tsx index 0f62e2e8..f174b1bb 100644 --- a/packages/lsd/src/components/ui/alert/Alert.tsx +++ b/packages/lsd/src/components/ui/alert/Alert.tsx @@ -39,14 +39,14 @@ const alertVariants = cva( { variants: { variant: { - default: 'lsd:bg-lsd-surface lsd:text-lsd-text-primary lsd:border-lsd-border', + default: 'lsd:border-lsd-border', destructive: - 'lsd:bg-lsd-surface lsd:text-lsd-destructive-text lsd:border-lsd-destructive [&>svg]:lsd:text-current data-[variant=destructive]>*:data-[slot=alert-description]:lsd:text-lsd-destructive-text', - info: 'lsd:bg-lsd-surface lsd:text-lsd-info-text lsd:border-lsd-info [&>svg]:lsd:text-current data-[variant=info]>*:data-[slot=alert-description]:lsd:text-lsd-info-text', + 'lsd:text-lsd-text-destructive lsd:border-lsd-destructive [&>svg]:lsd:text-current data-[variant=destructive]>*:data-[slot=alert-description]:lsd:text-lsd-text-destructive', + info: 'lsd:text-lsd-text-info lsd:border-lsd-info [&>svg]:lsd:text-current data-[variant=info]>*:data-[slot=alert-description]:lsd:text-lsd-text-info', success: - 'lsd:bg-lsd-surface lsd:text-lsd-success-text lsd:border-lsd-success [&>svg]:lsd:text-current data-[variant=success]>*:data-[slot=alert-description]:lsd:text-lsd-success-text', + 'lsd:text-lsd-text-success lsd:border-lsd-success [&>svg]:lsd:text-current data-[variant=success]>*:data-[slot=alert-description]:lsd:text-lsd-text-success', warning: - 'lsd:bg-lsd-surface lsd:text-lsd-warning-text lsd:border-lsd-warning [&>svg]:lsd:text-current data-[variant=warning]>*:data-[slot=alert-description]:lsd:text-lsd-warning-text', + 'lsd:text-lsd-text-warning lsd:border-lsd-warning [&>svg]:lsd:text-current data-[variant=warning]>*:data-[slot=alert-description]:lsd:text-lsd-text-warning', }, }, defaultVariants: { diff --git a/packages/lsd/src/components/ui/alert/AlertDescription.tsx b/packages/lsd/src/components/ui/alert/AlertDescription.tsx index 8570cf06..b1318e10 100644 --- a/packages/lsd/src/components/ui/alert/AlertDescription.tsx +++ b/packages/lsd/src/components/ui/alert/AlertDescription.tsx @@ -32,10 +32,10 @@ function AlertDescription({ className, ...props }: AlertDescriptionProps) { // Pseudo-selectors & ARIA '[&_p]:lsd:leading-relaxed', // Colors & Backgrounds (variants) - 'data-[variant=destructive]:lsd:text-lsd-destructive-text/90', - 'data-[variant=info]:lsd:text-lsd-info-text/90', - 'data-[variant=success]:lsd:text-lsd-success-text/90', - 'data-[variant=warning]:lsd:text-lsd-warning-text/90', + 'data-[variant=destructive]:lsd:text-lsd-text-destructive/90', + 'data-[variant=info]:lsd:text-lsd-text-info/90', + 'data-[variant=success]:lsd:text-lsd-text-success/90', + 'data-[variant=warning]:lsd:text-lsd-text-warning/90', className )} {...props} diff --git a/packages/lsd/src/components/ui/autocomplete/Autocomplete.tsx b/packages/lsd/src/components/ui/autocomplete/Autocomplete.tsx index c924e4a4..a7c9bc5f 100644 --- a/packages/lsd/src/components/ui/autocomplete/Autocomplete.tsx +++ b/packages/lsd/src/components/ui/autocomplete/Autocomplete.tsx @@ -142,7 +142,7 @@ const Autocomplete = React.forwardRef( 'lsd:text-sm', currentSize.label, // Colors & Backgrounds - disabled ? 'lsd:text-lsd-text-secondary' : 'lsd:text-lsd-text-primary' + disabled && 'lsd:text-lsd-text-secondary' )} > {label} @@ -159,11 +159,11 @@ const Autocomplete = React.forwardRef( // Borders, Shapes & Effects variant === 'outlined' ? disabled - ? 'lsd:border lsd:border-lsd-border' - : 'lsd:border lsd:border-lsd-border' + ? 'lsd:border lsd:border-lsd-border/30' + : `lsd:border ${error ? 'lsd:border-lsd-destructive' : 'lsd:border-lsd-border'}` : disabled - ? 'lsd:border lsd:border-transparent lsd:border-b-lsd-border' - : 'lsd:border lsd:border-transparent lsd:border-b-lsd-border', + ? 'lsd:border lsd:border-transparent lsd:border-b-lsd-border/30' + : `lsd:border lsd:border-transparent ${error ? 'lsd:border-b-lsd-destructive' : 'lsd:border-b-lsd-border'}`, // Interactive States disabled ? 'lsd:cursor-not-allowed' : 'lsd:cursor-pointer' )} @@ -189,9 +189,7 @@ const Autocomplete = React.forwardRef( // Colors & Backgrounds disabled ? 'lsd:text-lsd-text-secondary' - : error - ? 'lsd:text-lsd-destructive' - : 'lsd:text-lsd-text-primary', + : error && 'lsd:text-lsd-text-destructive', // Interactive States disabled ? 'lsd:cursor-not-allowed' : '', // Typography @@ -230,7 +228,7 @@ const Autocomplete = React.forwardRef( // Sizing 'lsd:size-(--lsd-spacing-base)', // Colors & Backgrounds - disabled ? 'lsd:text-lsd-text-secondary' : 'lsd:text-lsd-icon-primary' + disabled && 'lsd:text-lsd-text-secondary' )} weight="duotone" /> diff --git a/packages/lsd/src/components/ui/autocomplete/AutocompleteList.tsx b/packages/lsd/src/components/ui/autocomplete/AutocompleteList.tsx index 2ad4858b..3a5e4ae3 100644 --- a/packages/lsd/src/components/ui/autocomplete/AutocompleteList.tsx +++ b/packages/lsd/src/components/ui/autocomplete/AutocompleteList.tsx @@ -74,7 +74,7 @@ export function AutocompleteContent({ // Spacing 'lsd:p-0', // Colors & Backgrounds - 'lsd:bg-lsd-surface', + 'lsd:bg-foreground', 'lsd:border-lsd-border', // Borders, Shapes & Effects 'lsd:data-[side=top]:border-b-0', @@ -121,8 +121,6 @@ export function AutocompleteContent({ keywords={[option.label]} onSelect={() => onSelect(option.value)} className={cn( - // Colors & Backgrounds - 'lsd:text-lsd-text-primary', // Interactive States 'lsd:hover:underline', 'lsd:focus:underline', @@ -144,8 +142,6 @@ export function AutocompleteContent({ {matchedPart} { it('applies error state', () => { render(); const input = screen.getByRole('textbox'); - expect(input).toHaveClass('lsd:text-lsd-destructive'); + expect(input).toHaveClass('lsd:text-lsd-text-destructive'); }); it('applies medium size classes by default', () => { @@ -250,6 +250,16 @@ describe('Autocomplete', () => { expect(screen.getByText('Option 1')).toBeInTheDocument(); }); + it('applies correct background to autocomplete list', () => { + render(); + const input = screen.getByRole('textbox'); + fireEvent.click(input); + const popover = screen.getByRole('dialog'); + expect(popover).toBeInTheDocument(); + // The popover content should have foreground background for overlay + expect(popover).toHaveClass('lsd:bg-foreground'); + }); + it('handles error when async fetch fails', async () => { const onOptionsFetch = vi.fn().mockRejectedValue(new Error('Fetch failed')); render(); diff --git a/packages/lsd/src/components/ui/avatar/AvatarBadge.tsx b/packages/lsd/src/components/ui/avatar/AvatarBadge.tsx index 9fd73040..4571a9ad 100644 --- a/packages/lsd/src/components/ui/avatar/AvatarBadge.tsx +++ b/packages/lsd/src/components/ui/avatar/AvatarBadge.tsx @@ -25,10 +25,10 @@ function AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) { 'lsd:rounded-full', // Colors & Backgrounds 'lsd:bg-lsd-primary', - 'lsd:text-lsd-surface', + 'lsd:text-lsd-primary-content', // Ring 'lsd:ring-2', - 'lsd:ring-lsd-surface', + 'lsd:ring-lsd-background', // Other Utility Classes 'lsd:select-none', // Pseudo-selectors & ARIA diff --git a/packages/lsd/src/components/ui/avatar/AvatarFallback.tsx b/packages/lsd/src/components/ui/avatar/AvatarFallback.tsx index 4c7120e0..dfd391b6 100644 --- a/packages/lsd/src/components/ui/avatar/AvatarFallback.tsx +++ b/packages/lsd/src/components/ui/avatar/AvatarFallback.tsx @@ -26,7 +26,7 @@ function AvatarFallback({ // Borders, Shapes & Effects 'lsd:rounded-full', // Colors & Backgrounds - 'lsd:bg-lsd-muted', + 'lsd:bg-lsd-foreground', // Typography 'lsd:text-sm', 'lsd:text-lsd-text-secondary', diff --git a/packages/lsd/src/components/ui/avatar/AvatarGroup.tsx b/packages/lsd/src/components/ui/avatar/AvatarGroup.tsx index 7785196b..c24551e4 100644 --- a/packages/lsd/src/components/ui/avatar/AvatarGroup.tsx +++ b/packages/lsd/src/components/ui/avatar/AvatarGroup.tsx @@ -13,7 +13,7 @@ function AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {
)
svg]:size-(--lsd-spacing-base) lsd:group-has-data-[size=lg]/avatar-group:[&>svg]:size-(--lsd-spacing-large) lsd:group-has-data-[size=sm]/avatar-group:[&>svg]:size-(--lsd-spacing-small)', + 'lsd:relative lsd:flex lsd:size-(--lsd-spacing-large) lsd:shrink-0 lsd:items-center lsd:justify-center lsd:rounded-full lsd:bg-lsd-foreground lsd:text-sm lsd:text-lsd-text-secondary lsd:ring-2 lsd:ring-lsd-background lsd:group-has-data-[size=lg]/avatar-group:size-(--lsd-spacing-larger) lsd:group-has-data-[size=sm]/avatar-group:size-(--lsd-spacing-base) lsd:[&>svg]:size-(--lsd-spacing-base) lsd:group-has-data-[size=lg]/avatar-group:[&>svg]:size-(--lsd-spacing-large) lsd:group-has-data-[size=sm]/avatar-group:[&>svg]:size-(--lsd-spacing-small)', className )} {...props} diff --git a/packages/lsd/src/components/ui/badge/Badge.tsx b/packages/lsd/src/components/ui/badge/Badge.tsx index fee21ca8..2e0ec7ff 100644 --- a/packages/lsd/src/components/ui/badge/Badge.tsx +++ b/packages/lsd/src/components/ui/badge/Badge.tsx @@ -137,7 +137,7 @@ function Badge({ {onDismiss && ( ); const button = screen.getByRole('button'); - expect(button).toHaveClass('lsd:bg-primary'); - expect(button).toHaveClass('lsd:text-primary-foreground'); + expect(button).toHaveClass('lsd:bg-lsd-primary'); + expect(button).toHaveClass('lsd:text-lsd-primary-content'); }); it('applies outlined variant classes correctly', () => { render(); const button = screen.getByRole('button'); expect(button).toHaveClass('lsd:bg-transparent'); - expect(button).toHaveClass('lsd:text-foreground'); }); it('applies filled-rounded variant classes correctly', () => { render(); const button = screen.getByRole('button'); - expect(button).toHaveClass('lsd:bg-primary'); + expect(button).toHaveClass('lsd:bg-lsd-primary'); + expect(button).toHaveClass('lsd:text-lsd-primary-content'); expect(button).toHaveClass('lsd:rounded-full'); }); @@ -45,68 +45,56 @@ describe('Button', () => { render(); const button = screen.getByRole('button'); expect(button).toHaveClass('lsd:bg-transparent'); - expect(button).toHaveClass('lsd:text-foreground'); expect(button).toHaveClass('lsd:border-0'); - expect(button).toHaveClass('lsd:hover:underline'); }); it('applies ghost variant classes correctly', () => { render(); const button = screen.getByRole('button'); expect(button).toHaveClass('lsd:bg-transparent'); - expect(button).toHaveClass('lsd:text-foreground'); expect(button).toHaveClass('lsd:border-0'); - expect(button).toHaveClass('lsd:hover:bg-accent'); - expect(button).toHaveClass('lsd:hover:text-accent-foreground'); }); it('applies ghost-rounded variant classes correctly', () => { render(); const button = screen.getByRole('button'); expect(button).toHaveClass('lsd:bg-transparent'); - expect(button).toHaveClass('lsd:text-foreground'); expect(button).toHaveClass('lsd:border-0'); expect(button).toHaveClass('lsd:rounded-full'); - expect(button).toHaveClass('lsd:hover:bg-accent'); - expect(button).toHaveClass('lsd:hover:text-accent-foreground'); }); it('applies destructive variant classes correctly', () => { render(); const button = screen.getByRole('button'); expect(button).toHaveClass('lsd:bg-lsd-destructive'); - expect(button).toHaveClass('lsd:text-white'); + expect(button).toHaveClass('lsd:text-lsd-primary-content'); expect(button).toHaveClass('lsd:border-lsd-destructive'); - expect(button).toHaveClass('lsd:hover:bg-lsd-destructive/90'); }); it('applies destructive-rounded variant classes correctly', () => { render(); const button = screen.getByRole('button'); expect(button).toHaveClass('lsd:bg-lsd-destructive'); - expect(button).toHaveClass('lsd:text-white'); + expect(button).toHaveClass('lsd:text-lsd-primary-content'); expect(button).toHaveClass('lsd:border-lsd-destructive'); expect(button).toHaveClass('lsd:rounded-full'); - expect(button).toHaveClass('lsd:hover:bg-lsd-destructive/90'); }); it('applies success variant classes correctly', () => { render(); const button = screen.getByRole('button'); expect(button).toHaveClass('lsd:bg-lsd-success'); - expect(button).toHaveClass('lsd:text-white'); + expect(button).toHaveClass('lsd:text-lsd-primary-content'); expect(button).toHaveClass('lsd:border-lsd-success'); - expect(button).toHaveClass('lsd:hover:bg-lsd-success/90'); }); it('applies success-rounded variant classes correctly', () => { render(); const button = screen.getByRole('button'); expect(button).toHaveClass('lsd:bg-lsd-success'); - expect(button).toHaveClass('lsd:text-white'); + expect(button).toHaveClass('lsd:text-lsd-primary-content'); expect(button).toHaveClass('lsd:border-lsd-success'); expect(button).toHaveClass('lsd:rounded-full'); - expect(button).toHaveClass('lsd:hover:bg-lsd-success/90'); }); it('applies medium size classes correctly', () => { @@ -167,7 +155,7 @@ describe('Button', () => { it('uses default variant when not specified', () => { render(); const button = screen.getByRole('button'); - expect(button).toHaveClass('lsd:bg-primary'); + expect(button).toHaveClass('lsd:bg-lsd-primary'); }); it('uses default size when not specified', () => { @@ -211,10 +199,7 @@ describe('Button', () => { expect(button).toHaveClass('lsd:items-center'); expect(button).toHaveClass('lsd:justify-center'); expect(button).toHaveClass('lsd:border'); - expect(button).toHaveClass('lsd:transition-colors'); expect(button).toHaveClass('lsd:cursor-pointer'); - expect(button).toHaveClass('lsd:text-primary-foreground'); - expect(button).toHaveClass('lsd:hover:underline'); }); it('passes through additional props', () => { @@ -350,17 +335,17 @@ describe('Button', () => { describe('buttonVariants', () => { it('returns correct classes for filled variant', () => { - expect(buttonVariants({ variant: 'filled' })).toContain('lsd:bg-primary'); - expect(buttonVariants({ variant: 'filled' })).toContain('lsd:text-primary-foreground'); + expect(buttonVariants({ variant: 'filled' })).toContain('lsd:bg-lsd-primary'); + expect(buttonVariants({ variant: 'filled' })).toContain('lsd:text-lsd-primary-content'); }); it('returns correct classes for outlined variant', () => { expect(buttonVariants({ variant: 'outlined' })).toContain('lsd:bg-transparent'); - expect(buttonVariants({ variant: 'outlined' })).toContain('lsd:text-foreground'); }); it('returns correct classes for filled-rounded variant', () => { - expect(buttonVariants({ variant: 'filled-rounded' })).toContain('lsd:bg-primary'); + expect(buttonVariants({ variant: 'filled-rounded' })).toContain('lsd:bg-lsd-primary'); + expect(buttonVariants({ variant: 'filled-rounded' })).toContain('lsd:text-lsd-primary-content'); expect(buttonVariants({ variant: 'filled-rounded' })).toContain('lsd:rounded-full'); }); @@ -371,56 +356,44 @@ describe('buttonVariants', () => { it('returns correct classes for link variant', () => { expect(buttonVariants({ variant: 'link' })).toContain('lsd:bg-transparent'); - expect(buttonVariants({ variant: 'link' })).toContain('lsd:text-foreground'); expect(buttonVariants({ variant: 'link' })).toContain('lsd:border-0'); - expect(buttonVariants({ variant: 'link' })).toContain('lsd:underline'); }); it('returns correct classes for ghost variant', () => { expect(buttonVariants({ variant: 'ghost' })).toContain('lsd:bg-transparent'); - expect(buttonVariants({ variant: 'ghost' })).toContain('lsd:text-foreground'); expect(buttonVariants({ variant: 'ghost' })).toContain('lsd:border-0'); - expect(buttonVariants({ variant: 'ghost' })).toContain('lsd:hover:bg-accent'); - expect(buttonVariants({ variant: 'ghost' })).toContain('lsd:hover:text-accent-foreground'); }); it('returns correct classes for ghost-rounded variant', () => { expect(buttonVariants({ variant: 'ghost-rounded' })).toContain('lsd:bg-transparent'); - expect(buttonVariants({ variant: 'ghost-rounded' })).toContain('lsd:text-foreground'); expect(buttonVariants({ variant: 'ghost-rounded' })).toContain('lsd:border-0'); expect(buttonVariants({ variant: 'ghost-rounded' })).toContain('lsd:rounded-full'); - expect(buttonVariants({ variant: 'ghost-rounded' })).toContain('lsd:hover:bg-accent'); - expect(buttonVariants({ variant: 'ghost-rounded' })).toContain( - 'lsd:hover:text-accent-foreground' - ); }); it('returns correct classes for destructive variant', () => { expect(buttonVariants({ variant: 'destructive' })).toContain('lsd:bg-lsd-destructive'); - expect(buttonVariants({ variant: 'destructive' })).toContain('lsd:text-white'); - expect(buttonVariants({ variant: 'destructive' })).toContain('lsd:hover:bg-lsd-destructive/90'); + expect(buttonVariants({ variant: 'destructive' })).toContain('lsd:text-lsd-primary-content'); }); it('returns correct classes for destructive-rounded variant', () => { expect(buttonVariants({ variant: 'destructive-rounded' })).toContain('lsd:bg-lsd-destructive'); - expect(buttonVariants({ variant: 'destructive-rounded' })).toContain('lsd:text-white'); - expect(buttonVariants({ variant: 'destructive-rounded' })).toContain('lsd:rounded-full'); expect(buttonVariants({ variant: 'destructive-rounded' })).toContain( - 'lsd:hover:bg-lsd-destructive/90' + 'lsd:text-lsd-primary-content' ); + expect(buttonVariants({ variant: 'destructive-rounded' })).toContain('lsd:rounded-full'); }); it('returns correct classes for success variant', () => { expect(buttonVariants({ variant: 'success' })).toContain('lsd:bg-lsd-success'); - expect(buttonVariants({ variant: 'success' })).toContain('lsd:text-white'); - expect(buttonVariants({ variant: 'success' })).toContain('lsd:hover:bg-lsd-success/90'); + expect(buttonVariants({ variant: 'success' })).toContain('lsd:text-lsd-primary-content'); }); it('returns correct classes for success-rounded variant', () => { expect(buttonVariants({ variant: 'success-rounded' })).toContain('lsd:bg-lsd-success'); - expect(buttonVariants({ variant: 'success-rounded' })).toContain('lsd:text-white'); + expect(buttonVariants({ variant: 'success-rounded' })).toContain( + 'lsd:text-lsd-primary-content' + ); expect(buttonVariants({ variant: 'success-rounded' })).toContain('lsd:rounded-full'); - expect(buttonVariants({ variant: 'success-rounded' })).toContain('lsd:hover:bg-lsd-success/90'); }); it('returns correct classes for medium size', () => { @@ -465,7 +438,7 @@ describe('buttonVariants', () => { }); it('uses default variant when not specified', () => { - expect(buttonVariants({})).toContain('lsd:bg-primary'); + expect(buttonVariants({})).toContain('lsd:bg-lsd-primary'); }); it('uses default size when not specified', () => { diff --git a/packages/lsd/src/components/ui/button/types.ts b/packages/lsd/src/components/ui/button/types.ts index 0755dd3c..70e6598f 100644 --- a/packages/lsd/src/components/ui/button/types.ts +++ b/packages/lsd/src/components/ui/button/types.ts @@ -15,27 +15,25 @@ export type ButtonVariant = | 'success-rounded'; export const buttonVariants = cva( - 'lsd:inline-flex lsd:items-center lsd:justify-center lsd:border lsd:transition-colors lsd:cursor-pointer', + 'lsd:inline-flex lsd:items-center lsd:justify-center lsd:border lsd:cursor-pointer lsd:hover:underline', { variants: { variant: { - filled: 'lsd:bg-primary lsd:text-primary-foreground', - outlined: 'lsd:bg-transparent lsd:text-foreground', - 'filled-rounded': 'lsd:bg-primary lsd:text-primary-foreground lsd:rounded-full', - 'outlined-rounded': 'lsd:bg-transparent lsd:text-foreground lsd:rounded-full', - link: 'lsd:bg-transparent lsd:border-0 lsd:text-foreground lsd:underline', - ghost: - 'lsd:bg-transparent lsd:text-foreground lsd:border-0 lsd:hover:bg-accent lsd:hover:text-accent-foreground', - 'ghost-rounded': - 'lsd:bg-transparent lsd:text-foreground lsd:border-0 lsd:rounded-full lsd:hover:bg-accent lsd:hover:text-accent-foreground', + filled: 'lsd:bg-lsd-primary lsd:text-lsd-primary-content', + outlined: 'lsd:bg-transparent', + 'filled-rounded': 'lsd:bg-lsd-primary lsd:text-lsd-primary-content lsd:rounded-full', + 'outlined-rounded': 'lsd:bg-transparent lsd:rounded-full', + link: 'lsd:bg-transparent lsd:border-0 lsd:underline', + ghost: 'lsd:bg-transparent lsd:border-0', + 'ghost-rounded': 'lsd:bg-transparent lsd:border-0 lsd:rounded-full lsd:hover:border', destructive: - 'lsd:bg-lsd-destructive lsd:text-white lsd:border-lsd-destructive lsd:hover:bg-lsd-destructive/90', + 'lsd:bg-lsd-destructive lsd:text-lsd-primary-content lsd:border-lsd-destructive lsd:hover:bg-lsd-destructive/90', 'destructive-rounded': - 'lsd:bg-lsd-destructive lsd:text-white lsd:border-lsd-destructive lsd:rounded-full lsd:hover:bg-lsd-destructive/90', + 'lsd:bg-lsd-destructive lsd:text-lsd-primary-content lsd:border-lsd-destructive lsd:rounded-full lsd:hover:bg-lsd-destructive/90', success: - 'lsd:bg-lsd-success lsd:text-white lsd:border-lsd-success lsd:hover:bg-lsd-success/90', + 'lsd:bg-lsd-success lsd:text-lsd-primary-content lsd:border-lsd-success lsd:hover:bg-lsd-success/90', 'success-rounded': - 'lsd:bg-lsd-success lsd:text-white lsd:border-lsd-success lsd:rounded-full lsd:hover:bg-lsd-success/90', + 'lsd:bg-lsd-success lsd:text-lsd-primary-content lsd:border-lsd-success lsd:rounded-full lsd:hover:bg-lsd-success/90', }, size: { sm: 'lsd:h-8 lsd:px-[var(--lsd-spacing-small)] lsd:py-[var(--lsd-spacing-smaller)] lsd:text-sm', diff --git a/packages/lsd/src/components/ui/calendar/Calendar.tsx b/packages/lsd/src/components/ui/calendar/Calendar.tsx index 7ec93b7b..03fdf346 100644 --- a/packages/lsd/src/components/ui/calendar/Calendar.tsx +++ b/packages/lsd/src/components/ui/calendar/Calendar.tsx @@ -88,7 +88,7 @@ function Calendar({ buttonVariant = 'ghost', ...props }: CalendarProps) { svg]:rotate-180`, String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, className @@ -140,15 +140,15 @@ function Calendar({ buttonVariant = 'ghost', ...props }: CalendarProps) { defaultClassNames.day ), range_start: cn( - 'lsd:rounded-l-md lsd:bg-lsd-primary lsd:text-lsd-primary-foreground', + 'lsd:rounded-l-md lsd:bg-lsd-primary lsd:text-lsd-primary-content', defaultClassNames.range_start ), range_middle: cn('lsd:rounded-none', defaultClassNames.range_middle), range_end: cn( - 'lsd:rounded-r-md lsd:bg-lsd-primary lsd:text-lsd-primary-foreground', + 'lsd:rounded-r-md lsd:bg-lsd-primary lsd:text-lsd-primary-content', defaultClassNames.range_end ), - today: cn('lsd:rounded-md lsd:bg-lsd-surface', defaultClassNames.today), + today: cn('lsd:border lsd:border-lsd-border/20', defaultClassNames.today), outside: cn( 'lsd:text-lsd-text-secondary lsd:aria-selected:lsd:text-lsd-text-secondary', defaultClassNames.outside diff --git a/packages/lsd/src/components/ui/calendar/CalendarDayButton.tsx b/packages/lsd/src/components/ui/calendar/CalendarDayButton.tsx index fb948912..892922c1 100644 --- a/packages/lsd/src/components/ui/calendar/CalendarDayButton.tsx +++ b/packages/lsd/src/components/ui/calendar/CalendarDayButton.tsx @@ -54,23 +54,21 @@ function CalendarDayButton({ className, day, modifiers, ...props }: CalendarDayB 'lsd:group-data-[focused=true]/day:lsd:z-10', 'lsd:group-data-[focused=true]/day:lsd:border-lsd-border', 'lsd:group-data-[focused=true]/day:lsd:ring-[3px]', - 'lsd:group-data-[focused=true]/day:lsd:ring-lsd-text-primary/50', + 'lsd:group-data-[focused=true]/day:lsd:ring-lsd-text-neutral/50', // Borders, Shapes & Effects (conditional) isRangeStart && cn('lsd:rounded-md', 'lsd:rounded-l-md'), // Colors & Backgrounds (conditional) - isRangeStart && cn('lsd:bg-lsd-primary', 'lsd:text-lsd-primary-foreground'), + isRangeStart && cn('lsd:bg-lsd-primary', 'lsd:text-lsd-primary-content'), // Borders, Shapes & Effects (conditional) isRangeEnd && cn('lsd:rounded-md', 'lsd:rounded-r-md'), // Colors & Backgrounds (conditional) - isRangeEnd && cn('lsd:bg-lsd-primary', 'lsd:text-lsd-primary-foreground'), + isRangeEnd && cn('lsd:bg-lsd-primary', 'lsd:text-lsd-primary-content'), // Borders, Shapes & Effects (conditional) - isRangeMiddle && cn('lsd:rounded-none'), + isRangeMiddle && 'lsd:rounded-none', // Colors & Backgrounds (conditional) - isRangeMiddle && cn('lsd:bg-lsd-surface', 'lsd:text-lsd-text-primary'), + isRangeMiddle && 'lsd:bg-lsd-background', // Borders, Shapes & Effects (conditional) isSelected && cn('lsd:border', 'lsd:border-lsd-border'), - // Dark Mode - 'dark:lsd:hover:lsd:text-lsd-text-primary', // Pseudo-selectors & ARIA '[&>span]:lsd:opacity-70', '[&>span]:lsd:lsd-typography-label1', diff --git a/packages/lsd/src/components/ui/calendar/__tests__/calendar.test.tsx b/packages/lsd/src/components/ui/calendar/__tests__/calendar.test.tsx index 13e4fe7d..5b36624e 100644 --- a/packages/lsd/src/components/ui/calendar/__tests__/calendar.test.tsx +++ b/packages/lsd/src/components/ui/calendar/__tests__/calendar.test.tsx @@ -22,12 +22,6 @@ describe('Calendar', () => { expect(root).toBeInTheDocument(); }); - it('applies LSD background token', () => { - const { container } = render(); - const root = container.querySelector('[data-slot="calendar"]') as HTMLElement; - expect(root).toHaveClass('lsd:bg-lsd-surface'); - }); - it('applies LSD padding token with CSS variable', () => { const { container } = render(); const root = container.querySelector('[data-slot="calendar"]') as HTMLElement; @@ -219,12 +213,6 @@ describe('Calendar', () => { }); describe('LSD Tokens Integration', () => { - it('uses LSD background token', () => { - const { container } = render(); - const root = container.querySelector('[data-slot="calendar"]') as HTMLElement; - expect(root).toHaveClass('lsd:bg-lsd-surface'); - }); - it('uses LSD spacing tokens with CSS variables for padding', () => { const { container } = render(); const root = container.querySelector('[data-slot="calendar"]') as HTMLElement; diff --git a/packages/lsd/src/components/ui/card/Card.tsx b/packages/lsd/src/components/ui/card/Card.tsx index 781ae657..1e7ba181 100644 --- a/packages/lsd/src/components/ui/card/Card.tsx +++ b/packages/lsd/src/components/ui/card/Card.tsx @@ -42,8 +42,7 @@ const Card = React.forwardRef>( data-slot="card" className={cn( // Colors & Backgrounds - 'lsd:bg-lsd-surface', - 'lsd:text-lsd-text-primary', + 'lsd:bg-lsd-foreground', // Layout & Positioning 'lsd:flex', 'lsd:flex-col', diff --git a/packages/lsd/src/components/ui/card/__tests__/card.test.tsx b/packages/lsd/src/components/ui/card/__tests__/card.test.tsx index cd04dae4..6ecd14b6 100644 --- a/packages/lsd/src/components/ui/card/__tests__/card.test.tsx +++ b/packages/lsd/src/components/ui/card/__tests__/card.test.tsx @@ -30,8 +30,7 @@ describe('Card', () => { it('applies default classes', () => { const { container } = render(); const card = queryByDataSlot(container, 'card'); - expect(card).toHaveClass('lsd:bg-lsd-surface'); - expect(card).toHaveClass('lsd:text-lsd-text-primary'); + expect(card).toHaveClass('lsd:bg-lsd-foreground'); expect(card).toHaveClass('lsd:flex'); expect(card).toHaveClass('lsd:flex-col'); expect(card).toHaveClass('lsd:border-lsd-border'); diff --git a/packages/lsd/src/components/ui/checkbox/Checkbox.tsx b/packages/lsd/src/components/ui/checkbox/Checkbox.tsx index f1910b45..31e72f5d 100644 --- a/packages/lsd/src/components/ui/checkbox/Checkbox.tsx +++ b/packages/lsd/src/components/ui/checkbox/Checkbox.tsx @@ -36,9 +36,9 @@ function Checkbox({ className, ...props }: React.ComponentProps { render(); const checkbox = screen.getByRole('checkbox'); expect(checkbox).toHaveClass('lsd:data-[state=checked]:bg-lsd-primary'); - expect(checkbox).toHaveClass('lsd:data-[state=checked]:text-lsd-surface'); + expect(checkbox).toHaveClass('lsd:data-[state=checked]:text-lsd-primary-content'); expect(checkbox).toHaveClass('lsd:data-[state=checked]:border-lsd-primary'); }); diff --git a/packages/lsd/src/components/ui/command/Command.tsx b/packages/lsd/src/components/ui/command/Command.tsx index 8d03f785..6873eb5d 100644 --- a/packages/lsd/src/components/ui/command/Command.tsx +++ b/packages/lsd/src/components/ui/command/Command.tsx @@ -56,8 +56,7 @@ export function Command({ className, ...props }: React.ComponentProps { ); const group = document.querySelector('[data-slot="command-group"]'); - expect(group).toHaveClass('lsd:text-lsd-text-primary'); expect(group).toHaveClass('lsd:overflow-hidden'); expect(group).toHaveClass('lsd:px-(--lsd-spacing-smallest)'); expect(group).toHaveClass('lsd:py-(--lsd-spacing-base)'); diff --git a/packages/lsd/src/components/ui/command/__tests__/command-item.test.tsx b/packages/lsd/src/components/ui/command/__tests__/command-item.test.tsx index 4275a764..8bc3a7f7 100644 --- a/packages/lsd/src/components/ui/command/__tests__/command-item.test.tsx +++ b/packages/lsd/src/components/ui/command/__tests__/command-item.test.tsx @@ -58,8 +58,7 @@ describe('CommandItem', () => { ); const item = document.querySelector('[data-slot="command-item"]'); - expect(item).toHaveClass('lsd:data-[selected=true]:bg-lsd-surface'); - expect(item).toHaveClass('lsd:data-[selected=true]:text-lsd-text-primary'); + expect(item).toHaveClass('lsd:data-[selected=true]:underline'); }); it('applies disabled state classes', () => { diff --git a/packages/lsd/src/components/ui/command/__tests__/command-shortcut.test.tsx b/packages/lsd/src/components/ui/command/__tests__/command-shortcut.test.tsx index be828d55..4e2d9509 100644 --- a/packages/lsd/src/components/ui/command/__tests__/command-shortcut.test.tsx +++ b/packages/lsd/src/components/ui/command/__tests__/command-shortcut.test.tsx @@ -44,7 +44,6 @@ describe('CommandShortcut', () => { ); const shortcut = document.querySelector('[data-slot="command-shortcut"]'); - expect(shortcut).toHaveClass('lsd:text-lsd-text-primary'); expect(shortcut).toHaveClass('lsd:ml-auto'); expect(shortcut).toHaveClass('lsd:text-xs'); expect(shortcut).toHaveClass('lsd:tracking-widest'); diff --git a/packages/lsd/src/components/ui/command/__tests__/command.test.tsx b/packages/lsd/src/components/ui/command/__tests__/command.test.tsx index e9340ada..a091ee55 100644 --- a/packages/lsd/src/components/ui/command/__tests__/command.test.tsx +++ b/packages/lsd/src/components/ui/command/__tests__/command.test.tsx @@ -46,8 +46,7 @@ describe('Command', () => { ); const command = document.querySelector('[data-slot="command"]'); - expect(command).toHaveClass('lsd:bg-lsd-surface'); - expect(command).toHaveClass('lsd:text-lsd-text-primary'); + expect(command).toHaveClass('lsd:bg-lsd-foreground'); expect(command).toHaveClass('lsd:flex'); expect(command).toHaveClass('lsd:h-full'); expect(command).toHaveClass('lsd:w-full'); diff --git a/packages/lsd/src/components/ui/dialog/DialogContent.tsx b/packages/lsd/src/components/ui/dialog/DialogContent.tsx index 36df32d7..bd0044b3 100644 --- a/packages/lsd/src/components/ui/dialog/DialogContent.tsx +++ b/packages/lsd/src/components/ui/dialog/DialogContent.tsx @@ -21,7 +21,7 @@ function DialogContent({ data-slot="dialog-content" className={cn( // Colors & Backgrounds - 'lsd:bg-lsd-surface', + 'lsd:bg-lsd-foreground', // Pseudo-selectors & ARIA - Animations 'lsd:data-[state=open]:animate-in', 'lsd:data-[state=closed]:animate-out', @@ -66,8 +66,7 @@ function DialogContent({ // Pseudo-selectors & ARIA - Focus 'focus:lsd:outline-hidden', // Pseudo-selectors & ARIA - State - 'lsd:data-[state=open]:bg-lsd-surface', - 'lsd:data-[state=open]:text-lsd-text-primary', + 'lsd:data-[state=open]:bg-lsd-foreground', // Positioning 'lsd:absolute', 'lsd:top-(--lsd-spacing-base)', diff --git a/packages/lsd/src/components/ui/dialog/DialogDescription.tsx b/packages/lsd/src/components/ui/dialog/DialogDescription.tsx index 956df021..3f12b7e8 100644 --- a/packages/lsd/src/components/ui/dialog/DialogDescription.tsx +++ b/packages/lsd/src/components/ui/dialog/DialogDescription.tsx @@ -10,7 +10,7 @@ function DialogDescription({ return ( ); diff --git a/packages/lsd/src/components/ui/dialog/__tests__/dialog-content.test.tsx b/packages/lsd/src/components/ui/dialog/__tests__/dialog-content.test.tsx index c270aa8a..b9ff4ea9 100644 --- a/packages/lsd/src/components/ui/dialog/__tests__/dialog-content.test.tsx +++ b/packages/lsd/src/components/ui/dialog/__tests__/dialog-content.test.tsx @@ -27,7 +27,7 @@ describe('DialogContent', () => { ); const content = document.querySelector('[data-slot="dialog-content"]'); - expect(content).toHaveClass('lsd:bg-lsd-surface'); + expect(content).toHaveClass('lsd:bg-lsd-foreground'); expect(content).toHaveClass('lsd:fixed'); expect(content).toHaveClass('lsd:top-[50%]'); expect(content).toHaveClass('lsd:left-[50%]'); diff --git a/packages/lsd/src/components/ui/dialog/__tests__/dialog-description.test.tsx b/packages/lsd/src/components/ui/dialog/__tests__/dialog-description.test.tsx index 4be496b2..ba0e41ba 100644 --- a/packages/lsd/src/components/ui/dialog/__tests__/dialog-description.test.tsx +++ b/packages/lsd/src/components/ui/dialog/__tests__/dialog-description.test.tsx @@ -29,7 +29,6 @@ describe('DialogDescription', () => { ); const description = screen.getByText('Description'); - expect(description).toHaveClass('lsd:text-lsd-text-primary'); expect(description).toHaveClass('lsd:text-sm'); }); diff --git a/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.tsx b/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.tsx index f1a4a5d2..47dace58 100644 --- a/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.tsx +++ b/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.tsx @@ -40,8 +40,6 @@ export function DropdownMenuCheckboxItem({ 'lsd:outline-none', 'lsd:select-none', // Interactive States - Focus - 'focus:lsd:bg-[var(--lsd-accent)]', - 'focus:lsd:text-[var(--lsd-accent-foreground)]', 'lsd:hover:underline', 'lsd:focus:underline', // Interactive States - Disabled diff --git a/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuContent.tsx b/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuContent.tsx index 74825dcf..dba0cd42 100644 --- a/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuContent.tsx +++ b/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuContent.tsx @@ -30,8 +30,7 @@ export function DropdownMenuContent({ sideOffset={sideOffset} className={cn( // Colors & Backgrounds - 'lsd:bg-lsd-surface', - 'lsd:text-lsd-text-primary', + 'lsd:bg-lsd-foreground', // Borders 'lsd:border', // Layout & Positioning diff --git a/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuItem.tsx b/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuItem.tsx index e0f181d3..e2ccc608 100644 --- a/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuItem.tsx +++ b/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuItem.tsx @@ -32,7 +32,7 @@ export function DropdownMenuItem({ data-variant={variant} className={cn( // Colors & Backgrounds - Destructive variant - variant === 'destructive' && 'lsd:text-lsd-destructive-text', + variant === 'destructive' && 'lsd:text-lsd-text-destructive', // Layout & Positioning 'lsd:relative', 'lsd:flex', @@ -49,8 +49,6 @@ export function DropdownMenuItem({ 'lsd:outline-none', 'lsd:select-none', // Interactive States - Focus/Hover - 'focus:lsd:bg-[var(--lsd-accent)]', - 'focus:lsd:text-[var(--lsd-accent-foreground)]', 'lsd:hover:underline', 'lsd:focus:underline', // Interactive States - Disabled @@ -58,7 +56,7 @@ export function DropdownMenuItem({ 'data-[disabled]:lsd:opacity-50', // Interactive States - Destructive variant focus variant === 'destructive' && 'focus:lsd:bg-[var(--lsd-destructive)]/10', - variant === 'destructive' && 'focus:lsd:text-[var(--lsd-destructive-text)]', + variant === 'destructive' && 'focus:lsd:text-[var(--lsd-text-destructive)]', variant === 'destructive' && 'dark:focus:lsd:bg-[var(--lsd-destructive)]/20', // Interactive States - Cursor 'lsd:cursor-pointer', @@ -71,7 +69,7 @@ export function DropdownMenuItem({ '[&_svg:not([class*=text-])]:lsd:text-[var(--lsd-text-secondary)]', // Pseudo-selectors & ARIA - Destructive variant SVG variant === 'destructive' && - '[&[data-variant=destructive]]:[&_svg]:lsd:text-[var(--lsd-destructive-text)]', + '[&[data-variant=destructive]]:[&_svg]:lsd:text-[var(--lsd-text-destructive)]', className )} {...props} diff --git a/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuLabel.tsx b/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuLabel.tsx index f76c8a2e..75312c0c 100644 --- a/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuLabel.tsx +++ b/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuLabel.tsx @@ -24,8 +24,6 @@ export function DropdownMenuLabel({ className, inset, ...props }: DropdownMenuLa // Typography Font 'lsd:font-medium', 'lsd:text-sm', - // Colors & Backgrounds - 'lsd:text-lsd-text-primary', // Spacing 'lsd:px-2', 'lsd:py-1.5', diff --git a/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuRadioItem.tsx b/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuRadioItem.tsx index 53608608..c8e90fcb 100644 --- a/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuRadioItem.tsx +++ b/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuRadioItem.tsx @@ -39,8 +39,6 @@ export function DropdownMenuRadioItem({ 'lsd:outline-none', 'lsd:select-none', // Interactive States - Focus - 'focus:lsd:bg-[var(--lsd-accent)]', - 'focus:lsd:text-[var(--lsd-accent-foreground)]', 'lsd:hover:underline', 'lsd:focus:underline', // Interactive States - Disabled diff --git a/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuSubContent.tsx b/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuSubContent.tsx index 05b9dd01..464dab55 100644 --- a/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuSubContent.tsx +++ b/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuSubContent.tsx @@ -11,8 +11,7 @@ export function DropdownMenuSubContent({ className, ...props }: DropdownMenuSubC data-slot="dropdown-menu-sub-content" className={cn( // Colors & Backgrounds - 'lsd:bg-lsd-surface', - 'lsd:text-lsd-text-primary', + 'lsd:bg-lsd-foreground', // Borders 'lsd:border', // Layout & Positioning diff --git a/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.tsx b/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.tsx index 7dcb381e..8d40bcb9 100644 --- a/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.tsx +++ b/packages/lsd/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.tsx @@ -42,13 +42,8 @@ export function DropdownMenuSubTrigger({ 'lsd:outline-none', 'lsd:select-none', // Interactive States - Focus/Hover - 'focus:lsd:bg-[var(--lsd-accent)]', - 'focus:lsd:text-[var(--lsd-accent-foreground)]', 'lsd:hover:underline', 'lsd:focus:underline', - // Interactive States - Open state - 'data-[state=open]:lsd:bg-[var(--lsd-accent)]', - 'data-[state=open]:lsd:text-[var(--lsd-accent-foreground)]', // Interactive States - Cursor 'lsd:cursor-pointer', // Pseudo-selectors & ARIA - Inset variant diff --git a/packages/lsd/src/components/ui/dropdown-menu/dropdown-menu.test.tsx b/packages/lsd/src/components/ui/dropdown-menu/dropdown-menu.test.tsx index 6b40f4c9..1065e9c9 100644 --- a/packages/lsd/src/components/ui/dropdown-menu/dropdown-menu.test.tsx +++ b/packages/lsd/src/components/ui/dropdown-menu/dropdown-menu.test.tsx @@ -135,12 +135,15 @@ describe('DropdownMenuContent', () => { ); const content = document.querySelector('[data-slot="dropdown-menu-content"]'); + expect(content).toHaveClass('lsd:bg-lsd-foreground'); + expect(content).toHaveClass('lsd:border'); expect(content).toHaveClass('lsd:z-50'); + expect(content).toHaveClass('lsd:max-h-(--radix-dropdown-menu-content-available-height)'); expect(content).toHaveClass('lsd:min-w-32'); expect(content).toHaveClass('lsd:rounded-none'); - expect(content).toHaveClass('lsd:border'); - expect(content).toHaveClass('lsd:bg-lsd-surface'); - expect(content).toHaveClass('lsd:p-(--lsd-spacing-smaller)'); + expect(content).toHaveClass('lsd:shadow-md'); + expect(content).toHaveClass('lsd:overflow-x-hidden'); + expect(content).toHaveClass('lsd:overflow-y-auto'); }); it('merges custom className with component classes', () => { @@ -258,7 +261,7 @@ describe('DropdownMenuItem', () => { ); const item = screen.getByText('Delete'); expect(item).toHaveAttribute('data-variant', 'destructive'); - expect(item).toHaveClass('lsd:text-lsd-destructive-text'); + expect(item).toHaveClass('lsd:text-lsd-text-destructive'); }); it('applies inset prop correctly', () => { @@ -780,7 +783,6 @@ describe('DropdownMenuLabel', () => { expect(label).toHaveClass('lsd:py-1.5'); expect(label).toHaveClass('lsd:text-sm'); expect(label).toHaveClass('lsd:font-medium'); - expect(label).toHaveClass('lsd:text-lsd-text-primary'); }); it('applies inset prop correctly', () => { diff --git a/packages/lsd/src/components/ui/field/FieldError.tsx b/packages/lsd/src/components/ui/field/FieldError.tsx index 1a4ca438..0e1cc1ee 100644 --- a/packages/lsd/src/components/ui/field/FieldError.tsx +++ b/packages/lsd/src/components/ui/field/FieldError.tsx @@ -59,7 +59,7 @@ const FieldError = React.forwardRef( // Typography Font 'lsd:font-normal', // Colors & Backgrounds - 'lsd:text-lsd-destructive-text', + 'lsd:text-lsd-text-destructive', className )} {...props} diff --git a/packages/lsd/src/components/ui/field/FieldLabel.tsx b/packages/lsd/src/components/ui/field/FieldLabel.tsx index fa0dd1ab..72d5aa5a 100644 --- a/packages/lsd/src/components/ui/field/FieldLabel.tsx +++ b/packages/lsd/src/components/ui/field/FieldLabel.tsx @@ -23,8 +23,6 @@ const FieldLabel = React.forwardRef( 'lsd:text-[0.875rem]', // Typography Font 'lsd:font-medium', - // Colors & Backgrounds - 'lsd:text-lsd-text-primary', // Spacing 'lsd:mb-(--lsd-spacing-smaller)', // Display diff --git a/packages/lsd/src/components/ui/field/FieldLegend.tsx b/packages/lsd/src/components/ui/field/FieldLegend.tsx index 5e133e0f..fdaa05bb 100644 --- a/packages/lsd/src/components/ui/field/FieldLegend.tsx +++ b/packages/lsd/src/components/ui/field/FieldLegend.tsx @@ -16,7 +16,7 @@ const FieldLegend = React.forwardRef( ); diff --git a/packages/lsd/src/components/ui/field/field.test.tsx b/packages/lsd/src/components/ui/field/field.test.tsx index e9e51d75..c9ab7d23 100644 --- a/packages/lsd/src/components/ui/field/field.test.tsx +++ b/packages/lsd/src/components/ui/field/field.test.tsx @@ -176,7 +176,6 @@ describe('FieldLegend', () => { render(Legend Text); const legend = screen.getByText('Legend Text'); expect(legend).toHaveClass('lsd:text-[1.5rem]'); - expect(legend).toHaveClass('lsd:text-lsd-text-primary'); expect(legend).toHaveClass('lsd:font-medium'); }); @@ -262,7 +261,6 @@ describe('FieldLabel', () => { const label = screen.getByText('Label Text'); expect(label).toHaveClass('lsd:text-[0.875rem]'); expect(label).toHaveClass('lsd:font-medium'); - expect(label).toHaveClass('lsd:text-lsd-text-primary'); expect(label).toHaveClass('lsd:mb-(--lsd-spacing-smaller)'); expect(label).toHaveClass('lsd:block'); }); @@ -448,7 +446,7 @@ describe('FieldError', () => { const error = screen.getByText('Error message'); expect(error).toHaveClass('lsd:text-sm'); expect(error).toHaveClass('lsd:font-normal'); - expect(error).toHaveClass('lsd:text-lsd-destructive-text'); + expect(error).toHaveClass('lsd:text-lsd-text-destructive'); }); it('applies role="alert" for accessibility', () => { diff --git a/packages/lsd/src/components/ui/form/FormLabel.tsx b/packages/lsd/src/components/ui/form/FormLabel.tsx index 351905f6..2f66cf49 100644 --- a/packages/lsd/src/components/ui/form/FormLabel.tsx +++ b/packages/lsd/src/components/ui/form/FormLabel.tsx @@ -20,7 +20,7 @@ function FormLabel({ className, ...props }: React.ComponentProps diff --git a/packages/lsd/src/components/ui/form/FormMessage.tsx b/packages/lsd/src/components/ui/form/FormMessage.tsx index 1c6dc8b7..91aef52d 100644 --- a/packages/lsd/src/components/ui/form/FormMessage.tsx +++ b/packages/lsd/src/components/ui/form/FormMessage.tsx @@ -29,9 +29,9 @@ function FormMessage({ className, ...props }: FormMessageProps) { // Typography Size 'lsd:text-sm', // Typography Line Height - 'lsd:leading-[1.25rem]', + 'lsd:leading-5', // Colors & Backgrounds - 'lsd:text-lsd-destructive-text', + 'lsd:text-lsd-text-destructive', className )} {...props} diff --git a/packages/lsd/src/components/ui/form/form.test.tsx b/packages/lsd/src/components/ui/form/form.test.tsx index fbc35b44..8f3ee54c 100644 --- a/packages/lsd/src/components/ui/form/form.test.tsx +++ b/packages/lsd/src/components/ui/form/form.test.tsx @@ -171,7 +171,7 @@ describe('Form Components', () => { }); const label = screen.getByTestId('form-label'); - expect(label).toHaveClass('lsd:text-lsd-destructive-text'); + expect(label).toHaveClass('lsd:text-lsd-text-destructive'); }); it('does not apply destructive class when there is no error', () => { @@ -195,7 +195,7 @@ describe('Form Components', () => { } render(); const label = screen.getByTestId('form-label'); - expect(label).not.toHaveClass('lsd:text-lsd-destructive-text'); + expect(label).not.toHaveClass('lsd:text-lsd-text-destructive'); }); }); @@ -421,7 +421,9 @@ describe('Form Components', () => { } render(); const message = screen.getByTestId('message'); - expect(message).toHaveClass('lsd:text-lsd-destructive-text'); + expect(message).toHaveClass('lsd:text-sm'); + expect(message).toHaveClass('lsd:leading-5'); + expect(message).toHaveClass('lsd:text-lsd-text-destructive'); }); it('does not render when no error or children', () => { diff --git a/packages/lsd/src/components/ui/input-group/InputGroup.tsx b/packages/lsd/src/components/ui/input-group/InputGroup.tsx index 2e6e3fc7..a25874fa 100644 --- a/packages/lsd/src/components/ui/input-group/InputGroup.tsx +++ b/packages/lsd/src/components/ui/input-group/InputGroup.tsx @@ -65,8 +65,6 @@ const InputGroup = React.forwardRef( 'lsd:transition-[color,box-shadow]', // Interactive States - Focus 'lsd:outline-none', - // Colors & Backgrounds - 'lsd:bg-lsd-surface', // Dynamic height classes getInputGroupHeightClasses(size), // Sizing - minimum width and textarea diff --git a/packages/lsd/src/components/ui/input-group/InputGroupText.tsx b/packages/lsd/src/components/ui/input-group/InputGroupText.tsx index cd4c4ce2..f61b66ed 100644 --- a/packages/lsd/src/components/ui/input-group/InputGroupText.tsx +++ b/packages/lsd/src/components/ui/input-group/InputGroupText.tsx @@ -28,8 +28,8 @@ const InputGroupText = React.forwardRef( // Colors & Backgrounds 'lsd:text-lsd-text-secondary', // Pseudo-selectors & ARIA - SVG styling - '[&_svg]:lsd:pointer-events-none', - "[&_svg:not([class*='size-'])]:lsd:size-4", + 'lsd:[&_svg]:pointer-events-none', + "lsd:[&_svg:not([class*='size-'])]:size-4", // Dynamic text size classes getInputGroupTextSizeClasses(size), className diff --git a/packages/lsd/src/components/ui/input/Input.tsx b/packages/lsd/src/components/ui/input/Input.tsx index af2dfda9..2381ffad 100644 --- a/packages/lsd/src/components/ui/input/Input.tsx +++ b/packages/lsd/src/components/ui/input/Input.tsx @@ -101,14 +101,11 @@ const Input = React.forwardRef( disabled={disabled} data-slot="input" className={cn( - // Pseudo-selectors & ARIA - File colors - 'file:lsd:text-lsd-text-primary', // Pseudo-selectors & ARIA - Placeholder colors - 'placeholder:lsd:text-lsd-text-primary', 'placeholder:lsd:opacity-30', // Pseudo-selectors & ARIA - Selection colors 'selection:lsd:bg-lsd-primary', - 'selection:lsd:text-lsd-surface', + 'selection:lsd:text-lsd-primary-content', // Borders 'lsd:border-none', // Interactive States - Focus @@ -116,7 +113,6 @@ const Input = React.forwardRef( 'focus-visible:lsd:outline-none', // Colors & Backgrounds 'lsd:bg-transparent', - 'lsd:text-lsd-text-primary', // Sizing 'lsd:w-full', 'lsd:h-full', diff --git a/packages/lsd/src/components/ui/input/input.test.tsx b/packages/lsd/src/components/ui/input/input.test.tsx index 2387ec83..1a49f2c0 100644 --- a/packages/lsd/src/components/ui/input/input.test.tsx +++ b/packages/lsd/src/components/ui/input/input.test.tsx @@ -167,22 +167,21 @@ describe('Input', () => { expect(input).toHaveValue('test value'); }); - it('applies focus-visible classes', () => { - render(); - const input = screen.getByRole('textbox'); - expect(input).toHaveClass('focus-visible:lsd:outline-none'); - expect(input).toHaveClass('lsd:px-(--lsd-spacing-base)'); - }); - it('applies base classes correctly', () => { render(); const input = screen.getByRole('textbox'); expect(input).toHaveClass('lsd:border-none'); expect(input).toHaveClass('lsd:outline-none'); expect(input).toHaveClass('lsd:bg-transparent'); - expect(input).toHaveClass('lsd:text-lsd-text-primary'); expect(input).toHaveClass('lsd:w-full'); expect(input).toHaveClass('lsd:h-full'); + expect(input).toHaveClass('focus-visible:lsd:outline-none'); + expect(input).toHaveClass('lsd:px-(--lsd-spacing-base)'); + expect(input).toHaveClass('lsd:py-[var(--lsd-spacing-small)]'); + expect(input).toHaveClass('lsd:text-base'); + expect(input).toHaveClass('placeholder:lsd:opacity-30'); + expect(input).toHaveClass('selection:lsd:bg-lsd-primary'); + expect(input).toHaveClass('selection:lsd:text-lsd-primary-content'); }); it('applies label size classes correctly', () => { diff --git a/packages/lsd/src/components/ui/label/label.test.tsx b/packages/lsd/src/components/ui/label/label.test.tsx index 5c3e4320..d78e8168 100644 --- a/packages/lsd/src/components/ui/label/label.test.tsx +++ b/packages/lsd/src/components/ui/label/label.test.tsx @@ -21,7 +21,7 @@ describe('Label', () => { it('applies default variant classes correctly', () => { render(); const label = screen.getByText('Label'); - expect(label).toHaveClass('lsd:text-lsd-text-primary'); + expect(label).toHaveClass('lsd:text-lsd-text-neutral'); }); it('applies secondary variant classes correctly', () => { @@ -54,7 +54,7 @@ describe('Label', () => { it('uses default variant when not specified', () => { render(); const label = screen.getByText('Label'); - expect(label).toHaveClass('lsd:text-lsd-text-primary'); + expect(label).toHaveClass('lsd:text-lsd-text-neutral'); }); it('uses default size when not specified', () => { @@ -103,7 +103,7 @@ describe('Label', () => { describe('labelVariants', () => { it('returns correct classes for default variant', () => { - expect(labelVariants({ variant: 'default' })).toContain('lsd:text-lsd-text-primary'); + expect(labelVariants({ variant: 'default' })).toContain('lsd:text-lsd-text-neutral'); }); it('returns correct classes for secondary variant', () => { @@ -126,7 +126,7 @@ describe('labelVariants', () => { }); it('uses default variant when not specified', () => { - expect(labelVariants({})).toContain('lsd:text-lsd-text-primary'); + expect(labelVariants({})).toContain('lsd:text-lsd-text-neutral'); }); it('uses default size when not specified', () => { diff --git a/packages/lsd/src/components/ui/label/types.ts b/packages/lsd/src/components/ui/label/types.ts index 289f3756..ef5495f2 100644 --- a/packages/lsd/src/components/ui/label/types.ts +++ b/packages/lsd/src/components/ui/label/types.ts @@ -8,7 +8,7 @@ export const labelVariants = cva( { variants: { variant: { - default: 'lsd:text-lsd-text-primary', + default: 'lsd:text-lsd-text-neutral', secondary: 'lsd:text-lsd-text-secondary', }, size: { diff --git a/packages/lsd/src/components/ui/menubar/__tests__/menubar.test.tsx b/packages/lsd/src/components/ui/menubar/__tests__/menubar.test.tsx index d7f8f221..8caef8f1 100644 --- a/packages/lsd/src/components/ui/menubar/__tests__/menubar.test.tsx +++ b/packages/lsd/src/components/ui/menubar/__tests__/menubar.test.tsx @@ -49,7 +49,6 @@ describe('Menubar', () => { expect(menubar).toHaveClass('lsd:items-center'); expect(menubar).toHaveClass('lsd:gap-[var(--lsd-spacing-smallest)]'); expect(menubar).toHaveClass('lsd:border'); - expect(menubar).toHaveClass('lsd:bg-lsd-surface'); }); it('merges custom className with component classes', () => { diff --git a/packages/lsd/src/components/ui/menubar/types.ts b/packages/lsd/src/components/ui/menubar/types.ts index 6fa218d7..e1b1668d 100644 --- a/packages/lsd/src/components/ui/menubar/types.ts +++ b/packages/lsd/src/components/ui/menubar/types.ts @@ -1,15 +1,15 @@ import { cva, type VariantProps } from 'class-variance-authority'; export const menubarVariants = cva( - 'lsd:flex lsd:h-9 lsd:items-center lsd:gap-[var(--lsd-spacing-smallest)] lsd:border lsd:border-lsd-border lsd:bg-lsd-surface lsd:p-[var(--lsd-spacing-smallest)] lsd:shadow-xs' + 'lsd:flex lsd:h-9 lsd:items-center lsd:gap-[var(--lsd-spacing-smallest)] lsd:border lsd:border-lsd-border lsd:p-[var(--lsd-spacing-smallest)] lsd:shadow-xs' ); export const menubarTriggerVariants = cva( - 'lsd:cursor-pointer lsd:flex lsd:items-center lsd:px-[var(--lsd-spacing-smaller)] lsd:py-[var(--lsd-spacing-smallest)] lsd:text-sm lsd:font-medium lsd:outline-none lsd:select-none lsd:text-lsd-text-primary lsd:bg-lsd-surface lsd:hover:underline lsd:focus:underline lsd:data-[state=open]:bg-lsd-surface-secondary lsd:data-[state=open]:text-lsd-text-primary lsd:transition-colors' + 'lsd:cursor-pointer lsd:flex lsd:items-center lsd:px-[var(--lsd-spacing-smaller)] lsd:py-[var(--lsd-spacing-smallest)] lsd:text-sm lsd:font-medium lsd:outline-none lsd:select-none lsd:hover:underline lsd:focus:underline lsd:data-[state=open]:bg-lsd-foreground lsd:transition-colors' ); export const menubarItemVariants = cva( - "lsd:relative lsd:flex lsd:cursor-pointer lsd:items-center lsd:gap-[var(--lsd-spacing-smaller)] lsd:px-[var(--lsd-spacing-smaller)] lsd:py-[var(--lsd-spacing-smaller)] lsd:text-sm lsd:outline-none lsd:select-none lsd:text-lsd-text-primary lsd:bg-lsd-surface lsd:disabled:pointer-events-none lsd:disabled:opacity-50 lsd:transition-colors lsd:[&_svg]:pointer-events-none lsd:[&_svg]:shrink-0 lsd:[&_svg:not([class*='size-'])]:size-4", + "lsd:relative lsd:flex lsd:cursor-pointer lsd:items-center lsd:gap-[var(--lsd-spacing-smaller)] lsd:px-[var(--lsd-spacing-smaller)] lsd:py-[var(--lsd-spacing-smaller)] lsd:text-sm lsd:outline-none lsd:select-none lsd:bg-lsd-foreground lsd:disabled:pointer-events-none lsd:disabled:opacity-50 lsd:transition-colors lsd:[&_svg]:pointer-events-none lsd:[&_svg]:shrink-0 lsd:[&_svg:not([class*='size-'])]:size-4", { variants: { variant: { @@ -33,7 +33,7 @@ export const menubarSeparatorVariants = cva( ); export const menubarLabelVariants = cva( - 'lsd:px-[var(--lsd-spacing-smaller)] lsd:py-[var(--lsd-spacing-smaller)] lsd:text-sm lsd:font-bold lsd:text-lsd-text-primary', + 'lsd:px-[var(--lsd-spacing-smaller)] lsd:py-[var(--lsd-spacing-smaller)] lsd:text-sm lsd:font-bold', { variants: { inset: { @@ -47,11 +47,11 @@ export const menubarLabelVariants = cva( ); export const menubarContentVariants = cva( - 'lsd:bg-lsd-surface lsd:text-lsd-text-primary lsd:z-50 lsd:min-w-[12rem] lsd:overflow-hidden lsd:border lsd:border-lsd-border lsd:p-[var(--lsd-spacing-smallest)] lsd:shadow-lg lsd:data-[state=open]:animate-in lsd:data-[state=closed]:fade-out-0 lsd:data-[state=open]:fade-in-0 lsd:data-[state=closed]:zoom-out-95 lsd:data-[state=open]:zoom-in-95 lsd:data-[side=bottom]:slide-in-from-top-2 lsd:data-[side=left]:slide-in-from-right-2 lsd:data-[side=right]:slide-in-from-left-2 lsd:data-[side=top]:slide-in-from-bottom-2' + 'lsd:bg-lsd-foreground lsd:z-50 lsd:min-w-[12rem] lsd:overflow-hidden lsd:border lsd:border-lsd-border lsd:p-[var(--lsd-spacing-smallest)] lsd:shadow-lg lsd:data-[state=open]:animate-in lsd:data-[state=closed]:fade-out-0 lsd:data-[state=open]:fade-in-0 lsd:data-[state=closed]:zoom-out-95 lsd:data-[state=open]:zoom-in-95 lsd:data-[side=bottom]:slide-in-from-top-2 lsd:data-[side=left]:slide-in-from-right-2 lsd:data-[side=right]:slide-in-from-left-2 lsd:data-[side=top]:slide-in-from-bottom-2' ); export const menubarSubTriggerVariants = cva( - 'lsd:flex lsd:cursor-pointer lsd:items-center lsd:px-[var(--lsd-spacing-smaller)] lsd:py-[var(--lsd-spacing-smaller)] lsd:text-sm lsd:outline-none lsd:select-none lsd:text-lsd-text-primary lsd:bg-lsd-surface lsd:data-[state=open]:bg-lsd-surface-secondary lsd:data-[state=open]:text-lsd-text-primary lsd:transition-colors', + 'lsd:flex lsd:cursor-pointer lsd:items-center lsd:px-[var(--lsd-spacing-smaller)] lsd:py-[var(--lsd-spacing-smaller)] lsd:text-sm lsd:outline-none lsd:select-none lsd:data-[state=open]:bg-lsd-foreground lsd:transition-colors', { variants: { inset: { @@ -65,15 +65,15 @@ export const menubarSubTriggerVariants = cva( ); export const menubarSubContentVariants = cva( - 'lsd:bg-lsd-surface lsd:text-lsd-text-primary lsd:z-50 lsd:min-w-[8rem] lsd:overflow-hidden lsd:border lsd:border-lsd-border lsd:p-[var(--lsd-spacing-smallest)] lsd:shadow-lg lsd:data-[state=open]:animate-in lsd:data-[state=closed]:animate-out lsd:data-[state=closed]:fade-out-0 lsd:data-[state=open]:fade-in-0 lsd:data-[state=closed]:zoom-out-95 lsd:data-[state=open]:zoom-in-95 lsd:data-[side=bottom]:slide-in-from-top-2 lsd:data-[side=left]:slide-in-from-right-2 lsd:data-[side=right]:slide-in-from-left-2 lsd:data-[side=top]:slide-in-from-bottom-2' + 'lsd:bg-lsd-foreground lsd:z-50 lsd:min-w-[8rem] lsd:overflow-hidden lsd:border lsd:border-lsd-border lsd:p-[var(--lsd-spacing-smallest)] lsd:shadow-lg lsd:data-[state=open]:animate-in lsd:data-[state=closed]:animate-out lsd:data-[state=closed]:fade-out-0 lsd:data-[state=open]:fade-in-0 lsd:data-[state=closed]:zoom-out-95 lsd:data-[state=open]:zoom-in-95 lsd:data-[side=bottom]:slide-in-from-top-2 lsd:data-[side=left]:slide-in-from-right-2 lsd:data-[side=right]:slide-in-from-left-2 lsd:data-[side=top]:slide-in-from-bottom-2' ); export const menubarCheckboxItemVariants = cva( - "lsd:relative lsd:flex lsd:cursor-pointer lsd:items-center lsd:gap-[var(--lsd-spacing-smaller)] lsd:py-[var(--lsd-spacing-smaller)] lsd:pr-[var(--lsd-spacing-smaller)] lsd:pl-8 lsd:text-sm lsd:outline-none lsd:select-none lsd:text-lsd-text-primary lsd:bg-lsd-surface lsd:disabled:pointer-events-none lsd:disabled:opacity-50 lsd:[&_svg]:pointer-events-none lsd:[&_svg]:shrink-0 lsd:[&_svg:not([class*='size-'])]:size-4" + "lsd:relative lsd:flex lsd:cursor-pointer lsd:items-center lsd:gap-[var(--lsd-spacing-smaller)] lsd:py-[var(--lsd-spacing-smaller)] lsd:pr-[var(--lsd-spacing-smaller)] lsd:pl-8 lsd:text-sm lsd:outline-none lsd:select-none lsd:bg-lsd-foreground lsd:disabled:pointer-events-none lsd:disabled:opacity-50 lsd:[&_svg]:pointer-events-none lsd:[&_svg]:shrink-0 lsd:[&_svg:not([class*='size-'])]:size-4" ); export const menubarRadioItemVariants = cva( - "lsd:relative lsd:flex lsd:cursor-pointer lsd:items-center lsd:gap-[var(--lsd-spacing-smaller)] lsd:py-[var(--lsd-spacing-smaller)] lsd:pr-[var(--lsd-spacing-smaller)] lsd:pl-8 lsd:text-sm lsd:outline-none lsd:select-none lsd:text-lsd-text-primary lsd:bg-lsd-surface lsd:disabled:pointer-events-none lsd:disabled:opacity-50 lsd:[&_svg]:pointer-events-none lsd:[&_svg]:shrink-0 lsd:[&_svg:not([class*='size-'])]:size-4" + "lsd:relative lsd:flex lsd:cursor-pointer lsd:items-center lsd:gap-[var(--lsd-spacing-smaller)] lsd:py-[var(--lsd-spacing-smaller)] lsd:pr-[var(--lsd-spacing-smaller)] lsd:pl-8 lsd:text-sm lsd:outline-none lsd:select-none lsd:bg-lsd-foreground lsd:disabled:pointer-events-none lsd:disabled:opacity-50 lsd:[&_svg]:pointer-events-none lsd:[&_svg]:shrink-0 lsd:[&_svg:not([class*='size-'])]:size-4" ); export type MenubarVariants = VariantProps; diff --git a/packages/lsd/src/components/ui/navigation-menu/NavigationMenuContent.tsx b/packages/lsd/src/components/ui/navigation-menu/NavigationMenuContent.tsx index 2431058f..987ec355 100644 --- a/packages/lsd/src/components/ui/navigation-menu/NavigationMenuContent.tsx +++ b/packages/lsd/src/components/ui/navigation-menu/NavigationMenuContent.tsx @@ -23,49 +23,36 @@ export function NavigationMenuContent({ className, ...props }: NavigationMenuCon // Sizing - Default 'lsd:w-full', // Spacing - Default - 'lsd:p-(--lsd-spacing-base)', - 'lsd:pr-(--lsd-spacing-large)', + 'lsd:pt-(--lsd-spacing-smallest)', // Layout & Positioning - Medium screens - 'md:lsd:absolute', + 'lsd:md:absolute', // Sizing - Medium screens - 'md:lsd:w-auto', - // Animations - Motion from end - 'data-[motion=from-end]:slide-in-from-right-52', - // Animations - Motion from start - 'data-[motion=from-start]:slide-in-from-left-52', - 'data-[motion^=from-]:animate-in', - 'data-[motion^=from-]:fade-in', - // Animations - Motion to end - 'data-[motion=to-end]:slide-out-to-right-52', - // Animations - Motion to start - 'data-[motion=to-start]:slide-out-to-left-52', - 'data-[motion^=to-]:animate-out', - 'data-[motion^=to-]:fade-out', + 'lsd:md:w-auto', // Viewport false state - Layout & Positioning - 'group-data-[viewport=false]/navigation-menu:lsd:top-full', - 'group-data-[viewport=false]/navigation-menu:lsd:overflow-hidden', + 'lsd:group-data-[viewport=false]/navigation-menu:top-full', + 'lsd:group-data-[viewport=false]/navigation-menu:overflow-hidden', // Viewport false state - Borders, Shapes & Effects - 'group-data-[viewport=false]/navigation-menu:lsd:rounded-md', - 'group-data-[viewport=false]/navigation-menu:lsd:border', - 'group-data-[viewport=false]/navigation-menu:lsd:shadow-md', + 'lsd:group-data-[viewport=false]/navigation-menu:rounded-md', + 'lsd:group-data-[viewport=false]/navigation-menu:border', + 'lsd:group-data-[viewport=false]/navigation-menu:shadow-md', // Viewport false state - Spacing - 'group-data-[viewport=false]/navigation-menu:lsd:mt-[1.5rem]', + 'lsd:group-data-[viewport=false]/navigation-menu:mt-6', // Viewport false state - Colors & Backgrounds - 'group-data-[viewport=false]/navigation-menu:lsd:bg-[var(--lsd-popover)]', - 'group-data-[viewport=false]/navigation-menu:lsd:text-[var(--lsd-popover-foreground)]', + 'lsd:group-data-[viewport=false]/navigation-menu:bg-lsd-foreground', // Viewport false state - Animations - 'group-data-[viewport=false]/navigation-menu:lsd:duration-200', + 'lsd:group-data-[viewport=false]/navigation-menu:duration-200', // Viewport false state - Open state animations - 'group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in', - 'group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0', - 'group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95', + 'lsd:group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in', + 'lsd:group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0', + 'lsd:group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95', // Viewport false state - Closed state animations - 'group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out', - 'group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0', - 'group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95', + 'lsd:group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out', + 'lsd:group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0', + 'lsd:group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95', // Pseudo-selectors & ARIA - Link focus states - '**:data-[slot=navigation-menu-link]:focus:lsd:ring-0', - '**:data-[slot=navigation-menu-link]:focus:lsd:outline-none', + 'lsd:**:data-[slot=navigation-menu-link]:border-b-0', + 'lsd:**:data-[slot=navigation-menu-link]:focus:ring-0', + 'lsd:**:data-[slot=navigation-menu-link]:focus:outline-none', className )} {...props} diff --git a/packages/lsd/src/components/ui/navigation-menu/NavigationMenuLink.tsx b/packages/lsd/src/components/ui/navigation-menu/NavigationMenuLink.tsx index c75fde49..99c29344 100644 --- a/packages/lsd/src/components/ui/navigation-menu/NavigationMenuLink.tsx +++ b/packages/lsd/src/components/ui/navigation-menu/NavigationMenuLink.tsx @@ -21,31 +21,20 @@ export function NavigationMenuLink({ className, ...props }: NavigationMenuLinkPr 'lsd:flex', 'lsd:flex-col', // Sizing - 'lsd:text-[0.875rem]', + 'lsd:h-9', + 'lsd:text-sm', // Spacing 'lsd:gap-(--lsd-spacing-smallest)', - 'lsd:p-(--lsd-spacing-base)', + 'lsd:px-(--lsd-spacing-base) lsd:py-(--lsd-spacing-smaller)', // Borders, Shapes & Effects - 'lsd:rounded-sm', + 'lsd:border', + 'lsd:cursor-pointer', 'lsd:transition-all', 'lsd:outline-none', - // Interactive States - Hover - 'hover:lsd:bg-[var(--lsd-accent)]', - 'hover:lsd:text-[var(--lsd-accent-foreground)]', - // Interactive States - Focus - 'focus:lsd:bg-[var(--lsd-accent)]', - 'focus:lsd:text-[var(--lsd-accent-foreground)]', - 'focus-visible:lsd:ring-[3px]', - 'focus-visible:lsd:ring-ring/50', - 'focus-visible:lsd:outline-1', - // Interactive States - Active state - 'data-[active=true]:lsd:bg-[var(--lsd-accent)]/50', - 'data-[active=true]:lsd:text-[var(--lsd-accent-foreground)]', - 'data-[active=true]:hover:lsd:bg-[var(--lsd-accent)]', - 'data-[active=true]:focus:lsd:bg-[var(--lsd-accent)]', // Pseudo-selectors & ARIA - SVG styling - "[&_svg:not([class*='size-']):lsd:size-4", - "[&_svg:not([class*='text-'):lsd:text-[var(--lsd-text-secondary)]", + 'lsd:hover:underline lsd:focus:underline', + "lsd:[&_svg:not([class*='size-']):size-4", + "lsd:[&_svg:not([class*='text-'):text-[var(--lsd-text-secondary)]", className )} {...props} diff --git a/packages/lsd/src/components/ui/navigation-menu/NavigationMenuList.tsx b/packages/lsd/src/components/ui/navigation-menu/NavigationMenuList.tsx index 9068bb2d..b8177a91 100644 --- a/packages/lsd/src/components/ui/navigation-menu/NavigationMenuList.tsx +++ b/packages/lsd/src/components/ui/navigation-menu/NavigationMenuList.tsx @@ -27,7 +27,7 @@ export function NavigationMenuList({ className, ...props }: NavigationMenuListPr 'lsd:items-start', 'lsd:justify-start', // Spacing - 'lsd:gap-(--lsd-spacing-1)', + 'lsd:gap-(--lsd-spacing-largest)', // Groups 'lsd:group', className diff --git a/packages/lsd/src/components/ui/navigation-menu/NavigationMenuTrigger.tsx b/packages/lsd/src/components/ui/navigation-menu/NavigationMenuTrigger.tsx index 1a0d7cfd..21b327f1 100644 --- a/packages/lsd/src/components/ui/navigation-menu/NavigationMenuTrigger.tsx +++ b/packages/lsd/src/components/ui/navigation-menu/NavigationMenuTrigger.tsx @@ -8,7 +8,7 @@ export interface NavigationMenuTriggerProps extends React.ComponentProps {} export const navigationMenuTriggerStyle = cva( - 'lsd:group lsd:inline-flex lsd:h-[2.25rem] lsd:w-max lsd:items-center lsd:justify-center lsd:rounded-md lsd:bg-[var(--lsd-background)] lsd:px-4 lsd:py-2 lsd:text-[0.875rem] lsd:font-medium lsd:transition-[color,box-shadow] lsd:outline-none hover:lsd:bg-[var(--lsd-accent)] hover:lsd:text-[var(--lsd-accent-foreground)] focus:lsd:bg-[var(--lsd-accent)] focus:lsd:text-[var(--lsd-accent-foreground)] focus-visible:lsd:ring-[3px] focus-visible:lsd:ring-ring/50 focus-visible:lsd:outline-1 disabled:lsd:pointer-events-none disabled:lsd:opacity-50 data-[state=open]:lsd:bg-[var(--lsd-accent)]/50 data-[state=open]:lsd:text-[var(--lsd-accent-foreground)] data-[state=open]:hover:lsd:bg-[var(--lsd-accent)] data-[state=open]:focus:lsd:bg-[var(--lsd-accent)]' + 'lsd:group lsd:inline-flex lsd:h-9 lsd:w-max lsd:items-center lsd:justify-center lsd:cursor-pointer lsd:px-(--lsd-spacing-base) lsd:py-(--lsd-spacing-smaller) lsd:border lsd:text-sm lsd:transition-[color,box-shadow] lsd:outline-none disabled:lsd:pointer-events-none disabled:lsd:opacity-50' ); /** @@ -31,18 +31,19 @@ export function NavigationMenuTrigger({ > {children}{' '}