From da28ffe67220405cc39d3df10eb3b3bef5afb5c9 Mon Sep 17 00:00:00 2001 From: Kai Uwe Date: Wed, 7 Jan 2026 11:08:45 +0100 Subject: [PATCH 01/71] refactor(ui/badge): expand variants, fix border var, and update stories --- libs/ui/src/atoms/badge.tsx | 2 +- libs/ui/stories/atoms/badge.stories.tsx | 33 +++++++++++++++++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/libs/ui/src/atoms/badge.tsx b/libs/ui/src/atoms/badge.tsx index 91d4952a32..bbe9fa12ef 100644 --- a/libs/ui/src/atoms/badge.tsx +++ b/libs/ui/src/atoms/badge.tsx @@ -8,7 +8,7 @@ const badgeVariants = tv({ "p-badge", "rounded-badge border-badge-border", "font-badge text-badge-size", - "border-(length:--border-width-badge-dynamic)", + "border-(length:--border-width-badge)", ], variants: { variant: { diff --git a/libs/ui/stories/atoms/badge.stories.tsx b/libs/ui/stories/atoms/badge.stories.tsx index 3725b10032..3622cc49d7 100644 --- a/libs/ui/stories/atoms/badge.stories.tsx +++ b/libs/ui/stories/atoms/badge.stories.tsx @@ -12,10 +12,26 @@ const meta: Meta = { argTypes: { variant: { control: 'select', - options: ['info', 'outline', 'dynamic'], + options: [ + 'primary', + 'secondary', + 'tertiary', + 'discount', + 'info', + 'success', + 'warning', + 'danger', + 'outline', + 'dynamic', + ], + description: 'Visual style variant of the badge', + table: { + defaultValue: { summary: 'info' }, + }, }, children: { control: 'text', + description: 'Text content of the badge', }, bgColor: { control: 'color', @@ -42,16 +58,20 @@ const meta: Meta = { }, }, }, + args: { + variant: 'info', + children: 'Badge', + }, } export default meta // ! Storybook can't infer dynamic badge props, we need to enforce manually type Story = Omit, 'args'> & { args: BadgeProps } -export const Info: Story = { +export const Playground: Story = { args: { variant: 'info', - children: 'Info', + children: 'Badge Text', }, } @@ -72,20 +92,23 @@ export const Dynamic: Story = { }, } -export const AllVariants: Story = { +export const Variants: Story = { args: {} as BadgeProps, render: () => ( Primary Secondary - Tertirary + Tertiary Sleva 30% Odesíláme do 24h Doručeno Poslední kusy Nedostupné + + Outline Badge + ), } From f8d64280415885091629e707e144ce2dd09c5db7 Mon Sep 17 00:00:00 2001 From: Kai Uwe Date: Wed, 7 Jan 2026 15:05:42 +0100 Subject: [PATCH 02/71] fix(ui): improve badge and semantic color contrast for WCAG compliance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Lighten primary, tertiary, danger background colors for better text contrast - Change badge foreground to use fg-light consistently - Darken fg-light (slate-800 → slate-950) for improved readability - Update dynamic badge story with accessible red color --- libs/ui/src/tokens/_semantic.css | 8 ++++---- libs/ui/src/tokens/components/atoms/_badge.css | 4 ++-- libs/ui/stories/atoms/badge.stories.tsx | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libs/ui/src/tokens/_semantic.css b/libs/ui/src/tokens/_semantic.css index e1906c421d..4033fa5c4f 100644 --- a/libs/ui/src/tokens/_semantic.css +++ b/libs/ui/src/tokens/_semantic.css @@ -86,7 +86,7 @@ @theme static { /* Colors */ /* User interface main tone and interactive colors, usually used for main operation button, etc */ - --color-primary: light-dark(var(--color-blue-500), var(--color-sky-300)); + --color-primary: light-dark(var(--color-blue-300), var(--color-sky-300)); --color-primary-hover: oklch( from var(--color-primary) calc(l + var(--state-hover)) c h ); @@ -141,7 +141,7 @@ ); /* In the user interface, non emphasis color and interactive color are usually used for general and non emphasis function operation buttons */ - --color-tertiary: light-dark(var(--color-pink-500), var(--color-rose-400)); + --color-tertiary: light-dark(var(--color-pink-300), var(--color-rose-400)); --color-tertiary-hover: oklch( from var(--color-tertiary) calc(l + var(--state-hover)) c h ); @@ -248,7 +248,7 @@ ); /* It is usually used to express the dangerous state, and is used in the scene with the above semantics */ - --color-danger: light-dark(var(--color-red-500), var(--color-red-400)); + --color-danger: light-dark(var(--color-red-400), var(--color-red-300)); --color-danger-hover: oklch( from var(--color-danger) calc(l + var(--state-hover)) c h ); @@ -275,7 +275,7 @@ /* Text colors and Icon colors*/ /* Four different levels of text / icon colors represent the most important, secondary, minor and minor contents in the product interface */ - --color-fg-light: var(--color-slate-800); /* Light mode */ + --color-fg-light: var(--color-slate-950); /* Light mode */ --color-fg-dark: var(--color-white); /* Dark mode */ --color-fg-primary: light-dark(var(--color-fg-light), var(--color-fg-dark)); diff --git a/libs/ui/src/tokens/components/atoms/_badge.css b/libs/ui/src/tokens/components/atoms/_badge.css index 7e1b8c082f..18ce8ef3d2 100644 --- a/libs/ui/src/tokens/components/atoms/_badge.css +++ b/libs/ui/src/tokens/components/atoms/_badge.css @@ -21,7 +21,7 @@ --color-badge-bg-discount: var(--color-badge-danger); /* Alias for discount */ /* Foreground colors */ - --color-badge-fg: var(--color-fg-reverse); + --color-badge-fg: var(--color-fg-light); --color-badge-fg-primary: var(--color-badge-fg); --color-badge-fg-secondary: var(--color-badge-fg); --color-badge-fg-tertiary: var(--color-badge-fg); @@ -30,7 +30,7 @@ --color-badge-fg-warning: var(--color-badge-fg); --color-badge-fg-danger: var(--color-badge-fg); --color-badge-fg-discount: var( - --color-fg-dark + --color-badge-fg ); /* Keep original for discount */ /* Border colors */ diff --git a/libs/ui/stories/atoms/badge.stories.tsx b/libs/ui/stories/atoms/badge.stories.tsx index 3622cc49d7..32130cd727 100644 --- a/libs/ui/stories/atoms/badge.stories.tsx +++ b/libs/ui/stories/atoms/badge.stories.tsx @@ -85,7 +85,7 @@ export const Outline: Story = { export const Dynamic: Story = { args: { variant: 'dynamic', - bgColor: '#f00', + bgColor: '#8A0002', fgColor: '#fff', borderColor: '#fff500', children: 'Dynamic Badge', From 59e6490aee110b5fa79602273f4477c478fcd899 Mon Sep 17 00:00:00 2001 From: Kai Uwe Date: Wed, 7 Jan 2026 16:15:31 +0100 Subject: [PATCH 03/71] feat(ui/tokens): add WCAG AAA compliant accent FG colors - Add --state-fg-lightness (0.42 light / 0.85 dark) for absolute lightness - Add --color-{primary,secondary,tertiary}-accent-fg for brand text colors - Add --color-{success,info,warning,danger}-fg for status text colors - Update button borderless FG to use new accent tokens --- libs/ui/src/tokens/_semantic.css | 32 ++++++++++++++++++- .../src/tokens/components/atoms/_button.css | 14 ++++---- libs/ui/stories/atoms/button.stories.tsx | 9 ++++++ 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/libs/ui/src/tokens/_semantic.css b/libs/ui/src/tokens/_semantic.css index 4033fa5c4f..5385c8d378 100644 --- a/libs/ui/src/tokens/_semantic.css +++ b/libs/ui/src/tokens/_semantic.css @@ -22,6 +22,7 @@ --opacity-disabled-placeholder: 30%; --fill-increment: 5%; --surface-increment: 0.05; + --state-fg-lightness: 0.42; /* Accent color lightness for text - WCAG AAA on light bg */ } /* Explicit Dark class(es) */ @@ -31,6 +32,7 @@ --state-secondary-text: -0.15; --state-placeholder: -0.25; --surface-increment: -0.05; + --state-fg-lightness: 0.85; } /* System Dark preference (when no explicit class forces light) */ @@ -41,6 +43,7 @@ --state-secondary-text: -0.15; --state-placeholder: -0.25; --surface-increment: -0.05; + --state-fg-lightness: 0.85; } } @@ -51,6 +54,7 @@ --state-secondary-text: -0.15; --state-placeholder: -0.25; --surface-increment: -0.05; + --state-fg-lightness: 0.85; } :is(.dark, .always-dark) .reverse { @@ -59,6 +63,7 @@ --state-secondary-text: 0.15; --state-placeholder: 0.25; --surface-increment: 0.05; + --state-fg-lightness: 0.42; } /* Reverse on System Light/Dark preference (when no explicit theme class is present) */ @@ -69,6 +74,7 @@ --state-secondary-text: -0.15; --state-placeholder: -0.25; --surface-increment: -0.05; + --state-fg-lightness: 0.85; } } @@ -79,6 +85,7 @@ --state-secondary-text: 0.15; --state-placeholder: 0.25; --surface-increment: 0.05; + --state-fg-lightness: 0.42; } } @@ -86,6 +93,8 @@ @theme static { /* Colors */ /* User interface main tone and interactive colors, usually used for main operation button, etc */ + --color-primary-light: var(--color-blue-300); + --color-primary-dark: var(--color-sky-300); --color-primary: light-dark(var(--color-blue-300), var(--color-sky-300)); --color-primary-hover: oklch( from var(--color-primary) calc(l + var(--state-hover)) c h @@ -273,7 +282,28 @@ from var(--color-danger-light) l var(--state-disabled-chroma) h ); - /* Text colors and Icon colors*/ + /* Text colors and Icon colors */ + /* semantic text color for better contrast WCAG AAA - uses absolute lightness */ + --color-primary-accent-fg: oklch( + from var(--color-primary) var(--state-fg-lightness) c h + ); + --color-secondary-accent-fg: oklch( + from var(--color-secondary) var(--state-fg-lightness) c h + ); + --color-tertiary-accent-fg: oklch( + from var(--color-tertiary) var(--state-fg-lightness) c h + ); + --color-success-fg: oklch( + from var(--color-success) var(--state-fg-lightness) c h + ); + --color-info-fg: oklch(from var(--color-info) var(--state-fg-lightness) c h); + --color-warning-fg: oklch( + from var(--color-warning) var(--state-fg-lightness) c h + ); + --color-danger-fg: oklch( + from var(--color-danger) var(--state-fg-lightness) c h + ); + /* Four different levels of text / icon colors represent the most important, secondary, minor and minor contents in the product interface */ --color-fg-light: var(--color-slate-950); /* Light mode */ --color-fg-dark: var(--color-white); /* Dark mode */ diff --git a/libs/ui/src/tokens/components/atoms/_button.css b/libs/ui/src/tokens/components/atoms/_button.css index 0bbb74e5f4..2fa119d71e 100644 --- a/libs/ui/src/tokens/components/atoms/_button.css +++ b/libs/ui/src/tokens/components/atoms/_button.css @@ -22,8 +22,8 @@ --color-button-bg-danger: var(--color-button-danger); /* Foreground colors */ - --color-button-fg: var(--color-fg-reverse); - --color-button-fg-light: var(--color-fg-reverse); + --color-button-fg: var(--color-fg-light); + --color-button-fg-light: var(--color-fg-light); --color-button-fg-outlined: var(--color-fg-primary); --color-button-fg-primary: var(--color-button-fg); --color-button-fg-secondary: var(--color-button-fg); @@ -40,11 +40,11 @@ --color-button-fg-outlined-tertiary: var(--color-button-fg-outlined); --color-button-fg-outlined-warning: var(--color-button-fg-outlined); --color-button-fg-outlined-danger: var(--color-button-fg-outlined); - --color-button-fg-primary-borderless: var(--color-button-primary); - --color-button-fg-secondary-borderless: var(--color-button-secondary); - --color-button-fg-tertiary-borderless: var(--color-button-tertiary); - --color-button-fg-warning-borderless: var(--color-button-warning); - --color-button-fg-danger-borderless: var(--color-button-danger); + --color-button-fg-primary-borderless: var(--color-primary-accent-fg); + --color-button-fg-secondary-borderless: var(--color-secondary-accent-fg); + --color-button-fg-tertiary-borderless: var(--color-tertiary-accent-fg); + --color-button-fg-warning-borderless: var(--color-warning-fg); + --color-button-fg-danger-borderless: var(--color-danger-fg); /* Border colors */ --color-button-border-primary: var(--color-button-primary); diff --git a/libs/ui/stories/atoms/button.stories.tsx b/libs/ui/stories/atoms/button.stories.tsx index d1ab6cd507..fbbdc95717 100644 --- a/libs/ui/stories/atoms/button.stories.tsx +++ b/libs/ui/stories/atoms/button.stories.tsx @@ -186,6 +186,15 @@ export const Variants: Story = { Danger Borderless + + + + + ), } From 95db088693af5a6ed58b563f48a7d13cf9dae383 Mon Sep 17 00:00:00 2001 From: Kai Uwe Date: Wed, 7 Jan 2026 16:34:03 +0100 Subject: [PATCH 04/71] fix(ui/rating): fix value 0 showing 1 star instead of empty --- libs/ui/src/atoms/rating.tsx | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/libs/ui/src/atoms/rating.tsx b/libs/ui/src/atoms/rating.tsx index b622fe021b..5a11de9777 100644 --- a/libs/ui/src/atoms/rating.tsx +++ b/libs/ui/src/atoms/rating.tsx @@ -11,20 +11,17 @@ const rating = tv({ control: ["flex"], itemWrapper: [ "flex items-center", - "has-[:focus-visible]:outline-none", - "has-[:focus-visible]:ring", - "has-[:focus-visible]:ring-rating-ring", + "has-focus-visible:ring", + "has-focus-visible:ring-rating-ring", ], item: [ "cursor-pointer transition-colors duration-200", "text-rating-fg", - // States - "data-[checked]:text-rating-fg-active", - "data-[highlighted]:text-rating-fg-active", - "data-[disabled]:cursor-not-allowed", - "data-[disabled]:data-[highlighted]:text-rating-fg-disabled", + "data-highlighted:text-rating-fg-active", + "data-disabled:cursor-not-allowed", + "data-disabled:data-highlighted:text-rating-fg-disabled", "token-icon-rating", - "data-[half]:token-icon-rating-half", + "data-half:token-icon-rating-half", ], }, variants: { @@ -105,8 +102,8 @@ export function Rating({ name, dir, translations, - onValueChange: ({ value }) => { - onChange?.(value) + onValueChange: ({ value: newValue }) => { + onChange?.(newValue) }, onHoverChange: ({ hoveredValue }) => { onHoverChange?.(hoveredValue) From 556f1ec0e6dd5b17f5c1f81e4ead35373112ba8f Mon Sep 17 00:00:00 2001 From: Kai Uwe Date: Wed, 7 Jan 2026 16:58:32 +0100 Subject: [PATCH 05/71] fix(ui/checkbox): align story controls with component props - Change argTypes from 'aria-invalid' to 'invalid' to match component prop - Change argTypes from 'aria-required' to 'required' for consistency - Remove RequiredState story (atom has no visual required state) - Remove Required from AllStates (visual indicator belongs in FormCheckbox molecule) --- libs/ui/src/atoms/checkbox.tsx | 5 ++-- libs/ui/stories/atoms/checkbox.stories.tsx | 28 +++++----------------- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/libs/ui/src/atoms/checkbox.tsx b/libs/ui/src/atoms/checkbox.tsx index 39d92a5d81..5aeea35f67 100644 --- a/libs/ui/src/atoms/checkbox.tsx +++ b/libs/ui/src/atoms/checkbox.tsx @@ -28,8 +28,8 @@ const checkboxVariants = tv({ "focus:outline-none", "focus-visible:ring", "focus-visible:ring-checkbox-ring-focus", - "aria-invalid:border-checkbox-border-error", - "aria-invalid:ring-checkbox-ring-error", + "data-invalid:border-checkbox-border-error", + "data-invalid:ring-checkbox-ring-error", ], }) @@ -61,6 +61,7 @@ export function Checkbox({ @@ -96,11 +92,6 @@ export const InvalidState: Story = { }, } -export const RequiredState: Story = { - args: { - 'aria-required': true, - }, -} // Advanced stories with custom renders export const WithLabel: Story = { @@ -161,13 +152,6 @@ export const AllStates: Story = { Invalid - -
- - -
) }, From 7019ed797271319fe80c567257b9885b9424709c Mon Sep 17 00:00:00 2001 From: Kai Uwe Date: Thu, 8 Jan 2026 07:32:54 +0100 Subject: [PATCH 06/71] refactor(ui/icon): remove Basic story per review feedback --- libs/ui/stories/atoms/icon.stories.tsx | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/libs/ui/stories/atoms/icon.stories.tsx b/libs/ui/stories/atoms/icon.stories.tsx index 95e7fed7ff..3e61216575 100644 --- a/libs/ui/stories/atoms/icon.stories.tsx +++ b/libs/ui/stories/atoms/icon.stories.tsx @@ -14,14 +14,6 @@ const meta: Meta = { export default meta type Story = StoryObj -// Basic usage -export const Basic: Story = { - args: { - icon: 'icon-[mdi-light--alert]', - size: 'md', - }, -} - // All sizes export const AllSizes: Story = { render: () => ( @@ -48,8 +40,6 @@ export const AllColors: Story = { - - ), From 545f13f8792b93ab8101a1fcfcedc8d0151f6535 Mon Sep 17 00:00:00 2001 From: Kai Uwe Date: Thu, 8 Jan 2026 07:49:48 +0100 Subject: [PATCH 07/71] feat(stories): rename defaults to Playground --- libs/ui/stories/atoms/checkbox.stories.tsx | 3 +-- libs/ui/stories/atoms/image.stories.tsx | 27 ++++++++++++---------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/libs/ui/stories/atoms/checkbox.stories.tsx b/libs/ui/stories/atoms/checkbox.stories.tsx index 4d19b27ff6..fd10ed850d 100644 --- a/libs/ui/stories/atoms/checkbox.stories.tsx +++ b/libs/ui/stories/atoms/checkbox.stories.tsx @@ -45,8 +45,7 @@ const meta = { export default meta type Story = StoryObj -// Basic stories -export const Default: Story = { +export const Playground: Story = { args: {}, } diff --git a/libs/ui/stories/atoms/image.stories.tsx b/libs/ui/stories/atoms/image.stories.tsx index 58e7218a7a..c28ca3d8d3 100644 --- a/libs/ui/stories/atoms/image.stories.tsx +++ b/libs/ui/stories/atoms/image.stories.tsx @@ -14,6 +14,20 @@ const meta: Meta = { }, }, }, + argTypes: { + src: { + control: 'text', + description: 'Image source URL', + }, + alt: { + control: 'text', + description: 'Alternative text for accessibility', + }, + className: { + control: 'text', + description: 'Tailwind classes for styling (size, rounded, object-fit)', + }, + }, args: { src: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800&h=400&fit=crop', alt: 'Mountain landscape', @@ -24,18 +38,8 @@ const meta: Meta = { export default meta type Story = StoryObj -// Basic usage with native img -export const Default: Story = { - render: () => ( - Small - ), -} +export const Playground: Story = {} -// Different sizes export const Sizes: Story = { render: () => (
@@ -58,7 +62,6 @@ export const Sizes: Story = { ), } -// With Next.js Image component export const WithNextImage: Story = { render: () => { return ( From 078cf6349909f6a8a4fe44207bb0bbefb625d379 Mon Sep 17 00:00:00 2001 From: Kai Uwe Date: Thu, 8 Jan 2026 08:19:05 +0100 Subject: [PATCH 08/71] fix(ui/input,button): improve disabled states and input styling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Input changes: - Rename Basic story to Playground per conventions - Reduce padding tokens (spacing-250/350/450 → 100/150/200) - Increase border width from 1px to 2px for better visibility - Use border token variable for configurable border width - Replace pointer-events-none with cursor-not-allowed - Add disabled:hover state to prevent hover effects Button changes: - Replace pointer-events-none with cursor-not-allowed - Add disabled:hover states for borderless and outlined variants - Add bg-button-bg-outlined for outlined variant base state --- libs/ui/src/atoms/button.tsx | 7 ++++--- libs/ui/src/atoms/input.tsx | 4 ++-- libs/ui/src/tokens/components/atoms/_button.css | 7 ++++--- libs/ui/src/tokens/components/atoms/_input.css | 8 ++++---- libs/ui/stories/atoms/input.stories.tsx | 4 ++-- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/libs/ui/src/atoms/button.tsx b/libs/ui/src/atoms/button.tsx index 875830f40e..9a52cb4086 100644 --- a/libs/ui/src/atoms/button.tsx +++ b/libs/ui/src/atoms/button.tsx @@ -12,7 +12,7 @@ export const buttonVariants = tv({ "focus:outline-none", "focus-visible:ring", "focus-visible:ring-button-ring", - "disabled:pointer-events-none disabled:text-button-fg-disabled", + "disabled:cursor-not-allowed disabled:text-button-fg-disabled", ], variants: { variant: { @@ -26,8 +26,9 @@ export const buttonVariants = tv({ solid: "disabled:bg-button-bg-disabled", light: "disabled:bg-button-bg-disabled", borderless: - "bg-button-bg-borderless hover:bg-button-bg-borderless-hover active:bg-button-bg-borderless-active", - outlined: "border disabled:border-button-border-disabled", + "bg-button-bg-borderless hover:bg-button-bg-borderless-hover active:bg-button-bg-borderless-active disabled:hover:bg-button-bg-borderless", + outlined: + "border bg-button-bg-outlined disabled:border-button-border-disabled disabled:hover:bg-button-bg-outlined", unstyled: "", }, uppercase: { diff --git a/libs/ui/src/atoms/input.tsx b/libs/ui/src/atoms/input.tsx index 77a39e3922..cca7215d8b 100644 --- a/libs/ui/src/atoms/input.tsx +++ b/libs/ui/src/atoms/input.tsx @@ -8,14 +8,14 @@ const inputVariants = tv({ "bg-input", "text-input-fg", "placeholder:text-input-fg-placeholder", - "border border-input-border", + "border-(length:--border-input-width) border-input-border", "rounded-input", "transition-all duration-200", "hover:border-input-border-hover hover:bg-input-hover", "focus:border-input-border-focus focus:bg-input-focus focus:outline-none", "focus-visible:ring", "focus-visible:ring-input-ring", - "disabled:pointer-events-none", + "disabled:cursor-not-allowed disabled:hover:bg-input-bg-disabled", ], variants: { size: { diff --git a/libs/ui/src/tokens/components/atoms/_button.css b/libs/ui/src/tokens/components/atoms/_button.css index 2fa119d71e..4f5efcfc85 100644 --- a/libs/ui/src/tokens/components/atoms/_button.css +++ b/libs/ui/src/tokens/components/atoms/_button.css @@ -141,6 +141,7 @@ ); /* Outlined variants */ + --color-button-bg-outlined: transparent; --color-button-bg-outlined-primary-hover: --alpha( var(--color-button-primary) / var(--opacity-outlined-hover) @@ -192,9 +193,9 @@ --spacing-button-md: var(--spacing-200); --spacing-button-lg: var(--spacing-250); - --padding-button-sm: var(--spacing-150) var(--spacing-250); - --padding-button-md: var(--spacing-200) var(--spacing-350); - --padding-button-lg: var(--spacing-250) var(--spacing-450); + --padding-button-sm: var(--spacing-50) var(--spacing-200); + --padding-button-md: var(--spacing-100) var(--spacing-250); + --padding-button-lg: var(--spacing-150) var(--spacing-400); /* === TYPOGRAPHY === */ --text-button-sm: var(--text-sm); diff --git a/libs/ui/src/tokens/components/atoms/_input.css b/libs/ui/src/tokens/components/atoms/_input.css index adbd2a4cd9..d3a91f93d4 100644 --- a/libs/ui/src/tokens/components/atoms/_input.css +++ b/libs/ui/src/tokens/components/atoms/_input.css @@ -55,9 +55,9 @@ --color-input-ring: var(--color-ring); /* Spacing - keeping your existing values */ - --padding-input-sm: var(--spacing-250) var(--spacing-250); - --padding-input-md: var(--spacing-350) var(--spacing-250); - --padding-input-lg: var(--spacing-450) var(--spacing-350); + --padding-input-sm: var(--spacing-100) var(--spacing-100); + --padding-input-md: var(--spacing-150) var(--spacing-150); + --padding-input-lg: var(--spacing-200) var(--spacing-200); --padding-with-button: var(--spacing-950); /* Typography */ @@ -66,7 +66,7 @@ --text-input-lg: var(--text-lg); /* Border */ - --border-width-input: 1px; + --border-input-width: 2px; --radius-input: var(--radius-md); /* Form field spacing */ diff --git a/libs/ui/stories/atoms/input.stories.tsx b/libs/ui/stories/atoms/input.stories.tsx index e7d2386a4d..7c828f8b1e 100644 --- a/libs/ui/stories/atoms/input.stories.tsx +++ b/libs/ui/stories/atoms/input.stories.tsx @@ -32,8 +32,8 @@ const meta: Meta = { export default meta type Story = StoryObj -// Basic input -export const Basic: Story = { +// Interactive playground +export const Playground: Story = { args: { placeholder: 'Enter text...', }, From e2ba558e81a1c6a435a5e145baeb86ef198ba547 Mon Sep 17 00:00:00 2001 From: Kai Uwe Date: Thu, 8 Jan 2026 09:07:47 +0100 Subject: [PATCH 09/71] feat(ui/numeric-input): add locale support and improve stories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Component changes: - Add locale prop for number formatting (decimal separator) - Fix data attribute syntax (data-[invalid] → data-invalid) - Fix input padding to use left-only padding token Token changes: -Fix border-width token reference (--border-width-input → --border-input-width) - Simplify padding token definition Stories improvements: - Add Playground story with full Controls support (size, locale, min, max, step, disabled, invalid, allowMouseWheel, clampValueOnBlur) - InvalidState: add clampValueOnBlur={false} to preserve invalid state on blur - WithPrecision: change step from 0.1 to 0.01 for finer control --- libs/ui/src/atoms/numeric-input.tsx | 14 ++- .../components/atoms/_numeric-input.css | 4 +- .../stories/atoms/numeric-input.stories.tsx | 117 ++++++------------ 3 files changed, 51 insertions(+), 84 deletions(-) diff --git a/libs/ui/src/atoms/numeric-input.tsx b/libs/ui/src/atoms/numeric-input.tsx index 8577389536..15a04d92d8 100644 --- a/libs/ui/src/atoms/numeric-input.tsx +++ b/libs/ui/src/atoms/numeric-input.tsx @@ -19,21 +19,22 @@ const numericInputVariants = tv({ container: [ "group border-(length:--border-width-numeric-input) relative flex", "items-center overflow-hidden rounded-numeric-input border-numeric-input-border", - "data-[invalid]:bg-numeric-input-invalid-bg", - "data-[invalid]:border-numeric-input-invalid-border", + "data-invalid:bg-numeric-input-invalid-bg", + "data-invalid:border-numeric-input-invalid-border", "text-numeric-input-fg", - "has-[input:hover]:bg-numeric-input-input-bg-hover", + "has-[input:not(:disabled):hover]:bg-numeric-input-input-bg-hover", "has-[input:focus]:bg-numeric-input-input-bg-focus", "focus-within:ring", "focus-within:ring-numeric-input-ring", ], input: [ - "border-none p-numeric-input-input", + "h-full rounded-none border-none pl-numeric-input-input", "bg-numeric-input-input-bg", "focus:bg-numeric-input-input-bg-focus", "hover:bg-numeric-input-input-bg-hover", + "disabled:hover:bg-numeric-input-input-bg", "focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0", - "duration-0 data-[invalid]:focus:border-input-border-danger-focus", + "duration-0 data-invalid:focus:border-input-border-danger-focus", ], triggerContainer: [ "flex h-fit flex-col justify-center bg-numeric-input-trigger-container-bg", @@ -106,6 +107,7 @@ export type NumericInputProps = Omit< describedBy?: string ref?: Ref id?: string + locale?: string } export function NumericInput({ @@ -135,6 +137,7 @@ export function NumericInput({ children, ref, className, + locale = "cs-CZ", ...props }: NumericInputProps) { const generatedId = useId() @@ -151,6 +154,7 @@ export function NumericInput({ step, name, disabled, + locale, required, pattern, readOnly, diff --git a/libs/ui/src/tokens/components/atoms/_numeric-input.css b/libs/ui/src/tokens/components/atoms/_numeric-input.css index acea8ba74a..644fd364b2 100644 --- a/libs/ui/src/tokens/components/atoms/_numeric-input.css +++ b/libs/ui/src/tokens/components/atoms/_numeric-input.css @@ -26,7 +26,7 @@ --color-numeric-input-input-bg: transparent; --color-numeric-input-input-bg-hover: var(--color-numeric-input-hover); --color-numeric-input-input-bg-focus: var(--color-numeric-input-focus); - --padding-numeric-input-input: 0 0 0 var(--spacing-150); + --padding-numeric-input-input: var(--spacing-150); /* === TRIGGER === */ --color-numeric-input-trigger-container-bg: var(--color-numeric-input-bg); @@ -42,7 +42,7 @@ /* === INCREMENT BUTTON === */ --radius-numeric-input: var(--radius-sm); - --border-width-numeric-input: var(--border-width-input); + --border-width-numeric-input: var(--border-input-width); /* === FOCUS RING === */ --color-numeric-input-ring: var(--color-ring); diff --git a/libs/ui/stories/atoms/numeric-input.stories.tsx b/libs/ui/stories/atoms/numeric-input.stories.tsx index 5cb273b2ea..643df3fd11 100644 --- a/libs/ui/stories/atoms/numeric-input.stories.tsx +++ b/libs/ui/stories/atoms/numeric-input.stories.tsx @@ -21,14 +21,43 @@ const meta: Meta = { export default meta type Story = StoryObj -// Default - Basic usage without label -export const Default: Story = { - render: () => { - const [value, setValue] = useState(0) - +export const Playground: Story = { + args: { + min: 0, + max: 100, + step: 0.1, + disabled: false, + invalid: false, + size: 'md', + locale: 'en-US', + allowMouseWheel: true, + clampValueOnBlur: true, + precision: 1, + }, + argTypes: { + size: { + control: 'select', + options: ['sm', 'md', 'lg'], + description: 'Size of the numeric input', + }, + locale: { + control: 'select', + options: ['en-US', 'cs-CZ', 'de-DE', 'fr-FR'], + description: 'Locale for number formatting (decimal separator)', + }, + min: { control: 'number', description: 'Minimum allowed value' }, + max: { control: 'number', description: 'Maximum allowed value' }, + step: { control: 'number', description: 'Step increment/decrement value' }, + disabled: { control: 'boolean', description: 'Disable the input' }, + invalid: { control: 'boolean', description: 'Show invalid/error state' }, + allowMouseWheel: { control: 'boolean', description: 'Allow mouse wheel to change value' }, + clampValueOnBlur: { control: 'boolean', description: 'Clamp value to min/max on blur' }, + }, + render: function Render(args) { + const [value, setValue] = useState(50.5) return (
- + @@ -37,12 +66,12 @@ export const Default: Story = { +

Current value: {value}

) }, } -// With Label - Shows label usage export const WithLabel: Story = { render: () => { const [value, setValue] = useState(42) @@ -70,7 +99,6 @@ export const WithLabel: Story = { }, } -// All Sizes - Shows all size variants export const AllSizes: Story = { render: () => { return ( @@ -124,7 +152,6 @@ export const AllSizes: Story = { }, } -// Without Controls - Clean input without visible buttons export const WithoutControls: Story = { render: () => { const [value, setValue] = useState(50) @@ -141,6 +168,7 @@ export const WithoutControls: Story = { min={0} max={100} allowMouseWheel + size='md' > @@ -154,7 +182,6 @@ export const WithoutControls: Story = { }, } -// With Scrubber - Drag to change value export const WithScrubber: Story = { render: () => { const [value, setValue] = useState(50) @@ -189,7 +216,6 @@ export const WithScrubber: Story = { }, } -// Min Max - Shows value constraints export const MinMax: Story = { render: () => { const [value, setValue] = useState(5) @@ -224,7 +250,6 @@ export const MinMax: Story = { }, } -// Invalid State - Shows error state export const InvalidState: Story = { render: () => { const [value, setValue] = useState(150) @@ -241,6 +266,7 @@ export const InvalidState: Story = { max={100} invalid={isInvalid} allowOverflow + clampValueOnBlur={false} > @@ -260,13 +286,12 @@ export const InvalidState: Story = { }, } -// Precision - Decimal values export const WithPrecision: Story = { render: () => { const [value, setValue] = useState(3.14) return ( -
+
@@ -276,7 +301,7 @@ export const WithPrecision: Story = { onChange={setValue} min={0} max={10} - step={0.1} + step={0.01} precision={2} > @@ -295,7 +320,6 @@ export const WithPrecision: Story = { }, } -// Custom Layout - Horizontal triggers export const CustomLayoutHorizontal: Story = { render: () => { const [value, setValue] = useState(50) @@ -323,8 +347,6 @@ export const CustomLayoutHorizontal: Story = { }, } - -// Disabled State export const Disabled: Story = { render: () => { return ( @@ -344,7 +366,6 @@ export const Disabled: Story = { }, } -// Custom Button Props - Shows Button customization options export const CustomButtonProps: Story = { render: () => { const [value1, setValue1] = useState(10) @@ -354,7 +375,6 @@ export const CustomButtonProps: Story = { return (
- {/* Variant customization */}
- {/* Size customization */}
{ - const [value, setValue] = useState(50) - const [min] = useState(0) - const [max] = useState(100) - const [step] = useState(5) - - return ( -
-
- - - - - - - - - - -
- -
-

Info

-
    -
  • - Current value: {value} -
  • -
  • - Min: {min}, Max: {max} -
  • -
  • Step: {step}
  • -
  • Features:
  • -
      -
    • ✓ Arrow keys (↑/↓)
    • -
    • ✓ Button controls
    • -
    • ✓ Keyboard shortcuts (Home/End, Page Up/Down)
    • -
    -
-
-
- ) - }, -} From 5fdb2d49521fd148631c962360c2f4ea0bb4a8b0 Mon Sep 17 00:00:00 2001 From: Kai Uwe Date: Thu, 8 Jan 2026 09:13:59 +0100 Subject: [PATCH 10/71] refactor(ui): add storybook playground and standardize skeleton sizes --- libs/ui/stories/atoms/skeleton.stories.tsx | 54 ++++++++++++++-------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/libs/ui/stories/atoms/skeleton.stories.tsx b/libs/ui/stories/atoms/skeleton.stories.tsx index e16fe07359..d4ee18b125 100644 --- a/libs/ui/stories/atoms/skeleton.stories.tsx +++ b/libs/ui/stories/atoms/skeleton.stories.tsx @@ -22,8 +22,30 @@ type Story = StoryObj // ===== BASIC USAGE ===== -export const Basic: Story = { - render: () => , +export const Playground: Story = { + args: { + variant: 'primary', + speed: 'normal', + }, + argTypes: { + variant: { + control: 'select', + options: ['primary', 'secondary'], + description: 'Visual variant of the skeleton', + }, + speed: { + control: 'select', + options: ['slow', 'normal', 'fast'], + description: 'Animation speed', + }, + }, + render: (args) => ( +
+ + + +
+ ), } export const WithContent: Story = { @@ -38,8 +60,8 @@ export const WithContent: Story = { > Toggle Loaded State - -
+ +
✨ Content loaded!
@@ -53,11 +75,11 @@ export const Variants: Story = {

Primary (default)

- +

Secondary

- +
), @@ -68,15 +90,15 @@ export const AnimationSpeed: Story = {

Slow (3s)

- +

Normal (2s) - default

- +

Fast (1s)

- +
), @@ -125,8 +147,6 @@ export const SpeedInheritance: Story = { ), } -// ===== CIRCLE VARIANTS ===== - export const CircleSizes: Story = { render: () => (
@@ -256,8 +276,6 @@ export const TextNumberOfLines: Story = { ), } -// ===== RECTANGLE VARIANTS ===== - export const RectangleAspectRatios: Story = { render: () => (
@@ -286,18 +304,16 @@ export const RectangleFixedDimensions: Story = {

Fixed width + height

- +
), } -// ===== COMPOSITION EXAMPLES ===== - export const ProductCardSkeleton: Story = { name: '🛍️ Product Card', render: () => ( -
+
@@ -344,8 +360,6 @@ export const FeedSkeleton: Story = { ), } -// ===== ACCESSIBILITY ===== - export const ReducedMotion: Story = { name: '♿ Reduced Motion', parameters: { @@ -364,7 +378,7 @@ export const ReducedMotion: Story = { in their OS, animations automatically switch to the static state shown below.

