feat: add focus-visible ring styles

This commit is contained in:
2025-10-08 19:23:08 -06:00
parent 396f8d2fde
commit 7287c39ea6
6 changed files with 161 additions and 46 deletions

View File

@@ -27,6 +27,9 @@ const buttonRecipe = ({ semanticColorNames }: ButtonRecipeArg) => {
transitionDuration: 'normal', transitionDuration: 'normal',
transitionTimingFunction: 'easeOut', transitionTimingFunction: 'easeOut',
fontWeight: 'semibold', fontWeight: 'semibold',
focusVisibleRing: 'outside',
focusRingWidth: '2px',
focusRingOffset: '0px',
_disabled: { _disabled: {
cursor: 'not-allowed', cursor: 'not-allowed',
_hover: { _hover: {
@@ -36,7 +39,14 @@ const buttonRecipe = ({ semanticColorNames }: ButtonRecipeArg) => {
}, },
variants: { variants: {
color: { color: {
...Object.fromEntries(colorVariants.map((color) => [color, {}])) ...Object.fromEntries(
colorVariants.map((color) => [
color,
{
focusRingColor: `{colors.${color}.7}`
}
])
)
}, },
size: { size: {
...Object.fromEntries( ...Object.fromEntries(
@@ -95,6 +105,10 @@ const buttonRecipe = ({ semanticColorNames }: ButtonRecipeArg) => {
backgroundColor: `${color}.10`, backgroundColor: `${color}.10`,
color: `${color}.foreground` color: `${color}.foreground`
}, },
_focusVisible: {
backgroundColor: `${color}.10`,
color: `${color}.foreground`
},
_active: { _active: {
backgroundColor: `color-mix(in srgb, {colors.${color}.9} 80%, {colors.${color}.1})`, backgroundColor: `color-mix(in srgb, {colors.${color}.9} 80%, {colors.${color}.1})`,
color: `${color}.foreground` color: `${color}.foreground`
@@ -105,7 +119,7 @@ const buttonRecipe = ({ semanticColorNames }: ButtonRecipeArg) => {
borderColor: `color-mix(in srgb, {colors.${color}.9} 10%, transparent)`, borderColor: `color-mix(in srgb, {colors.${color}.9} 10%, transparent)`,
_hover: { _hover: {
backgroundColor: `color-mix(in srgb, {colors.${color}.9} 10%, transparent)`, backgroundColor: `color-mix(in srgb, {colors.${color}.9} 10%, transparent)`,
color: `${color}.4/50` color: `color-mix(in srgb, {colors.${color}.foreground} 40%, transparent)`
} }
} }
} }
@@ -122,6 +136,10 @@ const buttonRecipe = ({ semanticColorNames }: ButtonRecipeArg) => {
backgroundColor: `${color}.10`, backgroundColor: `${color}.10`,
color: `${color}.foreground` color: `${color}.foreground`
}, },
_focusVisible: {
backgroundColor: `${color}.10`,
color: `${color}.foreground`
},
_active: { _active: {
backgroundColor: `color-mix(in srgb, {colors.${color}.9} 80%, {colors.${color}.1})`, backgroundColor: `color-mix(in srgb, {colors.${color}.9} 80%, {colors.${color}.1})`,
color: `${color}.foreground` color: `${color}.foreground`
@@ -130,13 +148,14 @@ const buttonRecipe = ({ semanticColorNames }: ButtonRecipeArg) => {
backgroundColor: `color-mix(in srgb, {colors.${color}.9} 10%, transparent)`, backgroundColor: `color-mix(in srgb, {colors.${color}.9} 10%, transparent)`,
color: `color-mix(in srgb, {colors.${color}.foreground} 40%, transparent)`, color: `color-mix(in srgb, {colors.${color}.foreground} 40%, transparent)`,
_hover: { _hover: {
backgroundColor: `color-mix(in srgb, {colors.${color}.9} 10%, transparent)` backgroundColor: `color-mix(in srgb, {colors.${color}.9} 10%, transparent)`,
color: `color-mix(in srgb, {colors.${color}.foreground} 40%, transparent)`
} }
} }
} }
} }
// case 'solid' or unmatched: // case 'solid' or unmatched:
default: case 'solid':
return { return {
color, color,
variant, variant,
@@ -152,6 +171,10 @@ const buttonRecipe = ({ semanticColorNames }: ButtonRecipeArg) => {
backgroundColor: `color-mix(in srgb, {colors.${color}.9} 80%, {colors.${color}.1})`, backgroundColor: `color-mix(in srgb, {colors.${color}.9} 80%, {colors.${color}.1})`,
color: `${color}.foreground` color: `${color}.foreground`
}, },
_focusVisible: {
backgroundColor: `${color}.10`,
color: `${color}.foreground`
},
_disabled: { _disabled: {
backgroundColor: `color-mix(in srgb, {colors.${color}.9} 40%, transparent)`, backgroundColor: `color-mix(in srgb, {colors.${color}.9} 40%, transparent)`,
color: `color-mix(in srgb, {colors.${color}.foreground} 40%, transparent)`, color: `color-mix(in srgb, {colors.${color}.foreground} 40%, transparent)`,
@@ -162,6 +185,12 @@ const buttonRecipe = ({ semanticColorNames }: ButtonRecipeArg) => {
} }
} }
} }
default:
return {
color,
variant,
css: {}
}
} }
}) })
), ),

