chore: initial commit

I just realized i was using this without commiting it, so this is a very big commit
This commit is contained in:
2025-08-31 19:43:26 -06:00
commit 88be319334
19 changed files with 1301 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
import type { Recursive, Token } from '@pandacss/types'
import * as radixColors from '@radix-ui/colors'
import { type RequireDarkForeground, requireDarkForeground } from '@/types.js'
const generateColors = () => {
const colors: Recursive<Token<string>> = {}
for (const [key, value] of Object.entries(radixColors)) {
const colorName = /([a-z]*)/.exec(key)?.[1] as keyof typeof radixColors
// if (colorName === 'default' || !colorName) continue
const newColor: Recursive<Token<string>> = {}
const isAlpha = key.endsWith('A')
const isP3 = key.includes('P3')
const isDark = key.includes('Dark')
const newColorName = `${colorName}${isDark ? 'Dark' : ''}${isP3 ? 'P3' : ''}${isAlpha ? 'Alpha' : ''}`
newColor.foreground = {
value: requireDarkForeground.includes(colorName as RequireDarkForeground) ? '#000' : '#fff'
}
for (const [subKey, subValue] of Object.entries(value)) {
const colorNumber = /.*?([0-9]{1,2})$/.exec(subKey)?.[1]
newColor[`${colorNumber}`] = { value: subValue }
if (colorNumber === '9') {
newColor.DEFAULT = { value: subValue }
}
}
colors[newColorName] = newColor
}
return colors
}
export default generateColors
export type GeneratedColorsReturn = ReturnType<typeof generateColors>

View File

@@ -0,0 +1,29 @@
import type { Recursive, Token } from '@pandacss/types'
import type { ColorVariation, NeutralColor } from '@/types.js'
const generateNeutralColor = (color: NeutralColor, colorVariation: ColorVariation) => {
const colorName = `${color}${colorVariation.dark ? 'Dark' : ''}${colorVariation.p3 ? 'P3' : ''}${colorVariation.alpha ? 'Alpha' : ''}`
const neutralColor: Recursive<Token<string>> = {
DEFAULT: { value: `{colors.${colorName}.DEFAULT}` },
foreground: { value: `{colors.${colorName}.foreground}` },
'1': { value: `{colors.${colorName}.1}` },
'2': { value: `{colors.${colorName}.2}` },
'3': { value: `{colors.${colorName}.3}` },
'4': { value: `{colors.${colorName}.4}` },
'5': { value: `{colors.${colorName}.5}` },
'6': { value: `{colors.${colorName}.6}` },
'7': { value: `{colors.${colorName}.7}` },
'8': { value: `{colors.${colorName}.8}` },
'9': { value: `{colors.${colorName}.9}` },
'10': { value: `{colors.${colorName}.10}` },
'11': { value: `{colors.${colorName}.11}` },
'12': { value: `{colors.${colorName}.12}` }
}
return neutralColor
}
export default generateNeutralColor
export type GenerateNeutralColorReturn = typeof generateNeutralColor

View File

@@ -0,0 +1,37 @@
import type { Recursive, Token } from '@pandacss/types'
import type { BrandColor, ColorVariation } from '@/types.js'
interface SemanticColors {
[key: string]: BrandColor
}
const generateSemanticColors = (
semanticColors: SemanticColors,
variation: ColorVariation
): Recursive<Token<string>> => {
const colors: Recursive<Token<string>> = {}
for (const [key, value] of Object.entries(semanticColors)) {
const colorName = `${value}${variation.dark ? 'Dark' : ''}${
variation.p3 ? 'P3' : ''
}${variation.alpha ? 'Alpha' : ''}`
colors[key] = {
DEFAULT: { value: `{colors.${colorName}.DEFAULT}` },
foreground: { value: `{colors.${colorName}.foreground}` },
'1': { value: `{colors.${colorName}.1}` },
'2': { value: `{colors.${colorName}.2}` },
'3': { value: `{colors.${colorName}.3}` },
'4': { value: `{colors.${colorName}.4}` },
'5': { value: `{colors.${colorName}.5}` },
'6': { value: `{colors.${colorName}.6}` },
'7': { value: `{colors.${colorName}.7}` },
'8': { value: `{colors.${colorName}.8}` },
'9': { value: `{colors.${colorName}.9}` },
'10': { value: `{colors.${colorName}.10}` },
'11': { value: `{colors.${colorName}.11}` },
'12': { value: `{colors.${colorName}.12}` }
}
}
return colors
}
export default generateSemanticColors

51
src/index.ts Normal file
View File

@@ -0,0 +1,51 @@
import { definePreset } from '@pandacss/dev'
import generateColors from '@/colors/generateColors.js'
import generateNeutralColor from '@/colors/generateNeutralColor.js'
import buttonRecipe from '@/recipes/button.js'
import { detailsRecipe } from '@/recipes/details.js'
import inputRecipe from '@/recipes/input.js'
import type { BrandColor, ColorVariation, NeutralColor } from '@/types.js'
import generateSemanticColors from './colors/generateSemanticColors.js'
export type themeConfig = {
neutral?: NeutralColor
semanticColors?: Record<string, BrandColor>
colorVariation?: ColorVariation
}
const defaultConfig: Required<themeConfig> = {
neutral: 'slate',
colorVariation: { dark: false, p3: false, alpha: false },
semanticColors: { primary: 'teal' }
}
const srJuggernautPandaPreset = (config?: themeConfig) => {
const mergedConfig = { ...defaultConfig, ...config }
const colors = generateColors()
const neutral = generateNeutralColor(mergedConfig.neutral, mergedConfig.colorVariation)
const semanticColors = generateSemanticColors(mergedConfig.semanticColors, mergedConfig.colorVariation)
return definePreset({
name: 'srjuggernaut-panda-preset',
theme: {
recipes: {
button: buttonRecipe({ semanticColors: Object.keys(semanticColors) }),
details: detailsRecipe({ semanticColors: Object.keys(semanticColors) }),
input: inputRecipe
},
tokens: {
colors
},
semanticTokens: {
colors: {
neutral: neutral,
...semanticColors
}
}
}
})
}
export default srJuggernautPandaPreset
export { buttonVariants } from '@/recipes/button.js'
export { BrandColor, ColorVariation, NeutralColor } from '@/types.js'

143
src/recipes/button.ts Normal file
View File

@@ -0,0 +1,143 @@
import { defineRecipe } from '@pandacss/dev'
export const buttonVariants = ['solid', 'outline', 'ghost'] as const
interface ButtonRecipeArg {
semanticColors: string[]
}
const buttonRecipe = ({ semanticColors }: ButtonRecipeArg) => {
const colorVariants = ['neutral', ...semanticColors]
return defineRecipe({
className: 'button',
base: {
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
gap: '0.5em',
width: 'fit-content',
borderRadius: '4px',
paddingBlock: '0.25em',
paddingInline: '0.5em',
cursor: 'pointer',
transition: 'all 200ms ease-in-out'
},
variants: {
color: {
...Object.fromEntries(colorVariants.map((color) => [color, {}]))
},
size: {
small: {
fontSize: '0.875em'
},
medium: {
fontSize: '1em'
},
large: {
fontSize: '1.125em'
}
},
variant: {
...Object.fromEntries(buttonVariants.map((variant) => [variant, {}]))
}
},
compoundVariants: colorVariants.flatMap((color) =>
buttonVariants.map((variant) => {
switch (variant) {
case 'outline':
return {
color,
variant,
css: {
border: '1px solid',
borderColor: `${color}.9`,
backgroundColor: 'transparent',
color: `${color}.9`,
_hover: {
backgroundColor: `${color}.10`,
color: `${color}.foreground`
},
_active: {
backgroundColor: `color-mix(in srgb, {colors.${color}.9} 80%, {colors.${color}.1})`,
color: `${color}.foreground`
},
_disabled: {
backgroundColor: `color-mix(in srgb, {colors.${color}.9} 10%, transparent)`,
color: `color-mix(in srgb, {colors.${color}.foreground} 40%, transparent)`,
borderColor: `color-mix(in srgb, {colors.${color}.9} 10%, transparent)`,
cursor: 'not-allowed',
pointerEvents: 'none',
_hover: {
backgroundColor: `color-mix(in srgb, {colors.${color}.9} 10%, transparent)`,
color: `${color}.4/50`
}
}
}
}
case 'ghost':
return {
color,
variant,
css: {
border: 'none',
backgroundColor: 'transparent',
color: `${color}.9`,
_hover: {
backgroundColor: `${color}.10`,
color: `${color}.foreground`
},
_active: {
backgroundColor: `color-mix(in srgb, {colors.${color}.9} 80%, {colors.${color}.1})`,
color: `${color}.foreground`
},
_disabled: {
backgroundColor: `color-mix(in srgb, {colors.${color}.9} 10%, transparent)`,
color: `color-mix(in srgb, {colors.${color}.foreground} 40%, transparent)`,
cursor: 'not-allowed',
pointerEvents: 'none',
_hover: {
backgroundColor: `color-mix(in srgb, {colors.${color}.9} 10%, transparent)`
}
}
}
}
default:
return {
color,
variant,
css: {
border: 'none',
backgroundColor: `${color}.9`,
color: `${color}.foreground`,
_hover: {
backgroundColor: `${color}.10`,
color: `${color}.foreground`
},
_active: {
backgroundColor: `color-mix(in srgb, {colors.${color}.9} 80%, {colors.${color}.1})`,
color: `${color}.foreground`
},
_disabled: {
backgroundColor: `color-mix(in srgb, {colors.${color}.9} 40%, transparent)`,
color: `color-mix(in srgb, {colors.${color}.foreground} 40%, transparent)`,
cursor: 'not-allowed',
pointerEvents: 'none',
_hover: {
backgroundColor: `color-mix(in srgb, {colors.${color}.9} 40%, transparent)`,
color: `color-mix(in srgb, {colors.${color}.foreground} 40%, transparent)`
}
}
}
}
}
})
),
defaultVariants: {
color: 'neutral',
size: 'medium',
variant: 'solid'
}
})
}
export default buttonRecipe

