Skip to content

Instantly share code, notes, and snippets.

@well1791
Last active April 12, 2022 17:33
Show Gist options
  • Save well1791/472c007cd5eca29bc59e7f9212c5552b to your computer and use it in GitHub Desktop.
Save well1791/472c007cd5eca29bc59e7f9212c5552b to your computer and use it in GitHub Desktop.
Stitches with a tailwindcss flavor
import type { BgGrdDir } from './types'
export const bgGrdDir: { [k in BgGrdDir]: string } = {
t: 'top',
tr: 'top right',
r: 'right',
br: 'bottom right',
b: 'bottom',
bl: 'bottom left',
l: 'left',
tl: 'top left',
}
export const bp = {
xs: '320px',
sm: '480px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px',
}
export const remSize = {
'0_5': '0.125rem',
1: '0.25rem', // 4px
'1_5': '0.375rem',
2: '0.5rem', // 8px
'2_5': '0.625rem',
3: '0.75rem',
'3_5': '0.875rem',
4: '1rem', // 16px
5: '1.25rem',
6: '1.5rem',
7: '1.75rem',
8: '2rem', // 32px
9: '2.25rem',
10: '2.5rem',
11: '2.75rem',
12: '3rem',
14: '3.5rem',
16: '4rem', // 64px
18: '4.5rem',
20: '5rem',
24: '6rem',
28: '7rem',
32: '8rem',
36: '9rem',
40: '10rem',
44: '11rem',
48: '12rem',
52: '13rem',
}
export * from './theme'
export * from './types'
export * from './utils'
export * from './constants'
export * from './useCurrentTheme'
export * from './ThemeProvider'
import { createStitches } from '@stitches/core'
import type Stitches from '@stitches/core'
import * as rc from '@radix-ui/colors'
import type * as Type from './types'
import {
isBgLinGrd,
isFlexStartEnd,
isFlexSpace,
flatVal,
percent,
} from './utils'
import { bgGrdDir, bp, remSize } from './constants'
export const {
css,
globalCss,
keyframes,
getCssText,
theme,
createTheme,
config,
} = createStitches({
theme: {
borderStyles: {},
borderWidths: {
...remSize,
},
colors: {
...rc.whiteA,
...rc.blackA,
...rc.slate,
...rc.slateA,
...rc.sky,
...rc.skyA,
...rc.yellow,
...rc.yellowA,
...rc.red,
...rc.redA,
...rc.purple,
...rc.purpleA,
...rc.mint,
...rc.mintA,
...rc.amber,
...rc.amberA,
text: '$slate12',
headerBorderClr: '$colors$red10',
},
fonts: {
quattro: 'iA Quattro',
},
fontSizes: {
// xs: remSize[3],
// sm: remSize['3_5'],
// base: remSize[4],
// lg: remSize['4_5'],
// xl: remSize[5],
// '2xl': remSize[6],
xs: 'clamp(0.68rem, calc(0.58rem + 0.12vw), 0.61rem)',
sm: 'clamp(0.73rem, calc(0.69rem + 0.20vw), 0.85rem)',
base: 'clamp(0.88rem, calc(0.81rem + 0.31vw), 1.06rem)',
lg: 'clamp(1.05rem, calc(0.96rem + 0.46vw), 1.33rem)',
xl: 'clamp(1.26rem, calc(1.13rem + 0.67vw), 1.66rem)',
'2xl': 'clamp(1.51rem, calc(1.32rem + 0.94vw), 2.08rem)',
'3xl': 'clamp(1.81rem, calc(1.55rem + 1.30vw), 2.59rem)',
'4xl': 'clamp(2.18rem, calc(1.82rem + 1.78vw), 3.24rem)',
},
fontWeights: {
thin: '100',
extralight: '200',
light: '300',
normal: '400',
medium: '500',
semibold: '600',
bold: '700',
extrabold: '800',
black: '900',
},
letterSpacings: {},
lineHeights: {
none: 1,
tight: 1.25,
snug: 1.375,
normal: 1.5,
relaxed: 1.625,
loose: 2,
},
radii: {
full: '9999px',
sm: remSize[1],
md: remSize['1_5'],
lg: remSize[2],
xl: remSize[3],
'2xl': remSize[4],
},
shadows: {
xs: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
sm: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
'2xl': '0 25px 50px -12px rgb(0 0 0 / 0.25)',
inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)',
none: '0 0 #0000',
},
sizes: {
...remSize,
...bp,
prose: '65ch',
'1of2': percent(1, 2),
'1of3': percent(1, 3),
'2of3': percent(2, 3),
'1of4': percent(1, 4),
'2of4': percent(2, 4),
'3of4': percent(3, 4),
'1of5': percent(1, 5),
'2of5': percent(2, 5),
'3of5': percent(3, 5),
'4of5': percent(4, 5),
'1of6': percent(1, 6),
'2of6': percent(2, 6),
'3of6': percent(3, 6),
'4of6': percent(4, 6),
'5of6': percent(5, 6),
'1of12': percent(1, 12),
'2of12': percent(2, 12),
'3of12': percent(3, 12),
'4of12': percent(4, 12),
'5of12': percent(5, 12),
'6of12': percent(6, 12),
'7of12': percent(7, 12),
'8of12': percent(8, 12),
'9of12': percent(9, 12),
'10of12': percent(10, 12),
'11of12': percent(11, 12),
full: '100%',
hScreen: '100vh',
wScreen: '100vw',
min: 'min-content',
max: 'max-content',
fit: 'fit-content',
},
space: {
...remSize,
},
transitions: {},
zIndices: {
1: '100',
2: '200',
3: '300',
4: '400',
max: '999',
},
},
media: {
xs: `(min-width: ${bp.xs})`,
sm: `(min-width: ${bp.sm})`,
md: `(min-width: ${bp.md})`,
lg: `(min-width: ${bp.lg})`,
xl: `(min-width: ${bp.xl})`,
'2xl': `(min-width: ${bp['2xl']})`,
nomotion: '(prefers-reduced-motion: no-preference)',
motion: '(prefers-reduced-motion)',
hover: '(any-hover: hover)',
dark: '(prefers-color-scheme: dark)',
light: '(prefers-color-scheme: light)',
touchDev: '(hover: none) and (pointer: coarse)',
},
utils: {
// flexbox
flexWrap: (value: Type.FlexWrap | Stitches.PropertyValue<'flexWrap'>) => ({
flexWrap:
{
w: 'wrap',
'w-r': 'wrap-reverse',
nw: 'nowrap',
}[value as Type.FlexWrap] ?? value,
}),
flexDir: (
value: Type.FlexDir | Stitches.PropertyValue<'flexDirection'>
) => ({
flexDirection:
{
'row-r': 'row-reverse',
col: 'column',
'col-r': 'column-reverse',
}[value as Type.FlexDir] ?? value,
}),
items: (value: Type.FlexAlign | 'baseline' | 'stretch') => ({
alignItems: isFlexStartEnd(value) ? `flex-${value}` : value,
}),
content: (value: Type.FlexAlign | keyof typeof Type.FlexSpace) => ({
justifyContent:
[
{ result: `flex-${value}`, isTrue: isFlexStartEnd(value) },
{ result: `space-${value}`, isTrue: isFlexSpace(value) },
].find(({ isTrue }) => isTrue)?.result ?? value,
}),
// position
insetT: (value: Stitches.PropertyValue<'insetBlockStart'>) =>
((qry) => ({
[`@supports ${qry}`]: { insetBlockStart: value },
[`@supports not ${qry}`]: { top: value },
}))('(inset-block-start: 1rem)'),
insetB: (value: Stitches.PropertyValue<'insetBlockEnd'>) =>
((qry) => ({
[`@supports ${qry}`]: { insetBlockEnd: value },
[`@supports not ${qry}`]: { bottom: value },
}))('(inset-block-end: 1rem)'),
insetL: (value: Stitches.PropertyValue<'insetInlineStart'>) =>
((qry) => ({
[`@supports ${qry}`]: { insetInlineStart: value },
[`@supports not ${qry}`]: { left: value },
}))('(inset-inline-start: 1rem)'),
insetR: (value: Stitches.PropertyValue<'insetInlineEnd'>) =>
((qry) => ({
[`@supports ${qry}`]: { insetInlineEnd: value },
[`@supports not ${qry}`]: { right: value },
}))('(inset-inline-end: 1rem)'),
insetX: (value: Stitches.PropertyValue<'insetBlock'>) =>
((qry) => ({
[`@supports ${qry}`]: {
insetInlineStart: value,
insetInlineEnd: value,
},
[`@supports not ${qry}`]: {
left: value,
right: value,
},
}))('((inset-inline-start: 1rem) and (inset-inline-end: 1rem))'),
insetY: (value: Stitches.PropertyValue<'insetInline'>) =>
((qry) => ({
[`@supports ${qry}`]: {
insetBlockStart: value,
insetBlockEnd: value,
},
[`@supports not ${qry}`]: {
top: value,
bottom: value,
},
}))('((inset-block-start: 1rem) and (inset-block-end: 1rem))'),
// height
h: (value: Stitches.PropertyValue<'blockSize'>) =>
((qry) => ({
[`@supports ${qry}`]: { blockSize: value },
[`@supports not ${qry}`]: { height: value },
}))('(block-size: 1rem)'),
maxH: (value: Stitches.PropertyValue<'maxBlockSize'>) =>
((qry) => ({
[`@supports ${qry}`]: { maxBlockSize: value },
[`@supports not ${qry}`]: { maxHeight: value },
}))('(max-block-size: 1rem)'),
minH: (value: Stitches.PropertyValue<'minBlockSize'>) =>
((qry) => ({
[`@supports ${qry}`]: { minBlockSize: value },
[`@supports not ${qry}`]: { minHeight: value },
}))('(min-block-size: 1rem)'),
// width
w: (value: Stitches.PropertyValue<'inlineSize'>) =>
((qry) => ({
[`@supports ${qry}`]: { inlineSize: value },
[`@supports not ${qry}`]: { width: value },
}))('(inline-size: 1rem)'),
maxW: (value: Stitches.PropertyValue<'maxInlineSize'>) =>
((qry) => ({
[`@supports ${qry}`]: { maxInlineSize: value },
[`@supports not ${qry}`]: { maxWidth: value },
}))('(max-inline-size: 1rem)'),
minW: (value: Stitches.PropertyValue<'minInlineSize'>) =>
((qry) => ({
[`@supports ${qry}`]: { minInlineSize: value },
[`@supports not ${qry}`]: { minWidth: value },
}))('(min-inline-size: 1rem)'),
// both width and height
size: (value: Stitches.PropertyValue<'inlineSize'>) =>
((qry) => ({
[`@supports ${qry}`]: {
inlineSize: value,
blockSize: value,
},
[`@supports not ${qry}`]: {
width: value,
height: value,
},
}))('((inline-size: 1rem) and (block-size: 1rem))'),
// padding
p: (value: Stitches.PropertyValue<'padding'>) => ({
padding: value,
}),
pt: (value: Stitches.PropertyValue<'paddingBlockStart'>) =>
((qry) => ({
[`@supports ${qry}`]: { paddingBlockStart: value },
[`@supports not ${qry}`]: { paddingTop: value },
}))('(padding-block-start: 1rem)'),
pr: (value: Stitches.PropertyValue<'paddingInlineEnd'>) =>
((qry) => ({
[`@supports ${qry}`]: { paddingInlineEnd: value },
[`@supports not ${qry}`]: { paddingRight: value },
}))('(padding-inline-end: 1rem)'),
pb: (value: Stitches.PropertyValue<'paddingBlockEnd'>) =>
((qry) => ({
[`@supports ${qry}`]: { paddingBlockEnd: value },
[`@supports not ${qry}`]: { paddingBottom: value },
}))('(padding-block-end: 1rem)'),
pl: (value: Stitches.PropertyValue<'paddingInlineStart'>) =>
((qry) => ({
[`@supports ${qry}`]: { paddingInlineStart: value },
[`@supports not ${qry}`]: { paddingLeft: value },
}))('(padding-inline-start: 1rem)'),
px: (value: Stitches.PropertyValue<'paddingInline'>) =>
((qry) => ({
[`@supports ${qry}`]: {
paddingInlineStart: value,
paddingInlineEnd: value,
},
[`@supports not ${qry}`]: {
paddingLeft: value,
paddingRight: value,
},
}))('((padding-inline-start: 1rem) and (padding-inline-end: 1rem))'),
py: (value: Stitches.PropertyValue<'paddingBlock'>) =>
((qry) => ({
[`@supports ${qry}`]: {
paddingBlockStart: value,
paddingBlockEnd: value,
},
[`@supports not ${qry}`]: {
paddingTop: value,
paddingBottom: value,
},
}))('((padding-block-start: 1rem) and (padding-block-end: 1rem))'),
// margin
m: (value: Stitches.PropertyValue<'margin'>) => ({
margin: value,
}),
mt: (value: Stitches.PropertyValue<'marginBlockStart'>) =>
((qry) => ({
[`@supports ${qry}`]: { marginBlockStart: value },
[`@supports not ${qry}`]: { marginTop: value },
}))('(margin-block-start: 1rem)'),
mr: (value: Stitches.PropertyValue<'marginInlineEnd'>) =>
((qry) => ({
[`@supports ${qry}`]: { marginInlineEnd: value },
[`@supports not ${qry}`]: { marginRight: value },
}))('(margin-inline-end: 1rem)'),
mb: (value: Stitches.PropertyValue<'marginBlockEnd'>) =>
((qry) => ({
[`@supports ${qry}`]: { marginBlockEnd: value },
[`@supports not ${qry}`]: { marginBottom: value },
}))('(margin-block-end: 1rem)'),
ml: (value: Stitches.PropertyValue<'marginInlineStart'>) =>
((qry) => ({
[`@supports ${qry}`]: { marginInlineStart: value },
[`@supports not ${qry}`]: { marginLeft: value },
}))('(margin-inline-start: 1rem)'),
mx: (value: Stitches.PropertyValue<'marginInline'>) =>
((qry) => ({
[`@supports ${qry}`]: {
marginInlineStart: value,
marginInlineEnd: value,
},
[`@supports not ${qry}`]: {
marginLeft: value,
marginRight: value,
},
}))('((margin-inline-start: 1rem) and (margin-inline-end: 1rem))'),
my: (value: Stitches.PropertyValue<'marginBlock'>) =>
((qry) => ({
[`@supports ${qry}`]: {
marginBlockStart: value,
marginBlockEnd: value,
},
[`@supports not ${qry}`]: {
marginTop: value,
marginBottom: value,
},
}))('((margin-block-start: 1rem) and (margin-block-end: 1rem))'),
// space
spaceX: (value: Stitches.PropertyValue<'marginInlineStart'>) => ({
'> * + *': ((qry) => ({
[`@supports ${qry}`]: { marginInlineStart: value },
[`@supports not ${qry}`]: { marginLeft: value },
}))('(margin-inline-start: 1rem)'),
}),
spaceY: (value: Stitches.PropertyValue<'marginBlockStart'>) => ({
'> * + *': ((qry) => ({
[`@supports ${qry}`]: { marginBlockStart: value },
[`@supports not ${qry}`]: { marginTop: value },
}))('(margin-block-start: 1rem)'),
}),
// background
bg: (value: Stitches.PropertyValue<'backgroundImage'>) => ({
background: value,
}),
bgImg: (
value:
| Stitches.PropertyValue<'backgroundImage'>
| Stitches.PropertyValue<'backgroundImage'>[]
) => ({
backgroundImage: flatVal(value),
}),
bgLinGrd: (value: Type.BgLinGrd | Type.BgLinGrd[]) => {
const toLinGrd = ([dir, grds]: Type.BgLinGrd) =>
`linear-gradient(to ${bgGrdDir[dir]}, ${flatVal(grds)})`
return {
backgroundImage: isBgLinGrd(value)
? toLinGrd(value)
: flatVal(value.map(toLinGrd)),
}
},
bgClr: (value: Stitches.PropertyValue<'backgroundColor'>) => ({
backgroundColor: value,
}),
// border-radius
rounded: (value: Stitches.PropertyValue<'borderRadius'>) => ({
borderRadius: value,
}),
// border
borderT: (value: Stitches.PropertyValue<'borderBlockStart'>) =>
((qry) => ({
[`@supports ${qry}`]: { borderBlockStart: value },
[`@supports not ${qry}`]: { borderTop: value },
}))('(border-block-start: 1rem)'),
borderB: (value: Stitches.PropertyValue<'borderBlockEnd'>) =>
((qry) => ({
[`@supports ${qry}`]: { borderBlockEnd: value },
[`@supports not ${qry}`]: { borderBottom: value },
}))('(border-block-end: 1rem)'),
borderL: (value: Stitches.PropertyValue<'borderInlineStart'>) =>
((qry) => ({
[`@supports ${qry}`]: { borderInlineStart: value },
[`@supports not ${qry}`]: { borderLeft: value },
}))('(border-inline-start: 1rem)'),
borderR: (value: Stitches.PropertyValue<'borderInlineEnd'>) =>
((qry) => ({
[`@supports ${qry}`]: { borderInlineEnd: value },
[`@supports not ${qry}`]: { borderRight: value },
}))('(border-inline-end: 1rem)'),
borderX: (value: Stitches.PropertyValue<'borderInlineEnd'>) =>
((qry) => ({
[`@supports ${qry}`]: {
borderInlineStart: value,
borderInlineEnd: value,
},
[`@supports not ${qry}`]: {
borderLeft: value,
borderRight: value,
},
}))('((border-inline-start: 1rem) and (border-inline-end: 1rem))'),
borderY: (value: Stitches.PropertyValue<'borderBlock'>) =>
((qry) => ({
[`@supports ${qry}`]: {
borderBlockStart: value,
borderBlockEnd: value,
},
[`@supports not ${qry}`]: {
borderTop: value,
borderBottom: value,
},
}))('((border-block-start: 1rem) and (border-block-end: 1rem))'),
// font
text: (value: Stitches.PropertyValue<'fontSize'>) => ({ fontSize: value }),
textOverflow: (
value: 'truncate' | Stitches.PropertyValue<'textOverflow'>
) =>
value === 'truncate'
? {
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}
: value,
// screen-reader
srOnly: (value: boolean) =>
value
? {
position: 'absolute',
width: 1,
height: 1,
padding: 0,
margin: -1,
overflow: 'hidden',
clip: 'rect(0, 0, 0, 0)',
whiteSpace: 'nowrap',
borderWidth: 0,
}
: {
position: 'static',
width: 'auto',
height: 'auto',
padding: 0,
margin: 0,
overflow: 'visible',
clip: 'auto',
whiteSpace: 'normal',
},
},
})
export type CSS = Stitches.CSS<typeof config>
export const lightTheme = theme
export const darkTheme = createTheme({
colors: {
...rc.slateDark,
...rc.slateDarkA,
...rc.redDark,
...rc.redDarkA,
...rc.purpleDark,
...rc.purpleDarkA,
...rc.mintDark,
...rc.mintDarkA,
...rc.amberDark,
...rc.amberDarkA,
text: '$slate12',
headerBorderClr: '$colors$sky9',
},
})
export const themes: {
[k in Type.ThemeType]: typeof theme | typeof darkTheme
} = {
light: theme,
dark: darkTheme,
}
import * as React from 'react'
import { themes } from './theme'
import useCurrentTheme from './useCurrentTheme'
export type Props = {
children?: React.ReactNode
}
export function ThemeProvider({ children }: Props) {
const [selectedTheme] = useCurrentTheme()
React.useLayoutEffect(() => {
const themeClass = themes[selectedTheme.name]
document.querySelector('body').setAttribute('class', themeClass)
}, [selectedTheme])
return <>{children}</>
}
export default ThemeProvider
import type Stitches from '@stitches/core'
export type ThemeType = 'light' | 'dark'
export type AvThemeType = ThemeType | 'system'
export type BgGrdDir = 't' | 'tr' | 'r' | 'br' | 'b' | 'bl' | 'l' | 'tl'
export type BgLinGrd = [
BgGrdDir,
string | Stitches.PropertyValue<'backgroundColor'>
]
export enum FlexStartEnd {
start,
end,
}
export type FlexAlign = keyof typeof FlexStartEnd | 'center'
export enum FlexSpace {
between,
around,
evenly,
}
export type FlexDir = 'row' | 'row-r' | 'col' | 'col-r'
export type FlexWrap = 'w' | 'w-r' | 'nw'
import * as React from 'react'
import { useAtom } from 'jotai'
import { useMediaQuery } from '@mantine/hooks'
import { Just } from 'purify-ts/Maybe'
import { currentThemeAtom, storedThemeAtom } from 'src/shared/store'
import { config, isValidTheme, isValidAvTheme } from 'src/shared/theme'
import type { ThemeType, AvThemeType } from 'src/shared/theme'
type ReturnType = [
{ isSystem: boolean; name: ThemeType },
(v: AvThemeType) => void
]
export function useCurrentTheme(): ReturnType {
const systemTheme: ThemeType = useMediaQuery(config.media.dark)
? 'dark'
: 'light'
const [storedTheme, setStoredTheme] = useAtom(storedThemeAtom)
const [currentTheme, setCurrentTheme] = useAtom(currentThemeAtom)
const updateCurrentTheme = React.useCallback(
(theme: AvThemeType) => {
if (!isValidAvTheme(theme)) return
const newTheme = Just(isValidTheme(theme) ? theme : systemTheme)
if (!currentTheme.equals(newTheme)) {
setCurrentTheme(newTheme)
}
},
[currentTheme, systemTheme, setCurrentTheme]
)
const handleCurrentThemeUpdate = (theme: AvThemeType): void => {
setStoredTheme(theme)
updateCurrentTheme(theme)
}
React.useEffect(() => {
updateCurrentTheme(storedTheme)
}, [storedTheme, updateCurrentTheme])
return [
{
name: currentTheme.orDefault(systemTheme),
isSystem: storedTheme === 'system',
},
handleCurrentThemeUpdate,
]
}
export default useCurrentTheme
import { themes } from './theme'
import { FlexStartEnd, FlexSpace } from './types'
import type { ThemeType, AvThemeType, BgLinGrd } from './types'
export const isValidTheme = (value?: string): value is ThemeType =>
Boolean(themes[value])
export const isValidAvTheme = (value?: string): value is AvThemeType =>
value === 'system' || Boolean(themes[value])
export const isBgLinGrd = (v: BgLinGrd | BgLinGrd[]): v is BgLinGrd => {
const [head] = v
return !Array.isArray(head)
}
export const isFlexStartEnd = <T>(v: T): boolean => {
return Object.values(FlexStartEnd).includes(String(v))
}
export const isFlexSpace = <T>(v: T): boolean => {
return Object.values(FlexSpace).includes(String(v))
}
export const flatVal = <T>(v: T, sep = ', '): string => {
return Array.isArray(v) ? v.join(sep) : String(v)
}
export const percent = (numerator: number, denominator: number): string => {
return `${((numerator * 100) / denominator).toFixed(6)}%`
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment