Add higher order breakpoints

This commit is contained in:
Zoe Roux
2022-12-13 00:05:33 +09:00
parent 069a878dc5
commit 6a54aded63
11 changed files with 116 additions and 35 deletions
+40 -8
View File
@@ -15,12 +15,11 @@
As any other npm package, simply run
``yarn add yoshiki``
`yarn add yoshiki`
or
``npm install --save yoshiki``
`npm install --save yoshiki`
## Usage
@@ -70,6 +69,41 @@ const ColoredBox = ({ color }: { color: string }) => {
};
```
You can also use multiple style objects to apply some conditions or a breakpoint to multiple styles at once:
```tsx
import { useState } from "react";
import { Text, View } from "react-native";
import { Stylable, useYoshiki, md } from "yoshiki/native";
const ColoredBox = ({ color }: { color: string }) => {
const { css } = useYoshiki();
const [state, setState] = useState(state);
return (
<View
{...css([
{
backgroundColor: color,
height: { xs: "13%", lg: "25%" },
},
state && {
paddingX: (theme) => theme.spaccing,
m: 1,
},
md({
width: rem(3),
}),
])}
>
<Text {...css({ color: "white" })}>Text inside the colored box.</Text>
</View>
);
};
```
This syntax, as any others of Yoshiki works on both React and React Native.
## Recipes
### Customize your own components
@@ -237,21 +271,19 @@ const { css, theme } = useYoshiki;
The `theme` variable is the one returned from `useTheme` and the css function has the following signature:
```typescript
css: (css: CssObject, leftovers: object) => Props
css: (css: CssObject, leftovers: object) => Props;
```
The first parameter is a css object, in react web that means a dictionary of css key-values. On React Native that means
a `ViewStyle`, a `TextStyle` or an `ImageStyle`. Yoshiki will unsure type safety by returning the style object needed
for your arguments.
a `ViewStyle`, a `TextStyle` or an `ImageStyle`. Yoshiki will unsure type safety by returning the style object needed
for your arguments.
The css object can also take the keys `hover`, `focus` and `press`, and it will apply the style object given as a value
to the object only when it should.
The leftover parameter is here to allow your component to be customized by yoshiki. See [Customize your own components](#customize-your-own-components)
for more details.
### useTheme
```typescript
+16 -12
View File
@@ -6,7 +6,7 @@
import { StatusBar } from "expo-status-bar";
import { Text, View, Pressable, TextProps } from "react-native";
import { registerRootComponent } from "expo";
import { Stylable, useYoshiki, px } from "yoshiki/native";
import { Stylable, useYoshiki, px, md } from "yoshiki/native";
import { H1 } from "@expo/html-elements";
const CustomBox = ({ color, ...props }: { color: string } & Stylable) => {
@@ -27,15 +27,19 @@ const BoxWithoutProps = (props: Stylable) => {
return (
<Pressable
{...css(
{
backgroundColor: { xs: "#00ff00", md: "#ff0000" },
hover: { alignContent: "center", alignItems: "center" },
press: { alignContent: "center" },
focus: { alignContent: "center" },
shadowOpacity: 0.5,
shadowRadius: 2,
shadowOffset: { width: 3, height: 3 },
},
[
{
backgroundColor: { xs: "#00ff00", md: "#ff0000" },
hover: { alignContent: "center", alignItems: "center" },
press: { alignContent: "center" },
focus: { alignContent: "center" },
},
md({
shadowOpacity: 0.5,
shadowRadius: 2,
shadowOffset: { width: 3, height: 3 },
}),
],
props,
)}
>
@@ -55,9 +59,9 @@ const P = (props: TextProps) => {
return (
<Text
{...css(
{
md({
fontFamily: "toto",
},
}),
props,
)}
/>
+12 -8
View File
@@ -5,7 +5,7 @@
import Head from "next/head";
import Image from "next/image";
import { useYoshiki, Stylable, px } from "yoshiki/web";
import { useYoshiki, Stylable, px, md } from "yoshiki/web";
import { ReactNode } from "react";
const Box = ({ children, ...props }: { children?: ReactNode } & Stylable) => {
@@ -22,13 +22,17 @@ export default function Home(props: object) {
return (
<div
{...css(
{
display: "flex",
paddingLeft: "2rem",
paddingRight: "2rem",
flexGrow: 1,
margin: undefined,
},
[
{
display: "flex",
paddingLeft: "2rem",
paddingRight: "2rem",
},
md({
flexGrow: 1,
margin: undefined,
}),
],
props,
)}
>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "yoshiki",
"version": "0.2.13",
"version": "0.3.1",
"author": "Zoe Roux <zoe.roux@sdg.moe> (https://github.com/AnonymusRaccoon)",
"license": "MIT",
"keywords": [
+2 -1
View File
@@ -36,7 +36,8 @@ const propertyMapper = (
const bpKeys = Object.keys(breakpoints) as Array<keyof Breakpoints<unknown>>;
for (let i = breakpoint; i >= 0; i--) {
if (bpKeys[i] in value) {
const bpVal = value[bpKeys[i]];
const bpValOrF = value[bpKeys[i]];
const bpVal = typeof bpValOrF === "function" ? bpValOrF(theme) : bpValOrF;
return bpVal ? [[key, bpVal]] : [];
}
}
+1
View File
@@ -27,6 +27,7 @@ export { breakpoints, useTheme } from "../theme";
export { useYoshiki } from "./generator";
export { Pressable } from "./hover";
export * from "./units";
export { sm, md, lg, xl } from "./type";
export const ThemeProvider = ({ theme, children }: { theme: Theme; children?: ReactNode }) => {
return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;
+20 -1
View File
@@ -6,6 +6,8 @@
import { FilterOr, WithState, YoshikiStyle, Length, StyleList } from "../type";
import { shorthandsFn } from "../shorthands";
import { ImageStyle, StyleProp, TextStyle, ViewStyle } from "react-native";
import { Theme } from "../theme";
import { forceBreakpoint, WithBreakpoints } from "../utils";
// The extends any check is only used to make EnhancedStyle a distributive type.
// This means EnhancedStyle<ViewStyle | TextStyle> = EnhancedStyle<ViewStyle> | EnhancedStyle<TextStyle>
@@ -21,6 +23,21 @@ export type EnhancedStyle<Properties> = Properties extends any
}
: never;
type ForcedBreakpointStyle<Properties> = Properties extends any
? {
[key in keyof Properties]: Properties[key] | ((theme: Theme) => Properties[key]);
}
: never;
export const sm = (value: ForcedBreakpointStyle<ViewStyle | TextStyle | ImageStyle>) =>
forceBreakpoint(value, "sm");
export const md = (value: ForcedBreakpointStyle<ViewStyle | TextStyle | ImageStyle>) =>
forceBreakpoint(value, "md");
export const lg = (value: ForcedBreakpointStyle<ViewStyle | TextStyle | ImageStyle>) =>
forceBreakpoint(value, "lg");
export const xl = (value: ForcedBreakpointStyle<ViewStyle | TextStyle | ImageStyle>) =>
forceBreakpoint(value, "xl");
export type StyleFunc<Style> = (state: {
pressed?: boolean;
focused?: boolean;
@@ -53,7 +70,9 @@ declare function nativeCss<Leftover = never>(
): AddLO<{ style?: ImageStyle }, Leftover>;
declare function nativeCss<Leftover = never>(
cssList: StyleList<EnhancedStyle<ImageStyle> & Partial<WithState<EnhancedStyle<ImageStyle>>>>,
leftOvers?: Leftover & { style?: StyleProp<ImageStyle> | StyleFunc<StyleProp<ImageStyle>> | null },
leftOvers?: Leftover & {
style?: StyleProp<ImageStyle> | StyleFunc<StyleProp<ImageStyle>> | null;
},
): AddLO<{ style?: StyleFunc<ImageStyle> }, Leftover>;
export type NativeCssFunc = typeof nativeCss;
+1 -1
View File
@@ -10,7 +10,7 @@ import { shorthandsFn } from "./shorthands";
export type YoshikiStyle<Property> =
| Property
| ((theme: Theme) => Property | Breakpoints<Property>)
| Breakpoints<Property>;
| Breakpoints<Property | ((theme: Theme) => Property)>;
export type Breakpoints<Property> = {
[key in keyof typeof breakpoints]?: Property;
+11
View File
@@ -15,3 +15,14 @@ export const isBreakpoints = <T>(value: unknown): value is Breakpoints<T> => {
}
return true;
};
export type WithBreakpoints<T> = T extends any ? { [key in keyof T]: Breakpoints<T[key]> } : never;
export const forceBreakpoint = <T extends Record<string, unknown>>(
value: T,
breakpoint: keyof typeof breakpoints,
): WithBreakpoints<T> => {
return Object.fromEntries(
Object.entries(value).map(([key, value]) => [key, { [breakpoint]: value }]),
) as any;
};
+11 -2
View File
@@ -7,7 +7,7 @@ import { useInsertionEffect } from "react";
import { prefix } from "inline-style-prefixer";
import { Theme, breakpoints, useTheme } from "../theme";
import { WithState, YoshikiStyle, CssProperties, StyleList, processStyleList } from "../type";
import { isBreakpoints } from "../utils";
import { forceBreakpoint, isBreakpoints } from "../utils";
import { StyleRegistry, useStyleRegistry } from "./registry";
import { shorthandsFn } from "../shorthands";
@@ -19,6 +19,15 @@ type _CssObject = {
export type CssObject = Partial<WithState<_CssObject>> & _CssObject;
type ForcedBreakpointStyle = {
[key in keyof CssProperties]: CssProperties[key] | ((theme: Theme) => CssProperties[key]);
};
export const sm = (value: ForcedBreakpointStyle) => forceBreakpoint(value, "sm");
export const md = (value: ForcedBreakpointStyle) => forceBreakpoint(value, "md");
export const lg = (value: ForcedBreakpointStyle) => forceBreakpoint(value, "lg");
export const xl = (value: ForcedBreakpointStyle) => forceBreakpoint(value, "xl");
const stateMapper: {
[key in keyof (WithState<undefined> & { normal: undefined })]: (cn: string) => string;
} = {
@@ -88,7 +97,7 @@ const generateAtomicCss = (
return Object.entries(value).flatMap(([bp, bpValue]) => {
return generateClass(
key,
bpValue,
typeof bpValue === "function" ? bpValue(theme) : bpValue,
`${statePrefix}${bp}_`,
(className, block) => {
const bpWidth = breakpoints[bp as keyof typeof breakpoints];
+1 -1
View File
@@ -16,7 +16,7 @@ export type Stylable = {
export type StylableHoverable = Stylable;
export { useYoshiki } from "./generator";
export { useYoshiki, sm, md, lg, xl } from "./generator";
export { StyleRegistryProvider, useStyleRegistry, createStyleRegistry } from "./registry";
export { useMobileHover } from "./hover";
export * from "./units";