View File

@@ -31,7 +31,10 @@ const cardRecipe = ({ semanticColorNames }: CardRecipeArg) => {
transitionProperty: 'color, background-color, border-color', transitionProperty: 'color, background-color, border-color',
transitionDuration: 'normal', transitionDuration: 'normal',
transitionTimingFunction: 'easeOut', transitionTimingFunction: 'easeOut',
focusRingColor: 'var(--card-border-color-hover)', focusVisibleRing: 'outside',
focusRingColor: 'var(--card-background-color-hover)',
focusRingWidth: '2px',
focusRingOffset: '0px',
_hover: { _hover: {
borderColor: 'var(--card-border-color-hover)', borderColor: 'var(--card-border-color-hover)',
backgroundColor: 'var(--card-background-color-hover)', backgroundColor: 'var(--card-background-color-hover)',
@@ -44,8 +47,6 @@ const cardRecipe = ({ semanticColorNames }: CardRecipeArg) => {
backgroundColor: 'var(--card-background-color-active)' backgroundColor: 'var(--card-background-color-active)'
}, },
_focus: { _focus: {
focusRing: 'outside',
focusRingWidth: '2px',
borderColor: 'var(--card-border-color-hover)', borderColor: 'var(--card-border-color-hover)',
backgroundColor: 'var(--card-background-color-hover)', backgroundColor: 'var(--card-background-color-hover)',
'& .card__header': { '& .card__header': {

View File

@@ -38,8 +38,15 @@ const detailsRecipe = ({ semanticColorNames }: DetailsRecipeArg) => {
transitionProperty: 'color, background-color', transitionProperty: 'color, background-color',
transitionDuration: 'normal', transitionDuration: 'normal',
transitionTimingFunction: 'easeIn', transitionTimingFunction: 'easeIn',
focusVisibleRing: 'outside',
focusRingColor: 'var(--details-background-widget-hover)',
focusRingWidth: '2px',
focusRingOffset: '0px',
_hover: { _hover: {
backgroundColor: 'var(--details-background-widget-hover)' backgroundColor: 'var(--details-background-widget-hover)'
},
_focusVisible: {
backgroundColor: 'var(--details-background-widget-hover)'
} }
}, },
content: { content: {
@@ -64,6 +71,8 @@ const detailsRecipe = ({ semanticColorNames }: DetailsRecipeArg) => {
'--details-background-widget': `colors.${color}.3`, '--details-background-widget': `colors.${color}.3`,
'--details-background-widget-hover': `colors.${color}.4`, '--details-background-widget-hover': `colors.${color}.4`,
'--details-background-widget-active': `colors.${color}.5`, '--details-background-widget-active': `colors.${color}.5`,
'--details-border-color': `colors.${color}.6`,
'--details-border-color-hover': `colors.${color}.7`,
'--details-color': `colors.${color}.12` '--details-color': `colors.${color}.12`
} as SystemStyleObject } as SystemStyleObject
] ]

View File

@@ -23,6 +23,9 @@ const iconButtonRecipe = ({ semanticColorNames }: IconButtonRecipeArg) => {
transitionProperty: 'color, background-color, border-color', transitionProperty: 'color, background-color, border-color',
transitionDuration: 'normal', transitionDuration: 'normal',
transitionTimingFunction: 'easeOut', transitionTimingFunction: 'easeOut',
focusVisibleRing: 'outside',
focusRingWidth: '2px',
focusRingOffset: '0px',
_disabled: { _disabled: {
cursor: 'not-allowed', cursor: 'not-allowed',
_hover: { _hover: {
@@ -56,7 +59,14 @@ const iconButtonRecipe = ({ semanticColorNames }: IconButtonRecipeArg) => {
) )
}, },
color: { color: {
...Object.fromEntries(colors.map((color) => [color, {}])) ...Object.fromEntries(
colors.map((color) => [
color,
{
focusRingColor: `{colors.${color}.6}`
}
])
)
}, },
variant: { variant: {
...Object.fromEntries(iconButtonVariants.map((variant) => [variant, {}])) ...Object.fromEntries(iconButtonVariants.map((variant) => [variant, {}]))
@@ -92,6 +102,9 @@ const iconButtonRecipe = ({ semanticColorNames }: IconButtonRecipeArg) => {
_hover: { _hover: {
backgroundColor: `${color}.10` backgroundColor: `${color}.10`
}, },
_focusVisible: {
backgroundColor: `${color}.10`
},
_active: { _active: {
backgroundColor: `color-mix(in srgb, {colors.${color}.9} 80%, {colors.${color}.1})` backgroundColor: `color-mix(in srgb, {colors.${color}.9} 80%, {colors.${color}.1})`
}, },
@@ -118,6 +131,10 @@ const iconButtonRecipe = ({ semanticColorNames }: IconButtonRecipeArg) => {
backgroundColor: `${color}.10`, backgroundColor: `${color}.10`,
color: `${color}.foreground` color: `${color}.foreground`
}, },
_focusVisible: {
backgroundColor: `${color}.10`,
color: `${color}.foreground`
},
_active: { _active: {
backgroundColor: `color-mix(in srgb, {colors.${color}.9} 80%, {colors.${color}.1})`, backgroundColor: `color-mix(in srgb, {colors.${color}.9} 80%, {colors.${color}.1})`,
color: `${color}.foreground` color: `${color}.foreground`
@@ -145,6 +162,10 @@ const iconButtonRecipe = ({ semanticColorNames }: IconButtonRecipeArg) => {
backgroundColor: `${color}.10`, backgroundColor: `${color}.10`,
color: `${color}.foreground` color: `${color}.foreground`
}, },
_focusVisible: {
backgroundColor: `${color}.10`,
color: `${color}.foreground`
},
_active: { _active: {
backgroundColor: `color-mix(in srgb, {colors.${color}.9} 80%, {colors.${color}.1})`, backgroundColor: `color-mix(in srgb, {colors.${color}.9} 80%, {colors.${color}.1})`,
color: `${color}.foreground` color: `${color}.foreground`

View File

@@ -1,5 +1,6 @@
import { defineRecipe } from '@pandacss/dev' import { defineRecipe } from '@pandacss/dev'
export const inputSizes = ['small', 'medium', 'large'] as const
export interface InputRecipeArg { export interface InputRecipeArg {
semanticColorNames: string[] semanticColorNames: string[]
} }
@@ -18,27 +19,50 @@ export const inputRecipe = ({ semanticColorNames }: InputRecipeArg) => {
transitionDuration: 'normal', transitionDuration: 'normal',
transitionTimingFunction: 'easeOut', transitionTimingFunction: 'easeOut',
cursor: 'text', cursor: 'text',
focusVisibleRing: 'outside',
focusRingWidth: '2px',
focusRingOffset: '0px',
_disabled: { _disabled: {
cursor: 'not-allowed' cursor: 'not-allowed'
} }
}, },
variants: { variants: {
size: { size: {
small: { ...Object.fromEntries(
paddingBlock: 'calc({spacing.xs} * 0.5)', inputSizes.map((size) => {
paddingInline: 'calc({spacing.xs} * 1)', switch (size) {
fontSize: 'sm' case 'small':
}, return [
medium: { size,
paddingBlock: 'calc({spacing.xs} * 0.75)', {
paddingInline: 'calc({spacing.xs} * 1.25)', paddingBlock: 'calc({spacing.xs} * 0.5)',
fontSize: 'base' paddingInline: 'calc({spacing.xs} * 1)',
}, fontSize: 'sm'
large: { }
paddingBlock: 'calc({spacing.xs} * 1)', ]
paddingInline: 'calc({spacing.xs} * 1.5)', case 'medium':
fontSize: 'lg' return [
} size,
{
paddingBlock: 'calc({spacing.xs} * 0.75)',
paddingInline: 'calc({spacing.xs} * 1.25)',
fontSize: 'base'
}
]
case 'large':
return [
size,
{
paddingBlock: 'calc({spacing.xs} * 1)',
paddingInline: 'calc({spacing.xs} * 1.5)',
fontSize: 'lg'
}
]
default:
return [size, {}]
}
})
)
}, },
color: { color: {
...Object.fromEntries( ...Object.fromEntries(
@@ -48,6 +72,7 @@ export const inputRecipe = ({ semanticColorNames }: InputRecipeArg) => {
borderColor: `${color}.6`, borderColor: `${color}.6`,
backgroundColor: `${color}.2`, backgroundColor: `${color}.2`,
color: `${color}.12`, color: `${color}.12`,
focusRingColor: `{colors.${color}.7}`,
_placeholder: { _placeholder: {
color: `${color}.11` color: `${color}.11`
}, },

View File

@@ -1,6 +1,6 @@
import { defineSlotRecipe, type SystemStyleObject } from '@pandacss/dev' import { defineSlotRecipe, type SystemStyleObject } from '@pandacss/dev'
const selectSlots = [ export const selectSlots = [
'trigger', 'trigger',
'content', 'content',
'viewport', 'viewport',
@@ -11,6 +11,7 @@ const selectSlots = [
'groupLabel', 'groupLabel',
'separator' 'separator'
] as const ] as const
export const selectSizes = ['small', 'medium', 'large'] as const
export interface SelectRecipeArg { export interface SelectRecipeArg {
semanticColorNames: string[] semanticColorNames: string[]
@@ -35,7 +36,10 @@ const selectRecipe = ({ semanticColorNames }: SelectRecipeArg) => {
cursor: 'pointer', cursor: 'pointer',
transitionProperty: 'color, background-color, border-color', transitionProperty: 'color, background-color, border-color',
transitionDuration: 'normal', transitionDuration: 'normal',
transitionTimingFunction: 'easeOut' transitionTimingFunction: 'easeOut',
focusVisibleRing: 'outside',
focusRingWidth: '2px',
focusRingOffset: '0px'
}, },
content: { content: {
overflow: 'hidden', overflow: 'hidden',
@@ -81,6 +85,10 @@ const selectRecipe = ({ semanticColorNames }: SelectRecipeArg) => {
transitionProperty: 'color, background-color', transitionProperty: 'color, background-color',
transitionDuration: 'normal', transitionDuration: 'normal',
transitionTimingFunction: 'easeOut', transitionTimingFunction: 'easeOut',
focusRingColor: 'var(--select-border-color-focus)',
focusVisibleRing: 'outside',
focusRingWidth: '2px',
focusRingOffset: '0px',
_highlighted: { _highlighted: {
backgroundColor: 'var(--select-background-color-hover)' backgroundColor: 'var(--select-background-color-hover)'
}, },
@@ -124,27 +132,47 @@ const selectRecipe = ({ semanticColorNames }: SelectRecipeArg) => {
}, },
variants: { variants: {
size: { size: {
small: { ...Object.fromEntries(
trigger: { selectSizes.map((size) => {
paddingBlock: 'calc({spacing.xs} * 0.5)', switch (size) {
paddingInline: 'calc({spacing.xs} * 1)', case 'small':
fontSize: 'sm' return [
} size,
}, {
medium: { trigger: {
trigger: { paddingBlock: 'calc({spacing.xs} * 0.5)',
paddingBlock: 'calc({spacing.xs} * 0.75)', paddingInline: 'calc({spacing.xs} * 1)',
paddingInline: 'calc({spacing.xs} * 1.25)', fontSize: 'sm'
fontSize: 'base' }
} }
}, ]
large: { case 'medium':
trigger: { return [
paddingBlock: 'calc({spacing.xs} * 1)', size,
paddingInline: 'calc({spacing.xs} * 1.5)', {
fontSize: 'lg' trigger: {
} paddingBlock: 'calc({spacing.xs} * 0.75)',
} paddingInline: 'calc({spacing.xs} * 1.25)',
fontSize: 'base'
}
}
]
case 'large':
return [
size,
{
trigger: {
paddingBlock: 'calc({spacing.xs} * 1)',
paddingInline: 'calc({spacing.xs} * 1.5)',
fontSize: 'lg'
}
}
]
default:
return [size, {}]
}
})
)
}, },
color: { color: {
...Object.fromEntries( ...Object.fromEntries(
@@ -161,6 +189,7 @@ const selectRecipe = ({ semanticColorNames }: SelectRecipeArg) => {
backgroundColor: `${color}.2`, backgroundColor: `${color}.2`,
color: `${color}.12`, color: `${color}.12`,
borderColor: `${color}.6`, borderColor: `${color}.6`,
focusRingColor: `{colors.${color}.7}`,
_placeholder: { _placeholder: {
color: `${color}.11` color: `${color}.11`
}, },
@@ -203,6 +232,7 @@ const selectRecipe = ({ semanticColorNames }: SelectRecipeArg) => {
'--select-background-color-active': `{colors.${color}.5}`, '--select-background-color-active': `{colors.${color}.5}`,
'--select-background-color-disabled': `color-mix(in srgb, {colors.${color}.2} 40%, transparent)`, '--select-background-color-disabled': `color-mix(in srgb, {colors.${color}.2} 40%, transparent)`,
'--select-border-color': `{colors.${color}.6}`, '--select-border-color': `{colors.${color}.6}`,
'--select-border-color-focus': `{colors.${color}.7}`,
'--select-color-subtle': `{colors.${color}.11}`, '--select-color-subtle': `{colors.${color}.11}`,
'--select-color': `{colors.${color}.12}`, '--select-color': `{colors.${color}.12}`,
'--select-color-disabled': `color-mix(in srgb, {colors.${color}.11} 40%, transparent)` '--select-color-disabled': `color-mix(in srgb, {colors.${color}.11} 40%, transparent)`