mirror of
https://github.com/zoriya/yoshiki.git
synced 2025-12-06 07:06:13 +00:00
Support child states for react native
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
"name": "expo-example",
|
"name": "expo-example",
|
||||||
"slug": "expo-example",
|
"slug": "expo-example",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"orientation": "portrait",
|
"orientation": "default",
|
||||||
"icon": "./assets/icon.png",
|
"icon": "./assets/icon.png",
|
||||||
"entryPoint": "./src/app.tsx",
|
"entryPoint": "./src/app.tsx",
|
||||||
"userInterfaceStyle": "light",
|
"userInterfaceStyle": "light",
|
||||||
|
|||||||
@@ -31,9 +31,28 @@ const BoxWithoutProps = (props: Stylable) => {
|
|||||||
{
|
{
|
||||||
backgroundColor: { xs: "#00ff00", md: "#ff0000" },
|
backgroundColor: { xs: "#00ff00", md: "#ff0000" },
|
||||||
transform: [{ scaleY: 0.7 }],
|
transform: [{ scaleY: 0.7 }],
|
||||||
hover: { alignContent: "center", alignItems: "center" },
|
press: {
|
||||||
press: { alignContent: "center" },
|
self: {
|
||||||
focus: { alignContent: "center" },
|
bg: "red",
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
color: "white",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
text: {
|
||||||
|
color: "blue",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
focus: {
|
||||||
|
self: {
|
||||||
|
bg: "yellow"
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
transform: [{ scale: 2 }],
|
||||||
|
color: "green"
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
md({
|
md({
|
||||||
shadowOpacity: 0.5,
|
shadowOpacity: 0.5,
|
||||||
@@ -45,9 +64,12 @@ const BoxWithoutProps = (props: Stylable) => {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<H1
|
<H1
|
||||||
{...css({
|
{...css([
|
||||||
color: { xs: "black", md: "white" },
|
{
|
||||||
})}
|
color: { xs: "black", md: "white" },
|
||||||
|
},
|
||||||
|
"text",
|
||||||
|
])}
|
||||||
>
|
>
|
||||||
Text inside the box without props (green on small screens, red on bigs)
|
Text inside the box without props (green on small screens, red on bigs)
|
||||||
</H1>
|
</H1>
|
||||||
@@ -85,12 +107,14 @@ function App() {
|
|||||||
<Text>Open up App.tsx to start working on your app!</Text>
|
<Text>Open up App.tsx to start working on your app!</Text>
|
||||||
<CustomBox color="black" {...css({ borderColor: "red", borderWidth: px(3) })} />
|
<CustomBox color="black" {...css({ borderColor: "red", borderWidth: px(3) })} />
|
||||||
<BoxWithoutProps {...css({ borderColor: "red", borderWidth: px(3) })} />
|
<BoxWithoutProps {...css({ borderColor: "red", borderWidth: px(3) })} />
|
||||||
<P
|
<Pressable android_ripple={{ color: "black"}}>
|
||||||
accessibilityLabel="toto"
|
<P
|
||||||
style={[undefined, false, { color: "red" }, [{ color: "green" }, false]]}
|
accessibilityLabel="toto"
|
||||||
>
|
style={[undefined, false, { color: "red" }, [{ color: "green" }, false]]}
|
||||||
Test
|
>
|
||||||
</P>
|
Test
|
||||||
|
</P>
|
||||||
|
</Pressable>
|
||||||
<StatusBar style="auto" />
|
<StatusBar style="auto" />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,12 +3,20 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { useWindowDimensions } from "react-native";
|
import { PressableProps, useWindowDimensions, ViewStyle } from "react-native";
|
||||||
import { breakpoints, Theme, useTheme } from "../theme";
|
import { breakpoints, Theme, useTheme } from "../theme";
|
||||||
import { Breakpoints, YoshikiStyle, hasState, processStyleList } from "../type";
|
import {
|
||||||
|
Breakpoints,
|
||||||
|
YoshikiStyle,
|
||||||
|
hasState,
|
||||||
|
processStyleList,
|
||||||
|
processStyleListWithChild,
|
||||||
|
assignChilds,
|
||||||
|
} from "../type";
|
||||||
import { isBreakpoints } from "../utils";
|
import { isBreakpoints } from "../utils";
|
||||||
import { shorthandsFn } from "../shorthands";
|
import { shorthandsFn } from "../shorthands";
|
||||||
import { StyleFunc, NativeCssFunc } from "./type";
|
import { StyleFunc, NativeCssFunc, NativeStyle } from "./type";
|
||||||
|
import { useReducer, useRef, useState } from "react";
|
||||||
|
|
||||||
const useBreakpoint = (): number => {
|
const useBreakpoint = (): number => {
|
||||||
const { width } = useWindowDimensions();
|
const { width } = useWindowDimensions();
|
||||||
@@ -46,12 +54,19 @@ const propertyMapper = (
|
|||||||
return [[key, value]];
|
return [[key, value]];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const useForceRerender = () => {
|
||||||
|
return useReducer((x) => x + 1, 0)[1];
|
||||||
|
};
|
||||||
|
|
||||||
export const useYoshiki = () => {
|
export const useYoshiki = () => {
|
||||||
const breakpoint = useBreakpoint();
|
const breakpoint = useBreakpoint();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const rerender = useForceRerender();
|
||||||
|
const childStyles = useRef<Record<string, NativeStyle | undefined>>({});
|
||||||
|
|
||||||
const css: NativeCssFunc = (cssList, leftOvers) => {
|
const css: NativeCssFunc = (cssList, leftOvers) => {
|
||||||
const css = processStyleList(cssList);
|
// The as any is because we can't be sure the style type is right one.
|
||||||
|
const css = processStyleListWithChild(cssList, childStyles.current as any);
|
||||||
|
|
||||||
const processStyle = (styleList: Record<string, YoshikiStyle<unknown>>) => {
|
const processStyle = (styleList: Record<string, YoshikiStyle<unknown>>) => {
|
||||||
const ret = Object.fromEntries(
|
const ret = Object.fromEntries(
|
||||||
@@ -62,35 +77,63 @@ export const useYoshiki = () => {
|
|||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (hasState<Record<string, unknown>>(css)) {
|
if (hasState<Record<string, ViewStyle>>(css)) {
|
||||||
const { hover, focus, press, ...inline } = css;
|
const { hover, focus, press, ...inline } = css;
|
||||||
const ret: StyleFunc<unknown> = ({ hovered, focused, pressed }) => ({
|
const { onPressIn, onPressOut, onHoverIn, onHoverOut, onFocus, onBlur } =
|
||||||
...processStyle(inline),
|
leftOvers as PressableProps;
|
||||||
...(hovered ? processStyle(hover ?? {}) : {}),
|
const ret: StyleFunc<unknown> = ({ hovered, focused, pressed }) => {
|
||||||
...(focused ? processStyle(focus ?? {}) : {}),
|
childStyles.current = {};
|
||||||
...(pressed ? processStyle(press ?? {}) : {}),
|
if (hovered) assignChilds(childStyles.current, hover);
|
||||||
...(leftOvers?.style
|
if (focused) assignChilds(childStyles.current, focus);
|
||||||
? typeof leftOvers?.style === "function"
|
if (pressed) assignChilds(childStyles.current, press);
|
||||||
? processStyleList(leftOvers?.style({ hovered, focused, pressed }))
|
|
||||||
: processStyleList(leftOvers?.style)
|
return [
|
||||||
: {}),
|
processStyle(inline),
|
||||||
});
|
hovered && processStyle(hover?.self ?? {}),
|
||||||
|
focused && processStyle(focus?.self ?? {}),
|
||||||
|
pressed && processStyle(press?.self ?? {}),
|
||||||
|
leftOvers?.style &&
|
||||||
|
(typeof leftOvers?.style === "function"
|
||||||
|
? processStyleList(leftOvers?.style({ hovered, focused, pressed }))
|
||||||
|
: leftOvers?.style),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...leftOvers,
|
...leftOvers,
|
||||||
style: ret,
|
style: ret as StyleFunc<ViewStyle>,
|
||||||
};
|
// We must use a setTimeout since the child styles are computed inside the style function (called after onIn/onOut)
|
||||||
|
// NOTE: The props onIn/onOut are overriden here and the user can't use them. Might want to find a way arround that.
|
||||||
|
onPressIn: (e) => {
|
||||||
|
onPressIn?.call(null, e);
|
||||||
|
setTimeout(rerender);
|
||||||
|
},
|
||||||
|
onPressOut: (e) => {
|
||||||
|
onPressOut?.call(null, e);
|
||||||
|
setTimeout(rerender);
|
||||||
|
},
|
||||||
|
onHoverIn: (e) => {
|
||||||
|
onHoverIn?.call(null, e);
|
||||||
|
setTimeout(rerender);
|
||||||
|
},
|
||||||
|
onHoverOut: (e) => {
|
||||||
|
onHoverOut?.call(null, e);
|
||||||
|
setTimeout(rerender);
|
||||||
|
},
|
||||||
|
onFocus: (e) => {
|
||||||
|
onFocus?.call(null, e);
|
||||||
|
setTimeout(rerender);
|
||||||
|
},
|
||||||
|
onBlur: (e) => {
|
||||||
|
onBlur?.call(null, e);
|
||||||
|
setTimeout(rerender);
|
||||||
|
},
|
||||||
|
} satisfies PressableProps;
|
||||||
} else {
|
} else {
|
||||||
const loStyles =
|
return {
|
||||||
leftOvers?.style && typeof leftOvers?.style !== "function"
|
|
||||||
? processStyleList(leftOvers.style)
|
|
||||||
: {};
|
|
||||||
const ret = {
|
|
||||||
...leftOvers,
|
...leftOvers,
|
||||||
style: { ...processStyle(css), ...loStyles },
|
style: [processStyle(css), leftOvers?.style],
|
||||||
};
|
} as any;
|
||||||
|
|
||||||
return ret as any;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { WithState, YoshikiStyle, StyleList } from "../type";
|
import { WithState, YoshikiStyle, StyleList } from "../type";
|
||||||
import { shorthandsFn } from "../shorthands";
|
import { shorthandsFn } from "../shorthands";
|
||||||
import { ImageStyle, StyleProp, TextStyle, ViewStyle } from "react-native";
|
import { ImageStyle, PressableProps, StyleProp, TextStyle, ViewStyle } from "react-native";
|
||||||
import { Theme } from "../theme";
|
import { Theme } from "../theme";
|
||||||
import { forceBreakpoint } from "../utils";
|
import { forceBreakpoint } from "../utils";
|
||||||
|
|
||||||
@@ -42,33 +42,18 @@ export type StyleFunc<Style> = (state: {
|
|||||||
|
|
||||||
type AddLO<T, LO> = [LO] extends [never] ? T : Omit<LO, "style"> & T;
|
type AddLO<T, LO> = [LO] extends [never] ? T : Omit<LO, "style"> & T;
|
||||||
|
|
||||||
declare function nativeCss<Leftover = never>(
|
export type NativeStyle = ViewStyle | TextStyle | ImageStyle;
|
||||||
cssList: StyleList<EnhancedStyle<ViewStyle>>,
|
|
||||||
leftOvers?: Leftover & { style?: StyleProp<ViewStyle> | null },
|
|
||||||
): AddLO<{ style?: ViewStyle }, Leftover>;
|
|
||||||
declare function nativeCss<Leftover = never>(
|
|
||||||
cssList: StyleList<EnhancedStyle<ViewStyle> & Partial<WithState<EnhancedStyle<ViewStyle>>>>,
|
|
||||||
leftOvers?: Leftover & { style?: StyleProp<ViewStyle> | StyleFunc<StyleProp<ViewStyle>> | null },
|
|
||||||
): AddLO<{ style?: StyleFunc<ViewStyle> }, Leftover>;
|
|
||||||
|
|
||||||
declare function nativeCss<Leftover = never>(
|
declare function nativeCss<Style extends NativeStyle, Leftover = never>(
|
||||||
cssList: StyleList<EnhancedStyle<TextStyle>>,
|
cssList: StyleList<EnhancedStyle<Style> | string>,
|
||||||
leftOvers?: Leftover & { style?: StyleProp<TextStyle> | null },
|
leftOvers?: Leftover & { style?: StyleProp<Style> | null },
|
||||||
): AddLO<{ style?: TextStyle }, Leftover>;
|
): AddLO<{ style?: Style }, Leftover>;
|
||||||
declare function nativeCss<Leftover = never>(
|
|
||||||
cssList: StyleList<EnhancedStyle<TextStyle> & Partial<WithState<EnhancedStyle<TextStyle>>>>,
|
|
||||||
leftOvers?: Leftover & { style?: StyleProp<TextStyle> | StyleFunc<StyleProp<TextStyle>> | null },
|
|
||||||
): AddLO<{ style?: StyleFunc<TextStyle> }, Leftover>;
|
|
||||||
|
|
||||||
declare function nativeCss<Leftover = never>(
|
declare function nativeCss<Style extends NativeStyle, Leftover = never>(
|
||||||
cssList: StyleList<EnhancedStyle<ImageStyle>>,
|
cssList: StyleList<
|
||||||
leftOvers?: Leftover & { style?: StyleProp<ImageStyle> | null },
|
(EnhancedStyle<Style> & Partial<WithState<EnhancedStyle<NativeStyle>>>) | string
|
||||||
): AddLO<{ style?: ImageStyle }, Leftover>;
|
>,
|
||||||
declare function nativeCss<Leftover = never>(
|
leftOvers?: Leftover & { style?: StyleProp<Style> | StyleFunc<StyleProp<Style>> | null },
|
||||||
cssList: StyleList<EnhancedStyle<ImageStyle> & Partial<WithState<EnhancedStyle<ImageStyle>>>>,
|
): AddLO<PressableProps, Leftover>;
|
||||||
leftOvers?: Leftover & {
|
|
||||||
style?: StyleProp<ImageStyle> | StyleFunc<StyleProp<ImageStyle>> | null;
|
|
||||||
},
|
|
||||||
): AddLO<{ style?: StyleFunc<ImageStyle> }, Leftover>;
|
|
||||||
|
|
||||||
export type NativeCssFunc = typeof nativeCss;
|
export type NativeCssFunc = typeof nativeCss;
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ export type Breakpoints<Property> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type WithState<Style> = {
|
export type WithState<Style> = {
|
||||||
hover: Style;
|
hover: { self?: Style; [key: string]: Style | undefined };
|
||||||
focus: Style;
|
focus: { self?: Style; [key: string]: Style | undefined };
|
||||||
press: Style;
|
press: { self?: Style; [key: string]: Style | undefined };
|
||||||
};
|
};
|
||||||
export type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U];
|
export type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U];
|
||||||
|
|
||||||
@@ -33,3 +33,25 @@ export const processStyleList = <Style>(los: StyleList<Style>): Partial<Style> =
|
|||||||
if (isReadonlyArray(los)) return los.reduce((acc, x) => ({ ...acc, ...processStyleList(x) }), {});
|
if (isReadonlyArray(los)) return los.reduce((acc, x) => ({ ...acc, ...processStyleList(x) }), {});
|
||||||
return los ? los : {};
|
return los ? los : {};
|
||||||
};
|
};
|
||||||
|
export const processStyleListWithChild = <Style>(
|
||||||
|
los: StyleList<Style | string>,
|
||||||
|
parent: Record<string, Style>,
|
||||||
|
): Partial<Style> => {
|
||||||
|
if (isReadonlyArray(los))
|
||||||
|
return los.reduce((acc, x) => ({ ...acc, ...processStyleListWithChild(x, parent) }), {});
|
||||||
|
if (!los) return {};
|
||||||
|
return typeof los === "string" ? parent[los] ?? {} : los;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const assignChilds = <Style>(
|
||||||
|
target: Record<string, Style | undefined>,
|
||||||
|
style?: Record<string, Style | undefined>,
|
||||||
|
) => {
|
||||||
|
if (!style) return target;
|
||||||
|
for (const entry in style) {
|
||||||
|
if (entry === "self") continue;
|
||||||
|
if (!target[entry]) target[entry] = style[entry];
|
||||||
|
else target[entry] = { ...target[entry], ...style[entry] } as any;
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user