- +
From edb579a2f29ef065c28d0716b41ccdb447ab2823 Mon Sep 17 00:00:00 2001 From: Kai Uwe Date: Thu, 8 Jan 2026 09:32:55 +0100 Subject: [PATCH 11/71] refactor(status-text): add alignment and simplify icon spacing vars --- libs/ui/src/atoms/status-text.tsx | 28 +++++++++++------ .../tokens/components/atoms/_status-text.css | 9 +++--- libs/ui/stories/atoms/status-text.stories.tsx | 30 ++----------------- 3 files changed, 26 insertions(+), 41 deletions(-) diff --git a/libs/ui/src/atoms/status-text.tsx b/libs/ui/src/atoms/status-text.tsx index db51c33af7..25a9479e45 100644 --- a/libs/ui/src/atoms/status-text.tsx +++ b/libs/ui/src/atoms/status-text.tsx @@ -6,7 +6,7 @@ import { Icon } from "./icon" const statusTextVariants = tv({ slots: { base: "flex items-center", - icon: "self-start" + icon: "", }, variants: { status: { @@ -15,24 +15,30 @@ const statusTextVariants = tv({ warning: "text-status-text-fg-warning", default: "text-status-text-fg-default", }, + /* for long text */ + align: { + start: { + icon: "mt-status-text-icon-long-text self-start", + }, + center: {}, + }, size: { sm: { - base: "text-status-text-sm gap-status-text-icon-gap-sm", - icon: "mt-status-text-icon-sm", + base: "gap-status-text-icon-gap-sm text-status-text-sm", }, md: { - base: "text-status-text-md gap-status-text-icon-gap-md", - icon: "mt-status-text-icon-md", + base: "gap-status-text-icon-gap-md text-status-text-md", }, lg: { - base: "text-status-text-lg gap-status-text-icon-gap-lg", - icon: "mt-status-text-icon-lg", + base: "items-start gap-status-text-icon-gap-lg text-status-text-lg", + icon: "mt-status-text-icon", }, }, }, defaultVariants: { status: "default", size: "md", + align: "center", }, }) @@ -56,15 +62,17 @@ export function StatusText({ showIcon = false, status = "default", size = "md", + align = "center", children, ref, ...props }: StatusTextProps) { const icon = ICON_MAP[status] - const {base, icon: iconSlot} = statusTextVariants({ + const { base, icon: iconSlot } = statusTextVariants({ status, size, + align, className, }) @@ -78,7 +86,9 @@ export function StatusText({ ref={ref} {...props} > - {showIcon && icon && } + {showIcon && icon && ( + + )} {children}
) diff --git a/libs/ui/src/tokens/components/atoms/_status-text.css b/libs/ui/src/tokens/components/atoms/_status-text.css index 5a097764f6..21bb249f6b 100644 --- a/libs/ui/src/tokens/components/atoms/_status-text.css +++ b/libs/ui/src/tokens/components/atoms/_status-text.css @@ -4,7 +4,7 @@ --color-status-text-fg-success: var(--color-success); --color-status-text-fg-warning: var(--color-warning); --color-status-text-fg-default: var(--color-fg-secondary); - + /* Text sizes - inherit from input sizing */ --text-status-text-sm: var(--text-input-sm); --text-status-text-md: var(--text-input-md); @@ -15,9 +15,8 @@ --spacing-status-text-icon-gap-md: var(--spacing-100); --spacing-status-text-icon-gap-lg: var(--spacing-200); - --spacing-status-text-icon-sm: var(--spacing-50); - --spacing-status-text-icon-md: var(--spacing-100); - --spacing-status-text-icon-lg: var(--spacing-200); + --spacing-status-text-icon: var(--spacing-50); + --spacing-status-text-icon-long-text: var(--spacing-100); } @utility token-icon-status-text-success { @@ -30,4 +29,4 @@ @utility token-icon-status-text-error { @apply token-icon-error; -} \ No newline at end of file +} diff --git a/libs/ui/stories/atoms/status-text.stories.tsx b/libs/ui/stories/atoms/status-text.stories.tsx index 6e35f9dffa..712181a1c1 100644 --- a/libs/ui/stories/atoms/status-text.stories.tsx +++ b/libs/ui/stories/atoms/status-text.stories.tsx @@ -44,30 +44,6 @@ export const Default: Story = { }, } -export const Error: Story = { - args: { - children: "Invalid email format", - status: "error", - showIcon: true, - }, -} - -export const Success: Story = { - args: { - children: "Username is available", - status: "success", - showIcon: true, - }, -} - -export const Warning: Story = { - args: { - children: "Password is weak", - status: "warning", - showIcon: true, - }, -} - export const AllStatuses: Story = { render: () => ( @@ -227,7 +203,7 @@ export const LongText: Story = {
- + Your password must contain at least 8 characters, including one uppercase letter, one lowercase letter, one number, and one special character (!@#$%^&*). @@ -235,7 +211,7 @@ export const LongText: Story = { - + Your account has been successfully created! We've sent a confirmation email to your address. Please check your inbox and click the verification link. @@ -243,7 +219,7 @@ export const LongText: Story = { - + This action cannot be undone. Please make sure you have backed up your data before proceeding. We recommend downloading a copy of your information first. From 6d06ae9330c35cef271a451bb3d53987d634e811 Mon Sep 17 00:00:00 2001 From: Kai Uwe Date: Thu, 8 Jan 2026 10:29:53 +0100 Subject: [PATCH 12/71] feat(ui): add Playground story and expand textarea controls for Storybook --- libs/ui/stories/atoms/textarea.stories.tsx | 65 +++++++++++++--------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/libs/ui/stories/atoms/textarea.stories.tsx b/libs/ui/stories/atoms/textarea.stories.tsx index 0a7612044c..b4a05ebd7d 100644 --- a/libs/ui/stories/atoms/textarea.stories.tsx +++ b/libs/ui/stories/atoms/textarea.stories.tsx @@ -14,9 +14,46 @@ const meta: Meta = { export default meta type Story = StoryObj -/** - * Displays the available sizes for the textarea component. - */ +export const Playground: Story = { + args: { + placeholder: 'Enter your text...', + size: 'md', + variant: 'default', + resize: 'y', + disabled: false, + readonly: false, + }, + argTypes: { + size: { + control: 'select', + options: ['sm', 'md', 'lg'], + description: 'Size of the textarea', + }, + variant: { + control: 'select', + options: ['default', 'error', 'success', 'warning', 'borderless'], + description: 'Visual variant for validation states', + }, + resize: { + control: 'select', + options: ['none', 'y', 'x', 'both', 'auto'], + description: 'Resize behavior (auto = grows with content)', + }, + disabled: { + control: 'boolean', + description: 'Disable the textarea', + }, + readonly: { + control: 'boolean', + description: 'Make textarea read-only (still focusable, sent on submit)', + }, + placeholder: { + control: 'text', + description: 'Placeholder text', + }, + }, +} + export const Sizes: Story = { render: () => ( @@ -29,9 +66,6 @@ export const Sizes: Story = { ), } -/** - * Shows the various validation states for visual feedback. - */ export const ValidationStates: Story = { render: () => ( @@ -46,10 +80,6 @@ export const ValidationStates: Story = { ), } - -/** - * Comprehensive overview of all key variants. - */ export const AllVariants: Story = { render: () => ( @@ -84,9 +114,6 @@ export const AllVariants: Story = { ), } -/** - * Demonstrates auto-sizing textarea that grows with content. - */ export const AutoSizing: Story = { render: () => ( @@ -105,41 +132,29 @@ export const AutoSizing: Story = { ), } -/** - * Practical combinations for real-world use cases. - */ export const UseCaseCombinations: Story = { render: () => ( - {/* Use Case 1: Product description for admins */}