diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index 35fec7c..dd8f539 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -3,6 +3,7 @@ on: pull_request: branches: [ dev ] paths: + - 'app/**' - 'package.json' - 'tsconfig.json' - 'bun.lockb' diff --git a/app/fragments/window/sidebar/index.tsx b/app/fragments/window/sidebar/index.tsx new file mode 100644 index 0000000..30c440b --- /dev/null +++ b/app/fragments/window/sidebar/index.tsx @@ -0,0 +1,5 @@ +import { sidebar } from './styles.css.ts' + +export function Sidebar() { + return
+} diff --git a/app/fragments/window/sidebar/styles.css.ts b/app/fragments/window/sidebar/styles.css.ts new file mode 100644 index 0000000..7de4392 --- /dev/null +++ b/app/fragments/window/sidebar/styles.css.ts @@ -0,0 +1,6 @@ +import { style } from '@vanilla-extract/css' + +export const sidebar = style({ + width: '4rem', + position: 'relative', +}) diff --git a/app/root.tsx b/app/root.tsx index f0aa73e..a1fea4c 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -1,8 +1,17 @@ -import '&theme/preflight.css.ts' -import '&theme/provider.tsx' +import '&theme/preflight.css.ts' +import '&theme/provider.tsx' -import { Button } from '&ui/button' +import { Sidebar } from '&fragments/window/sidebar' +import { Box } from '&ui/layout' +import { Row } from '&ui/layout' export function Root() { - return + return ( + + + + {/* TODO */} + + + ) } diff --git a/app/shared/nanolib.ts b/app/shared/nanolib.ts new file mode 100644 index 0000000..78a8b7a --- /dev/null +++ b/app/shared/nanolib.ts @@ -0,0 +1,31 @@ +const { entries } = Object + +export type Leaves = T extends object + ? { [K in keyof T]: `${Exclude}${Leaves extends never ? '' : `.${Leaves}`}` }[keyof T] + : never + +type PathAccess = P extends `${infer K}.${infer R}` + ? K extends keyof T + ? PathAccess + : never + : P extends keyof T + ? T[P] + : never + +export function leaves(target: T, prefix: string[] = []): Leaves[] { + const list = [] + + for (const [key, value] of entries(target)) { + if (typeof value === 'object') { + list.push(...leaves(value, [...prefix, key])) + } else { + list.push([...prefix, key].join('.')) + } + } + + return list as Leaves[] +} + +export function pathAccess>(target: T, path: P) { + return path.split('.').reduce((a, c) => Reflect.get(a as object, c), target) as PathAccess +} diff --git a/app/theme/base.css.ts b/app/theme/base.css.ts index 7afc3f9..a999e68 100644 --- a/app/theme/base.css.ts +++ b/app/theme/base.css.ts @@ -1,7 +1,6 @@ -import { clashDisplay } from '&theme/fonts.css.ts' -import { manrope } from '&theme/fonts.css.ts' -import { geistMono } from '&theme/fonts.css.ts' - +import { clashDisplay } from './fonts.css.ts' +import { manrope } from './fonts.css.ts' +import { geistMono } from './fonts.css.ts' import type { ThemePart } from './shared.ts' import type { vars } from './vars.css.ts' diff --git a/app/theme/dark.css.ts b/app/theme/dark.css.ts index 91202b6..d0490d2 100644 --- a/app/theme/dark.css.ts +++ b/app/theme/dark.css.ts @@ -17,6 +17,7 @@ export const theme = createTheme(vars, { readable: '#18181b', }, gray: { + surface: '#212124', input: '#3b3a38', }, }, diff --git a/app/theme/light.css.ts b/app/theme/light.css.ts index eb3e75c..fb23810 100644 --- a/app/theme/light.css.ts +++ b/app/theme/light.css.ts @@ -17,6 +17,7 @@ export const theme = createTheme(vars, { readable: '#f9f1e9', }, gray: { + surface: '#ece3d9', input: '#d2cfc9', }, }, diff --git a/app/theme/preflight.css.ts b/app/theme/preflight.css.ts index e360a82..30e1ca2 100644 --- a/app/theme/preflight.css.ts +++ b/app/theme/preflight.css.ts @@ -24,8 +24,8 @@ globalStyle('*', { globalStyle('body', { '@layer': { [preflight]: { - minHeight: '100vh', - backgroundColor: vars.color.background, + height: '100vh', + backgroundColor: vars.color.gray.surface, fontFamily: vars.font.sans, fontSize: '87.5%', transition: `all ${vars.transition.duration.l} ${vars.transition.timing}`, @@ -55,3 +55,8 @@ globalStyle('img', { }, }, }) + +globalStyle('#root', { + height: '100vh', + display: 'flex', +}) diff --git a/app/theme/vars.css.ts b/app/theme/vars.css.ts index 7ac5ced..b8df049 100644 --- a/app/theme/vars.css.ts +++ b/app/theme/vars.css.ts @@ -1,4 +1,8 @@ -import { createThemeContract } from '@vanilla-extract/css' +import type { style } from '@vanilla-extract/css' +import { createThemeContract } from '@vanilla-extract/css' + +import { leaves } from '&shared/nanolib.ts' +import { pathAccess } from '&shared/nanolib.ts' const typography = { size: null, @@ -20,6 +24,7 @@ export const vars = createThemeContract({ readable: null, }, gray: { + surface: null, input: null, }, }, @@ -73,3 +78,14 @@ export const vars = createThemeContract({ }, }, }) + +export function mapVars(vars: T, fn: (val: keyof T) => Parameters[0]) { + return Object.fromEntries(Object.entries(vars).map(([key, value]) => [key, fn(value)])) as Record< + keyof T, + Parameters[0] + > +} + +export function mapVarsPath(vars: T, fn: (val: string) => Parameters[0]) { + return Object.fromEntries(leaves(vars).map((path) => [path, fn(pathAccess(vars, path) as string)])) +} diff --git a/app/ui/layout/index.tsx b/app/ui/layout/index.tsx new file mode 100644 index 0000000..024e3d9 --- /dev/null +++ b/app/ui/layout/index.tsx @@ -0,0 +1,26 @@ +import { clsx } from 'clsx' + +import type { LayoutProps } from './shared.ts' +import { useStyle } from './shared.ts' +import { box } from './styles.css.ts' +import { col } from './styles.css.ts' +import { row } from './styles.css.ts' + +export function Row(props: LayoutProps) { + const { style, rest } = useStyle(props) + return
+} + +export function Col(props: LayoutProps) { + const { style, rest } = useStyle(props) + return
+} + +export type BoxProps = Omit + +export function Box(props: BoxProps) { + const { style, rest } = useStyle(props) + return
+} + +export type { LayoutProps } diff --git a/app/ui/layout/shared.ts b/app/ui/layout/shared.ts new file mode 100644 index 0000000..9a2248a --- /dev/null +++ b/app/ui/layout/shared.ts @@ -0,0 +1,32 @@ +import type { ComponentProps } from 'react' + +import { clsx } from 'clsx' + +import type { Leaves } from '&shared/nanolib.ts' +import type { vars } from '&theme/vars.css.ts' +import { variants } from '&ui/layout/styles.css.ts' + +export type LayoutProps = ComponentProps<'div'> & { + gap?: keyof typeof vars.space + p?: keyof typeof vars.space + bg?: Leaves + rounded?: keyof typeof vars.rounded + roundedLeft?: keyof typeof vars.rounded + grow?: boolean +} + +export function useStyle(props: LayoutProps) { + const { gap, p, bg, grow, rounded, roundedLeft, ...rest } = props + + return { + style: clsx( + gap && variants.gap[gap], + p && variants.p[p], + bg && variants.bg[bg], + grow && variants.grow.true, + rounded && variants.rounded[rounded], + roundedLeft && variants.roundedLeft[roundedLeft], + ), + rest, + } +} diff --git a/app/ui/layout/styles.css.ts b/app/ui/layout/styles.css.ts new file mode 100644 index 0000000..57de793 --- /dev/null +++ b/app/ui/layout/styles.css.ts @@ -0,0 +1,54 @@ +import { style } from '@vanilla-extract/css' +import { styleVariants } from '@vanilla-extract/css' + +import { mapVars } from '&theme/vars.css.ts' +import { mapVarsPath } from '&theme/vars.css.ts' +import { vars } from '&theme/vars.css.ts' + +const base = style({ + display: 'flex', + transitionProperty: 'all', + transitionDuration: vars.transition.duration.m, + transitionTimingFunction: vars.transition.timing, +}) + +export const variants = { + gap: styleVariants(mapVars(vars.space, (gap) => ({ gap }))), + p: styleVariants(mapVars(vars.space, (padding) => ({ padding }))), + bg: styleVariants(mapVarsPath(vars.color, (backgroundColor) => ({ backgroundColor }))), + rounded: styleVariants(mapVars(vars.rounded, (borderRadius) => ({ borderRadius }))), + roundedLeft: styleVariants( + mapVars(vars.rounded, (radius) => ({ + borderTopLeftRadius: radius, + borderBottomLeftRadius: radius, + })), + ), + grow: styleVariants({ + true: { + flexGrow: 1, + }, + }), +} + +export const row = style([ + base, + { + width: '100%', + flexDirection: 'row', + }, +]) + +export const col = style([ + base, + { + height: '100%', + flexDirection: 'column', + }, +]) + +export const box = style([ + base, + { + flexGrow: 1, + }, +])