mirror of
https://github.com/zoriya/yoshiki.git
synced 2025-12-05 22:56:14 +00:00
Add pressable support on react native
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
""
|
||||
],
|
||||
1
|
||||
]
|
||||
],
|
||||
"@typescript-eslint/ban-ts-ignore": "off"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { StatusBar } from "expo-status-bar";
|
||||
import { Text, View } from "react-native";
|
||||
import { registerRootComponent } from "expo";
|
||||
import { Stylable, useYoshiki } from "yoshiki/native";
|
||||
import { Pressable, Stylable, useYoshiki } from "yoshiki/native";
|
||||
|
||||
const CustomBox = ({ color, ...props }: { color: string } & Stylable) => {
|
||||
const { css } = useYoshiki();
|
||||
@@ -22,9 +22,29 @@ const BoxWithoutProps = (props: Stylable) => {
|
||||
const { css } = useYoshiki();
|
||||
|
||||
return (
|
||||
<View {...css({ backgroundColor: { xs: "#00ff00", md: "#ff0000" } }, props)}>
|
||||
<Text>Text inside the box without props (green on small screens, red on bigs)</Text>
|
||||
</View>
|
||||
<Pressable
|
||||
{...css(
|
||||
{
|
||||
backgroundColor: { xs: "#00ff00", md: "#ff0000" },
|
||||
hover: { alignContent: "center" },
|
||||
press: { alignContent: "center" },
|
||||
focus: { alignContent: "center" },
|
||||
},
|
||||
props,
|
||||
)}
|
||||
>
|
||||
<Text
|
||||
{...css(
|
||||
{
|
||||
backgroundColor: { xs: "#00ff00", md: "#ff0000" },
|
||||
hover: { alignContent: "center" },
|
||||
},
|
||||
props,
|
||||
)}
|
||||
>
|
||||
Text inside the box without props (green on small screens, red on bigs)
|
||||
</Text>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -13,14 +13,9 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/AnonymusRaccoon/yoshiki/issues"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"source": "src/index.ts",
|
||||
"exports": {
|
||||
".": "./dist/index.ts",
|
||||
"./native": "./dist/native/index.ts",
|
||||
"./web": "./dist/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --build ."
|
||||
},
|
||||
|
||||
@@ -54,28 +54,70 @@ const propertyMapper = <
|
||||
return [[key, value]];
|
||||
};
|
||||
|
||||
type WithState<Style> = {
|
||||
hover: Style;
|
||||
focus: Style;
|
||||
press: Style;
|
||||
};
|
||||
type AtLeastOne<T, U = {[K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U]
|
||||
|
||||
const hasState = <Style,>(obj: unknown): obj is WithState<Style> => {
|
||||
if (!obj || typeof obj !== "object") return false;
|
||||
return "hover" in obj || "focus" in obj || "press" in obj;
|
||||
};
|
||||
|
||||
export const useYoshiki = () => {
|
||||
const breakpoint = useBreakpoint();
|
||||
const theme = useTheme();
|
||||
|
||||
return {
|
||||
css: <Style extends ViewStyle | TextStyle | ImageStyle>(
|
||||
css: EnhancedStyle<Style>,
|
||||
css: <
|
||||
Style extends ViewStyle | TextStyle | ImageStyle,
|
||||
State extends Partial<WithState<EnhancedStyle<Style>>> | Record<string, never>,
|
||||
>(
|
||||
css: EnhancedStyle<Style> & State,
|
||||
leftOvers?: { style?: Style },
|
||||
): { style: Style } => {
|
||||
): State extends AtLeastOne<WithState<unknown>>
|
||||
? { style: (state: { pressed: boolean; focused: boolean; hovered: boolean }) => Style }
|
||||
: { 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).flatMap(([key, value]) =>
|
||||
propertyMapper(key, value, { breakpoint, theme }),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
style: { ...inline, ...style },
|
||||
...leftOverProps,
|
||||
const processStyle = (styleList: EnhancedStyle<Style>): Style => {
|
||||
// @ts-ignore propertyMapper always returns key that are valid for the current Style
|
||||
return Object.fromEntries(
|
||||
Object.entries(styleList).flatMap(([key, value]) =>
|
||||
propertyMapper(key, value, { breakpoint, theme }),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
if (hasState<Style>(css)) {
|
||||
const { hover, focus, press, ...inline } = css;
|
||||
const ret: {
|
||||
style: (state: { hovered: boolean; focused: boolean; pressed: boolean }) => Style;
|
||||
} = {
|
||||
style: ({ hovered, focused, pressed }) => ({
|
||||
// @ts-ignore EnhancedStyle<Style> is not assignable to EnhancedStyle<Style>...
|
||||
...processStyle(inline),
|
||||
...(hovered ? hover : {}),
|
||||
...(focused ? focus : {}),
|
||||
...(pressed ? press : {}),
|
||||
...style,
|
||||
}),
|
||||
...leftOverProps,
|
||||
};
|
||||
|
||||
// @ts-ignore Ts is not able to identify the type of the return type (the condition throws him off).
|
||||
return ret;
|
||||
} else {
|
||||
const ret: { style: Style } = {
|
||||
style: { ...processStyle(css), ...style },
|
||||
...leftOverProps,
|
||||
};
|
||||
|
||||
// @ts-ignore Ts is not able to identify the type of the return type (the condition throws him off).
|
||||
return ret;
|
||||
}
|
||||
},
|
||||
theme: theme,
|
||||
};
|
||||
|
||||
51
packages/yoshiki/src/native/hover.tsx
Normal file
51
packages/yoshiki/src/native/hover.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// Copyright (c) Zoe Roux and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
//
|
||||
|
||||
import { useState } from "react";
|
||||
import { Pressable as RPressable, PressableProps, StyleProp, ViewStyle } from "react-native";
|
||||
|
||||
export const Pressable = ({
|
||||
children,
|
||||
onHoverIn,
|
||||
onHoverOut,
|
||||
onFocus,
|
||||
onBlur,
|
||||
style,
|
||||
...props
|
||||
}: {
|
||||
style:
|
||||
| PressableProps["style"]
|
||||
| ((state: { pressed: boolean; hovered: boolean; focused: boolean }) => StyleProp<ViewStyle>);
|
||||
} & PressableProps) => {
|
||||
const [hovered, setHover] = useState(false);
|
||||
const [focused, setFocus] = useState(false);
|
||||
|
||||
return (
|
||||
<RPressable
|
||||
onHoverIn={(e) => {
|
||||
onHoverIn?.call(null, e);
|
||||
setHover(true);
|
||||
}}
|
||||
onHoverOut={(e) => {
|
||||
onHoverOut?.call(null, e);
|
||||
setHover(false);
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
onFocus?.call(null, e);
|
||||
setFocus(true);
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
onBlur?.call(null, e);
|
||||
setFocus(false);
|
||||
}}
|
||||
style={
|
||||
typeof style === "function" ? ({ pressed }) => style({ pressed, hovered, focused }) : style
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</RPressable>
|
||||
);
|
||||
};
|
||||
@@ -6,3 +6,5 @@
|
||||
export { type Theme, breakpoints, useTheme, ThemeProvider } from "../theme";
|
||||
|
||||
export { useYoshiki, type Stylable } from "./generator";
|
||||
|
||||
export { Pressable } from "./hover";
|
||||
|
||||
Reference in New Issue
Block a user