59
src/recipes/details.ts Normal file
View File

@@ -0,0 +1,59 @@
import { defineSlotRecipe } from '@pandacss/dev'
export interface DetailsRecipeArg {
semanticColors: string[]
}
export const detailsRecipe = ({ semanticColors }: DetailsRecipeArg) => {
const colorVariants = ['neutral', ...semanticColors]
const recipe = defineSlotRecipe({
className: 'details',
slots: ['summary', 'details'],
base: {
details: {
display: 'block',
overflow: 'hidden',
borderRadius: '0.25em',
transition: 'all 300ms ease-in-out'
},
summary: {
display: 'block',
overflow: 'hidden',
borderRadius: '0.25em',
padding: '0.25em',
cursor: 'pointer',
transition: 'all 300ms ease-in-out'
}
},
variants: {
color: {
...Object.fromEntries(
colorVariants.map((color) => [
color as keyof typeof colorVariants,
{
summary: {
backgroundColor: `{colors.${color}.3}`,
color: `{colors.${color}.12}`,
_hover: {
backgroundColor: `{colors.${color}.4}`
},
'[open] &': {
backgroundColor: `{colors.${color}.4}`
}
},
details: {
backgroundColor: `{colors.${color}.2}`,
color: `{colors.${color}.12}`
}
}
])
)
}
},
defaultVariants: {
color: 'neutral'
}
})
return recipe
}

36
src/recipes/input.ts Normal file
View File

@@ -0,0 +1,36 @@
import { defineRecipe } from '@pandacss/dev'
export const inputRecipe = defineRecipe({
className: 'input',
base: {
display: 'inline-flex',
borderRadius: '4px',
cursor: 'pointer',
paddingBlock: '0.25em',
paddingInline: '0.5em',
border: '1px solid',
borderColor: 'neutral.7',
backgroundColor: 'neutral.3',
color: 'neutral.12',
outline: 'none',
transition: 'all 200ms ease-in-out',
_hover: {
backgroundColor: 'neutral.4'
},
_disabled: {
backgroundColor: 'neutral.3/50',
color: 'neutral.11/50',
borderColor: 'neutral.6/50',
cursor: 'not-allowed',
_hover: {
backgroundColor: 'neutral.6/50',
color: 'neutral.11/50'
}
},
_active: {
backgroundColor: 'neutral.8'
}
}
})
export default inputRecipe

43
src/types.ts Normal file
View File

@@ -0,0 +1,43 @@
export const brandColor = [
'bronze',
'gold',
'brown',
'orange',
'tomato',
'red',
'ruby',
'crimson',
'pink',
'plum',
'purple',
'violet',
'iris',
'indigo',
'blue',
'cyan',
'teal',
'jade',
'green',
'grass',
'sky',
'mint',
'lime',
'yellow',
'amber'
] as const
export type BrandColor = (typeof brandColor)[number]
export const neutralColor = ['gray', 'mauve', 'slate', 'sage', 'olive', 'sand'] as const
export type NeutralColor = (typeof neutralColor)[number]
export type ColorVariation = {
dark?: boolean
p3?: boolean
alpha?: boolean
}
export const requireDarkForeground = ['sky', 'mint', 'lime', 'yellow', 'amber'] as const
export type RequireDarkForeground = (typeof requireDarkForeground)[number]