diff --git a/README.md b/README.md index 25b0be0..851a6aa 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# yoshiki +# Yoshiki ## Features diff --git a/examples/expo-example/src/app.tsx b/examples/expo-example/src/app.tsx index b293975..bdef548 100644 --- a/examples/expo-example/src/app.tsx +++ b/examples/expo-example/src/app.tsx @@ -12,7 +12,7 @@ const CustomBox = ({ color, ...props }: { color: string } & Stylable) => { const { css } = useYoshiki(); return ( - + Text inside the custom black box. ); diff --git a/examples/next-example/src/pages/index.tsx b/examples/next-example/src/pages/index.tsx index f77ac75..75ff55b 100644 --- a/examples/next-example/src/pages/index.tsx +++ b/examples/next-example/src/pages/index.tsx @@ -36,7 +36,7 @@ export default function Home(props: object) { - +

Get started by editing pages/index.tsx diff --git a/packages/yoshiki/index.ts b/packages/yoshiki/index.ts new file mode 100644 index 0000000..61423ec --- /dev/null +++ b/packages/yoshiki/index.ts @@ -0,0 +1,6 @@ +// +// Copyright (c) Zoe Roux and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. +// + +export * from "./src"; diff --git a/packages/yoshiki/src/index.ts b/packages/yoshiki/src/index.ts index f47dda5..33776ad 100644 --- a/packages/yoshiki/src/index.ts +++ b/packages/yoshiki/src/index.ts @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for details. // -export { type Theme, breakpoints, useTheme, ThemeProvider, defaultTheme } from "./theme"; +export { type Theme, breakpoints, useTheme, ThemeProvider } from "./theme"; export { useYoshiki, diff --git a/packages/yoshiki/src/native/generator.tsx b/packages/yoshiki/src/native/generator.tsx index b3449b5..6b3a371 100644 --- a/packages/yoshiki/src/native/generator.tsx +++ b/packages/yoshiki/src/native/generator.tsx @@ -7,10 +7,12 @@ import { ViewStyle, TextStyle, ImageStyle, useWindowDimensions } from "react-nat import { breakpoints, Theme, useTheme } from "../theme"; import { Breakpoints, YoshikiStyle } from "../type"; import { isBreakpoints } from "../utils"; +import { shorthandsFn } from "./shorthands"; -// TODO: shorhands type EnhancedStyle = { [key in keyof Properties]: YoshikiStyle; +} & { + [key in keyof typeof shorthandsFn]?: Parameters[0]; }; type Properties = ViewStyle | TextStyle | ImageStyle; @@ -24,22 +26,32 @@ const useBreakpoint = (): number => { const propertyMapper = < Property extends number | string | boolean | undefined | Property[] | object, >( - value: Property | Breakpoints | ((theme: Theme) => Property), + key: string, + value: YoshikiStyle, { breakpoint, theme }: { breakpoint: number; theme: Theme }, -): Property | undefined => { +): [string, number | string | boolean | undefined | object][] => { + if (key in shorthandsFn) { + // @ts-ignore `key` is not narrowed to `keyof typeof shorthandsFn` and value is not type safe. + const expanded = shorthandsFn[key as keyof typeof shorthandsFn](value); + return Object.entries(expanded) + .map(([eKey, eValue]) => propertyMapper(eKey, eValue, { breakpoint, theme })) + .flat(); + } + + if (typeof value === "function") { + value = value(theme); + } if (isBreakpoints(value)) { const bpKeys = Object.keys(breakpoints) as Array>; for (let i = breakpoint; i >= 0; i--) { if (bpKeys[i] in value) { - return value[bpKeys[i]]; + const bpVal = value[bpKeys[i]]; + return bpVal ? [[key, bpVal]] : []; } } - return undefined; + return []; } - if (typeof value === "function") { - return value(theme); - } - return value; + return [[key, value]]; }; export const useYoshiki = () => { @@ -53,10 +65,11 @@ export const useYoshiki = () => { ): { style: Style } => { const { style, ...leftOverProps } = leftOvers ?? {}; + // @ts-ignore propertyMapper always returns key that are valid for the current Style const inline: Style = Object.fromEntries( - Object.entries(css) - .map(([key, value]) => [key, propertyMapper(value, { breakpoint, theme })]) - .filter(([, value]) => value !== undefined), + Object.entries(css).flatMap(([key, value]) => + propertyMapper(key, value, { breakpoint, theme }), + ), ); return { diff --git a/packages/yoshiki/src/native/index.ts b/packages/yoshiki/src/native/index.ts index ce91d49..35bd12b 100644 --- a/packages/yoshiki/src/native/index.ts +++ b/packages/yoshiki/src/native/index.ts @@ -3,6 +3,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for details. // -export { type Theme, breakpoints, useTheme, ThemeProvider, defaultTheme } from "../theme"; +export { type Theme, breakpoints, useTheme, ThemeProvider } from "../theme"; export { useYoshiki, type Stylable } from "./generator"; diff --git a/packages/yoshiki/src/native/shorthands.ts b/packages/yoshiki/src/native/shorthands.ts new file mode 100644 index 0000000..41995f1 --- /dev/null +++ b/packages/yoshiki/src/native/shorthands.ts @@ -0,0 +1,54 @@ +// +// Copyright (c) Zoe Roux and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. +// + +import { YoshikiStyle } from "../type"; +import { ImageStyle, TextStyle, ViewStyle } from "react-native"; + +type Properties = ViewStyle | TextStyle | ImageStyle; +type YSPs = { [key in keyof Properties]: YoshikiStyle }; + +export const shorthandsFn = { + p: (v: YSPs["padding"]): YSPs => ({ + padding: v, + }), + pX: (v: YSPs["paddingLeft"]): YSPs => ({ + paddingLeft: v, + paddingRight: v, + }), + paddingX: (v: YSPs["paddingLeft"]): YSPs => ({ + paddingLeft: v, + paddingRight: v, + }), + pY: (v: YSPs["paddingTop"]): YSPs => ({ + paddingTop: v, + paddingBottom: v, + }), + paddingY: (v: YSPs["paddingTop"]): YSPs => ({ + paddingTop: v, + paddingBottom: v, + }), + m: (v: YSPs["margin"]): YSPs => ({ + margin: v, + }), + mX: (v: YSPs["marginLeft"]): YSPs => ({ + marginLeft: v, + marginRight: v, + }), + marginX: (v: YSPs["marginLeft"]): YSPs => ({ + marginLeft: v, + marginRight: v, + }), + mY: (v: YSPs["marginTop"]): YSPs => ({ + marginTop: v, + marginBottom: v, + }), + marginY: (v: YSPs["marginTop"]): YSPs => ({ + marginTop: v, + marginBottom: v, + }), + bg: (v: YSPs["backgroundColor"]): YSPs => ({ + backgroundColor: v, + }), +}; diff --git a/packages/yoshiki/src/react/generator.tsx b/packages/yoshiki/src/react/generator.tsx index 41f26b1..60a727e 100644 --- a/packages/yoshiki/src/react/generator.tsx +++ b/packages/yoshiki/src/react/generator.tsx @@ -9,9 +9,12 @@ import { isBreakpoints } from "../utils"; import { CSSProperties, useInsertionEffect } from "react"; import { useStyleRegistry } from "./registry"; import { Properties } from "csstype"; +import { shorthandsFn } from "./shorthands"; -export type CssObject = { +type CssObject = { [key in keyof Properties]: YoshikiStyle; +} & { + [key in keyof typeof shorthandsFn]?: Parameters[0]; }; const generateAtomicCss = ( @@ -19,6 +22,14 @@ const generateAtomicCss = , { theme }: { theme: Theme }, ): [string, string][] => { + if (key in shorthandsFn) { + // @ts-ignore `key` is not narrowed to `keyof typeof shorthandsFn` and value is not type safe. + const expanded = shorthandsFn[key as keyof typeof shorthandsFn](value); + return Object.entries(expanded) + .map(([eKey, eValue]) => generateAtomicCss(eKey, eValue, { theme })) + .flat(); + } + const cssKey = key.replace(/[A-Z]/g, "-$&").toLowerCase(); if (typeof value === "function") { diff --git a/packages/yoshiki/src/react/shorthands.ts b/packages/yoshiki/src/react/shorthands.ts new file mode 100644 index 0000000..bc2ea5e --- /dev/null +++ b/packages/yoshiki/src/react/shorthands.ts @@ -0,0 +1,54 @@ +// +// Copyright (c) Zoe Roux and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. +// + +import { YoshikiStyle } from "../type"; +import { Properties } from "csstype"; + +type YSPs = { [key in keyof Properties]: YoshikiStyle }; + +export const shorthandsFn = { + p: (v: YSPs["padding"]): YSPs => ({ + padding: v, + }), + pX: (v: YSPs["paddingLeft"]): YSPs => ({ + paddingLeft: v, + paddingRight: v, + }), + paddingX: (v: YSPs["paddingLeft"]): YSPs => ({ + paddingLeft: v, + paddingRight: v, + }), + pY: (v: YSPs["paddingTop"]): YSPs => ({ + paddingTop: v, + paddingBottom: v, + }), + paddingY: (v: YSPs["paddingTop"]): YSPs => ({ + paddingTop: v, + paddingBottom: v, + }), + m: (v: YSPs["margin"]): YSPs => ({ + margin: v, + }), + mX: (v: YSPs["marginLeft"]): YSPs => ({ + marginLeft: v, + marginRight: v, + }), + marginX: (v: YSPs["marginLeft"]): YSPs => ({ + marginLeft: v, + marginRight: v, + }), + mY: (v: YSPs["marginTop"]): YSPs => ({ + marginTop: v, + marginBottom: v, + }), + marginY: (v: YSPs["marginTop"]): YSPs => ({ + marginTop: v, + marginBottom: v, + }), + // This can't be background because react native does not support it. + bg: (v: YSPs["backgroundColor"]): YSPs => ({ + backgroundColor: v, + }), +}; diff --git a/packages/yoshiki/src/theme.tsx b/packages/yoshiki/src/theme.tsx index 81e9f2d..1fdb1f4 100644 --- a/packages/yoshiki/src/theme.tsx +++ b/packages/yoshiki/src/theme.tsx @@ -18,7 +18,7 @@ export const breakpoints = { const ThemeContext = createContext({}); -export const useTheme = () => useContext(ThemeContext); +export const useTheme = (): Theme => useContext(ThemeContext); export const ThemeProvider = ({ theme, children }: { theme: Theme; children?: ReactNode }) => { return {children}; diff --git a/packages/yoshiki/src/type.ts b/packages/yoshiki/src/type.ts index 249e7e4..747fa7e 100644 --- a/packages/yoshiki/src/type.ts +++ b/packages/yoshiki/src/type.ts @@ -7,7 +7,7 @@ import { breakpoints, Theme } from "./theme"; export type YoshikiStyle = | Property - | ((theme: Theme) => Property) + | ((theme: Theme) => Property | Breakpoints) | Breakpoints;