mirror of
https://github.com/zoriya/yoshiki.git
synced 2026-05-31 09:44:02 +00:00
Add higher order breakpoints
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -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,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": [
|
||||
|
||||
@@ -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]] : [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user