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",
|
||||
"slug": "expo-example",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"orientation": "default",
|
||||
"icon": "./assets/icon.png",
|
||||
"entryPoint": "./src/app.tsx",
|
||||
"userInterfaceStyle": "light",
|
||||
|
||||
@@ -31,9 +31,28 @@ const BoxWithoutProps = (props: Stylable) => {
|
||||
{
|
||||
backgroundColor: { xs: "#00ff00", md: "#ff0000" },
|
||||
transform: [{ scaleY: 0.7 }],
|
||||
hover: { alignContent: "center", alignItems: "center" },
|
||||
press: { alignContent: "center" },
|
||||
focus: { alignContent: "center" },
|
||||
press: {
|
||||
self: {
|
||||
bg: "red",
|
||||
},
|
||||
text: {
|
||||
color: "white",
|
||||
},
|
||||
},
|
||||
hover: {
|
||||
text: {
|
||||
color: "blue",
|
||||
},
|
||||
},
|
||||
focus: {
|
||||
self: {
|
||||
bg: "yellow"
|
||||
},
|
||||
text: {
|
||||
transform: [{ scale: 2 }],
|
||||
color: "green"
|
||||
},
|
||||
},
|
||||
},
|
||||
md({
|
||||
shadowOpacity: 0.5,
|
||||
@@ -45,9 +64,12 @@ const BoxWithoutProps = (props: Stylable) => {
|
||||
)}
|
||||
>
|
||||
<H1
|
||||
{...css({
|
||||
color: { xs: "black", md: "white" },
|
||||
})}
|
||||
{...css([
|
||||
{
|
||||
color: { xs: "black", md: "white" },
|
||||
},
|
||||
"text",
|
||||
])}
|
||||
>
|
||||
Text inside the box without props (green on small screens, red on bigs)
|
||||
</H1>
|
||||
@@ -85,12 +107,14 @@ function App() {
|
||||
<Text>Open up App.tsx to start working on your app!</Text>
|
||||
<CustomBox color="black" {...css({ borderColor: "red", borderWidth: px(3) })} />
|
||||
<BoxWithoutProps {...css({ borderColor: "red", borderWidth: px(3) })} />
|
||||
<P
|
||||
accessibilityLabel="toto"
|
||||
style={[undefined, false, { color: "red" }, [{ color: "green" }, false]]}
|
||||
>
|
||||
Test
|
||||
</P>
|
||||
<Pressable android_ripple={{ color: "black"}}>
|
||||
<P
|
||||
accessibilityLabel="toto"
|
||||
style={[undefined, false, { color: "red" }, [{ color: "green" }, false]]}
|
||||
>
|
||||
Test
|
||||
</P>
|
||||
</Pressable>
|
||||
<StatusBar style="auto" />
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -3,12 +3,20 @@
|
||||
// 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, YoshikiStyle, hasState, processStyleList } from "../type";
|
||||
import {
|
||||
Breakpoints,
|
||||
YoshikiStyle,
|
||||
hasState,
|
||||
processStyleList,
|
||||
processStyleListWithChild,
|
||||
assignChilds,
|
||||
} from "../type";
|
||||
import { isBreakpoints } from "../utils";
|
||||
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 { width } = useWindowDimensions();
|
||||
@@ -46,12 +54,19 @@ const propertyMapper = (
|
||||
return [[key, value]];
|
||||
};
|
||||
|
||||
const useForceRerender = () => {
|
||||
return useReducer((x) => x + 1, 0)[1];
|
||||
};
|
||||
|
||||
export const useYoshiki = () => {
|
||||
const breakpoint = useBreakpoint();
|
||||
const theme = useTheme();
|
||||
const rerender = useForceRerender();
|
||||
const childStyles = useRef<Record<string, NativeStyle | undefined>>({});
|
||||
|
||||
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 ret = Object.fromEntries(
|
||||
@@ -62,35 +77,63 @@ export const useYoshiki = () => {
|
||||
return ret;
|
||||
};
|
||||
|
||||
if (hasState<Record<string, unknown>>(css)) {
|
||||
if (hasState<Record<string, ViewStyle>>(css)) {
|
||||
const { hover, focus, press, ...inline } = css;
|
||||
const ret: StyleFunc<unknown> = ({ hovered, focused, pressed }) => ({
|
||||
...processStyle(inline),
|
||||
...(hovered ? processStyle(hover ?? {}) : {}),
|
||||
...(focused ? processStyle(focus ?? {}) : {}),
|
||||
...(pressed ? processStyle(press ?? {}) : {}),
|
||||
...(leftOvers?.style
|
||||
? typeof leftOvers?.style === "function"
|
||||
? processStyleList(leftOvers?.style({ hovered, focused, pressed }))
|
||||
: processStyleList(leftOvers?.style)
|
||||
: {}),
|
||||
});
|
||||
const { onPressIn, onPressOut, onHoverIn, onHoverOut, onFocus, onBlur } =
|
||||
leftOvers as PressableProps;
|
||||
const ret: StyleFunc<unknown> = ({ hovered, focused, pressed }) => {
|
||||
childStyles.current = {};
|
||||
if (hovered) assignChilds(childStyles.current, hover);
|
||||
if (focused) assignChilds(childStyles.current, focus);
|
||||
if (pressed) assignChilds(childStyles.current, press);
|
||||
|
||||
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 {
|
||||
...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 {
|
||||
const loStyles =
|
||||
leftOvers?.style && typeof leftOvers?.style !== "function"
|
||||
? processStyleList(leftOvers.style)
|
||||
: {};
|
||||
const ret = {
|
||||
return {
|
||||
...leftOvers,
|
||||
style: { ...processStyle(css), ...loStyles },
|
||||
};
|
||||
|
||||
return ret as any;
|
||||
style: [processStyle(css), leftOvers?.style],
|
||||
} as any;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { WithState, YoshikiStyle, StyleList } from "../type";
|
||||
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 { forceBreakpoint } from "../utils";
|
||||
|
||||
@@ -42,33 +42,18 @@ export type StyleFunc<Style> = (state: {
|
||||
|
||||
type AddLO<T, LO> = [LO] extends [never] ? T : Omit<LO, "style"> & T;
|
||||
|
||||
declare function nativeCss<Leftover = never>(
|
||||
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>;
|
||||
export type NativeStyle = ViewStyle | TextStyle | ImageStyle;
|
||||
|
||||
declare function nativeCss<Leftover = never>(
|
||||
cssList: StyleList<EnhancedStyle<TextStyle>>,
|
||||
leftOvers?: Leftover & { style?: StyleProp<TextStyle> | null },
|
||||
): AddLO<{ style?: TextStyle }, 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<Style extends NativeStyle, Leftover = never>(
|
||||
cssList: StyleList<EnhancedStyle<Style> | string>,
|
||||
leftOvers?: Leftover & { style?: StyleProp<Style> | null },
|
||||
): AddLO<{ style?: Style }, Leftover>;
|
||||
|
||||
declare function nativeCss<Leftover = never>(
|
||||
cssList: StyleList<EnhancedStyle<ImageStyle>>,
|
||||
leftOvers?: Leftover & { style?: StyleProp<ImageStyle> | null },
|
||||
): 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;
|
||||
},
|
||||
): AddLO<{ style?: StyleFunc<ImageStyle> }, Leftover>;
|
||||
declare function nativeCss<Style extends NativeStyle, Leftover = never>(
|
||||
cssList: StyleList<
|
||||
(EnhancedStyle<Style> & Partial<WithState<EnhancedStyle<NativeStyle>>>) | string
|
||||
>,
|
||||
leftOvers?: Leftover & { style?: StyleProp<Style> | StyleFunc<StyleProp<Style>> | null },
|
||||
): AddLO<PressableProps, Leftover>;
|
||||
|
||||
export type NativeCssFunc = typeof nativeCss;
|
||||
|
||||
@@ -15,9 +15,9 @@ export type Breakpoints<Property> = {
|
||||
};
|
||||
|
||||
export type WithState<Style> = {
|
||||
hover: Style;
|
||||
focus: Style;
|
||||
press: Style;
|
||||
hover: { self?: Style; [key: string]: Style | undefined };
|
||||
focus: { self?: Style; [key: string]: Style | undefined };
|
||||
press: { self?: Style; [key: string]: Style | undefined };
|
||||
};
|
||||
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) }), {});
|
||||
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