feat: add select component recipe with semantic color variants
This commit is contained in:
@@ -14,6 +14,7 @@ import alertRecipe from './recipes/alert.js'
|
|||||||
import chipRecipe from './recipes/chip.js'
|
import chipRecipe from './recipes/chip.js'
|
||||||
import iconButtonRecipe from './recipes/iconButton.js'
|
import iconButtonRecipe from './recipes/iconButton.js'
|
||||||
import markRecipe from './recipes/mark.js'
|
import markRecipe from './recipes/mark.js'
|
||||||
|
import selectRecipe from './recipes/select.js'
|
||||||
|
|
||||||
export type ThemeConfig = {
|
export type ThemeConfig = {
|
||||||
neutral?: NeutralColor
|
neutral?: NeutralColor
|
||||||
@@ -48,7 +49,8 @@ const srJuggernautPandaPreset = (config?: ThemeConfig) => {
|
|||||||
details: detailsRecipe({ semanticColorNames: semanticColorKeysArray }),
|
details: detailsRecipe({ semanticColorNames: semanticColorKeysArray }),
|
||||||
mark: markRecipe({ semanticColorNames: semanticColorKeysArray }),
|
mark: markRecipe({ semanticColorNames: semanticColorKeysArray }),
|
||||||
input: inputRecipe({ semanticColorNames: semanticColorKeysArray }),
|
input: inputRecipe({ semanticColorNames: semanticColorKeysArray }),
|
||||||
iconButton: iconButtonRecipe({ semanticColorNames: semanticColorKeysArray })
|
iconButton: iconButtonRecipe({ semanticColorNames: semanticColorKeysArray }),
|
||||||
|
select: selectRecipe({ semanticColorNames: semanticColorKeysArray })
|
||||||
},
|
},
|
||||||
tokens: {
|
tokens: {
|
||||||
animations: {},
|
animations: {},
|
||||||
@@ -186,6 +188,11 @@ const srJuggernautPandaPreset = (config?: ThemeConfig) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
conditions: {
|
||||||
|
extend: {
|
||||||
|
active: '&[data-state="open"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
globalCss: {
|
globalCss: {
|
||||||
'*, *:before, *:after': {
|
'*, *:before, *:after': {
|
||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
|
|||||||
234
src/recipes/select.ts
Normal file
234
src/recipes/select.ts
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
import { defineSlotRecipe, type SystemStyleObject } from '@pandacss/dev'
|
||||||
|
|
||||||
|
const selectSlots = [
|
||||||
|
'trigger',
|
||||||
|
'content',
|
||||||
|
'viewport',
|
||||||
|
'scrollButton',
|
||||||
|
'item',
|
||||||
|
'itemIndicator',
|
||||||
|
'group',
|
||||||
|
'groupLabel',
|
||||||
|
'separator'
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export interface SelectRecipeArg {
|
||||||
|
semanticColorNames: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectRecipe = ({ semanticColorNames }: SelectRecipeArg) => {
|
||||||
|
const colorVariants = ['neutral', ...semanticColorNames]
|
||||||
|
return defineSlotRecipe({
|
||||||
|
className: 'select',
|
||||||
|
slots: selectSlots,
|
||||||
|
base: {
|
||||||
|
trigger: {
|
||||||
|
all: 'unset',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
gap: 'xs',
|
||||||
|
minWidth: 'fit-content',
|
||||||
|
borderRadius: 'sm',
|
||||||
|
border: '1px solid',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transitionProperty: 'color, background-color, border-color',
|
||||||
|
transitionDuration: 'normal',
|
||||||
|
transitionTimingFunction: 'easeOut'
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
overflow: 'hidden',
|
||||||
|
maxHeight: 'var(--radix-select-content-available-height, 90dvh)',
|
||||||
|
width: 'var(--radix-select-trigger-width, min(max-content, 90dvw))',
|
||||||
|
backgroundColor: 'var(--select-background-color)',
|
||||||
|
color: 'var(--select-color)',
|
||||||
|
borderRadius: 'sm',
|
||||||
|
_active: {
|
||||||
|
animationName: 'fade',
|
||||||
|
animationDuration: 'normal'
|
||||||
|
},
|
||||||
|
'&[data-state=closed]': {
|
||||||
|
animationName: 'fade',
|
||||||
|
animationDuration: 'normal',
|
||||||
|
animationDirection: 'reverse'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
viewport: {
|
||||||
|
padding: 'xs'
|
||||||
|
},
|
||||||
|
scrollButton: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: 'var(--select-color-subtle)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transitionProperty: 'color, background-color',
|
||||||
|
transitionDuration: 'normal',
|
||||||
|
transitionTimingFunction: 'easeOut',
|
||||||
|
'&:hover': {
|
||||||
|
color: 'var(--select-color)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
gap: 'xs',
|
||||||
|
padding: 'xs',
|
||||||
|
borderRadius: 'sm',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transitionProperty: 'color, background-color',
|
||||||
|
transitionDuration: 'normal',
|
||||||
|
transitionTimingFunction: 'easeOut',
|
||||||
|
_highlighted: {
|
||||||
|
backgroundColor: 'var(--select-background-color-hover)'
|
||||||
|
},
|
||||||
|
'&[data-state=checked]': {
|
||||||
|
backgroundColor: 'var(--select-background-color-active)'
|
||||||
|
},
|
||||||
|
_disabled: {
|
||||||
|
color: 'var(--select-color-disabled)',
|
||||||
|
backgroundColor: 'var(--select-background-color-disabled)',
|
||||||
|
cursor: 'not-allowed',
|
||||||
|
_highlighted: {
|
||||||
|
backgroundColor: 'var(--select-background-color-disabled)',
|
||||||
|
color: 'var(--select-color-disabled)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemIndicator: {
|
||||||
|
color: 'var(--select-color-subtle)'
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 'xs',
|
||||||
|
marginInline: '-xs',
|
||||||
|
borderBlock: '1px solid var(--select-border-color)',
|
||||||
|
paddingInline: 'xs'
|
||||||
|
},
|
||||||
|
groupLabel: {
|
||||||
|
color: 'var(--select-color-subtle)',
|
||||||
|
fontSize: 'sm',
|
||||||
|
fontWeight: 'semibold',
|
||||||
|
lineHeight: 'tight',
|
||||||
|
userSelect: 'none'
|
||||||
|
},
|
||||||
|
separator: {
|
||||||
|
height: '1px',
|
||||||
|
backgroundColor: 'var(--select-border-color)',
|
||||||
|
marginBlock: 'xs',
|
||||||
|
marginInline: '-xs'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
small: {
|
||||||
|
trigger: {
|
||||||
|
paddingBlock: 'calc({spacing.xs} * 0.5)',
|
||||||
|
paddingInline: 'calc({spacing.xs} * 1)',
|
||||||
|
fontSize: 'sm'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
medium: {
|
||||||
|
trigger: {
|
||||||
|
paddingBlock: 'calc({spacing.xs} * 0.75)',
|
||||||
|
paddingInline: 'calc({spacing.xs} * 1.25)',
|
||||||
|
fontSize: 'base'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
large: {
|
||||||
|
trigger: {
|
||||||
|
paddingBlock: 'calc({spacing.xs} * 1)',
|
||||||
|
paddingInline: 'calc({spacing.xs} * 1.5)',
|
||||||
|
fontSize: 'lg'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
...Object.fromEntries(
|
||||||
|
colorVariants.map((color) => [
|
||||||
|
color,
|
||||||
|
Object.fromEntries(
|
||||||
|
selectSlots.map((slot) => {
|
||||||
|
switch (slot) {
|
||||||
|
case 'trigger':
|
||||||
|
// Trigger is outside the content, so it needs to be set independently
|
||||||
|
return [
|
||||||
|
slot,
|
||||||
|
{
|
||||||
|
backgroundColor: `${color}.2`,
|
||||||
|
color: `${color}.12`,
|
||||||
|
borderColor: `${color}.6`,
|
||||||
|
_placeholder: {
|
||||||
|
color: `${color}.11`
|
||||||
|
},
|
||||||
|
_hover: {
|
||||||
|
backgroundColor: `${color}.3`
|
||||||
|
},
|
||||||
|
_active: {
|
||||||
|
backgroundColor: `${color}.4`
|
||||||
|
},
|
||||||
|
|
||||||
|
_focus: {
|
||||||
|
backgroundColor: `${color}.3`,
|
||||||
|
borderColor: `${color}.7`
|
||||||
|
},
|
||||||
|
_disabled: {
|
||||||
|
cursor: 'not-allowed',
|
||||||
|
backgroundColor: `color-mix(in srgb, {colors.${color}.2} 40%, transparent)`,
|
||||||
|
color: `color-mix(in srgb, {colors.${color}.11} 40%, transparent)`,
|
||||||
|
_placeholder: {
|
||||||
|
color: `color-mix(in srgb, {colors.${color}.11} 40%, transparent)`
|
||||||
|
},
|
||||||
|
_hover: {
|
||||||
|
backgroundColor: `color-mix(in srgb, {colors.${color}.2} 40%, transparent)`,
|
||||||
|
color: `color-mix(in srgb, {colors.${color}.11} 40%, transparent)`
|
||||||
|
},
|
||||||
|
_active: {
|
||||||
|
backgroundColor: `color-mix(in srgb, {colors.${color}.2} 40%, transparent)`,
|
||||||
|
color: `color-mix(in srgb, {colors.${color}.11} 40%, transparent)`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as SystemStyleObject
|
||||||
|
]
|
||||||
|
case 'content':
|
||||||
|
// Items, Group, ScrollButtons are inside the content, so we can set them together
|
||||||
|
return [
|
||||||
|
slot,
|
||||||
|
{
|
||||||
|
'--select-background-color': `{colors.${color}.2}`,
|
||||||
|
'--select-background-color-hover': `{colors.${color}.4}`,
|
||||||
|
'--select-background-color-active': `{colors.${color}.5}`,
|
||||||
|
'--select-background-color-disabled': `color-mix(in srgb, {colors.${color}.2} 40%, transparent)`,
|
||||||
|
'--select-border-color': `{colors.${color}.6}`,
|
||||||
|
'--select-color-subtle': `{colors.${color}.11}`,
|
||||||
|
'--select-color': `{colors.${color}.12}`,
|
||||||
|
'--select-color-disabled': `color-mix(in srgb, {colors.${color}.11} 40%, transparent)`
|
||||||
|
} as SystemStyleObject
|
||||||
|
]
|
||||||
|
default:
|
||||||
|
return [slot, {}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
])
|
||||||
|
)
|
||||||
|
},
|
||||||
|
fullWidth: {
|
||||||
|
true: {
|
||||||
|
trigger: {
|
||||||
|
width: '100%'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
color: 'neutral',
|
||||||
|
size: 'medium'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default selectRecipe
|
||||||
Reference in New Issue
Block a user