mirror of
https://github.com/zoriya/yoshiki.git
synced 2025-12-05 22:56:14 +00:00
Add a readme
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -42,4 +42,4 @@ yarn-error.log*
|
||||
!.yarn/sdks
|
||||
|
||||
.expo
|
||||
|
||||
dist/
|
||||
|
||||
213
README.md
Normal file
213
README.md
Normal file
@@ -0,0 +1,213 @@
|
||||
# yoshiki
|
||||
|
||||
## Features
|
||||
|
||||
- Same interface for React and React Native
|
||||
- Universal API working for builtins, any component library or your own
|
||||
- Breakpoints as objects
|
||||
- User defined theme support
|
||||
- Shorthands (`m` for `margin`, `paddingX` for `paddingLeft` and `paddingRight`...)
|
||||
- Atomic CSS generation
|
||||
- SSR support
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Stylable, useYoshiki } from "yoshiki";
|
||||
|
||||
const ColoredDiv = ({ color }: { color: string }) => {
|
||||
const { css } = useYoshiki();
|
||||
|
||||
return (
|
||||
<div
|
||||
{...css({
|
||||
backgroundColor: color,
|
||||
height: { xs: "13%", lg: "25%" },
|
||||
paddingX: (theme) => theme.spaccing,
|
||||
m: 1,
|
||||
})}
|
||||
>
|
||||
<p {...css({ color: "white" })}>Text inside the colored div.</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
Or for React Native components, simply use the `yoshiki/native` import.
|
||||
Notice that the only difference between the two are the components and the import.
|
||||
|
||||
```tsx
|
||||
import { Text, View } from "react-native";
|
||||
import { Stylable, useYoshiki } from "yoshiki/native";
|
||||
|
||||
const ColoredBox = ({ color }: { color: string }) => {
|
||||
const { css } = useYoshiki();
|
||||
|
||||
return (
|
||||
<View
|
||||
{...css({
|
||||
backgroundColor: color,
|
||||
height: { xs: "13%", lg: "25%" },
|
||||
paddingX: (theme) => theme.spaccing,
|
||||
m: 1,
|
||||
})}
|
||||
>
|
||||
<Text {...css({ color: "white" })}>Text inside the colored box.</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Recipes
|
||||
|
||||
### Customize your own components
|
||||
|
||||
In order to theme your own components, you need to forward some props to the root element like the following example:
|
||||
|
||||
```tsx
|
||||
const Example = (props) => {
|
||||
return (
|
||||
<div {...props}>
|
||||
<p>Example component</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
If you want to use yoshiki to theme your component and allow others components to override styles, pass
|
||||
the props to the `css` function.
|
||||
|
||||
```tsx
|
||||
import { useYoshiki } from "yoshiki";
|
||||
|
||||
const Example = (props) => {
|
||||
const { css } = useYoshiki();
|
||||
|
||||
return (
|
||||
<div {...css({ background: "black" }, props)}>
|
||||
<p>Example component</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
To stay type-safe and ensure you don't forget to pass down the props, yoshiki exposes the `Stylable` type, so you can do:
|
||||
|
||||
```tsx
|
||||
import { ReactNode } from "react";
|
||||
import { useYoshiki, Stylable } from "yoshiki";
|
||||
// or
|
||||
// import { useYoshiki, Stylable } from "yoshiki/native";
|
||||
|
||||
const Example = (props: Stylable) => {
|
||||
const { css } = useYoshiki();
|
||||
|
||||
return (
|
||||
<div {...css({ background: "black" }, props)}>
|
||||
<ExampleText {...css({ padding: "15px" })}>Example component</ExampleText>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ExampleText = ({ children, ...props }: { children: ReactNode } & Stylable) => {
|
||||
const { css } = useYoshiki();
|
||||
|
||||
return <p {...css({ color: "white", padding: "10px" }, props)}>{children}</p>;
|
||||
};
|
||||
```
|
||||
|
||||
Yoshiki will handle overrides so the `ExampleText`'s p element will have a padding of `15px`.
|
||||
|
||||
### Server Side Rendering (SSR)
|
||||
|
||||
#### Generic
|
||||
|
||||
To support server side rendering, you need to create a style registry and wrap your app with a `StyleRegistryProvider`.
|
||||
|
||||
```tsx
|
||||
import { StyleRegistryProvider, createStyleRegistry } from "yoshiki";
|
||||
|
||||
const registry = createStyleRegistry();
|
||||
|
||||
const html = renderToString(
|
||||
<StyleRegistryProvider registry={registry}>
|
||||
<App />
|
||||
</StyleRegistryProvider>,
|
||||
);
|
||||
|
||||
// A list of classes to append to your html head.
|
||||
const style = registry.flush();
|
||||
```
|
||||
|
||||
#### Next
|
||||
|
||||
Simply use the following `getInitialProps` inside the `pages/_document.tsx` file.
|
||||
|
||||
```tsx
|
||||
import { StyleRegistryProvider, createStyleRegistry } from "yoshiki";
|
||||
import Document, { DocumentContext } from "next/document";
|
||||
|
||||
Document.getInitialProps = async (ctx: DocumentContext) => {
|
||||
const renderPage = ctx.renderPage;
|
||||
const registry = createStyleRegistry();
|
||||
|
||||
ctx.renderPage = () =>
|
||||
renderPage({
|
||||
enhanceApp: (App) => (props) => {
|
||||
return (
|
||||
<StyleRegistryProvider registry={registry}>
|
||||
<App {...props} />
|
||||
</StyleRegistryProvider>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const props = await ctx.defaultGetInitialProps(ctx);
|
||||
return {
|
||||
...props,
|
||||
styles: (
|
||||
<>
|
||||
{props.styles}
|
||||
{registry.flushToComponent()}
|
||||
</>
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
export default Document;
|
||||
```
|
||||
|
||||
### Theme
|
||||
|
||||
To use the theme, you need to wrap your app with a `ThemeProvider`. If you are using typescript, you will also
|
||||
need to use module augmentation to add your properties to the theme object.
|
||||
|
||||
```tsx
|
||||
import { Theme, ThemeProvider, useYoshiki } from "yoshiki";
|
||||
|
||||
declare module "yoshiki" {
|
||||
export interface Theme {
|
||||
spacing: string;
|
||||
name: string;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultTheme: Theme = {
|
||||
spacing: "24px",
|
||||
name: "yoshiki",
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<ThemeProvider theme={defaultTheme}>
|
||||
<AppName />
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const AppName = () => {
|
||||
const { css, theme } = useYoshiki();
|
||||
|
||||
return <p {...css({ padding: (theme) => theme.spacing })}>{theme.name}</p>;
|
||||
};
|
||||
```
|
||||
36
examples/next-example/src/pages/_app.tsx
Normal file
36
examples/next-example/src/pages/_app.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// Copyright (c) Zoe Roux and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
//
|
||||
|
||||
import { Theme, ThemeProvider, useYoshiki } from "yoshiki";
|
||||
import { AppProps } from "next/app";
|
||||
|
||||
declare module "yoshiki" {
|
||||
export interface Theme {
|
||||
spacing: string;
|
||||
name: string;
|
||||
}
|
||||
}
|
||||
|
||||
export const theme: Theme = {
|
||||
spacing: "24px",
|
||||
name: "yoshiki",
|
||||
};
|
||||
|
||||
const AppName = () => {
|
||||
const { css, theme } = useYoshiki();
|
||||
|
||||
return <p {...css({ padding: (theme) => theme.spacing })}>{theme.name}</p>;
|
||||
};
|
||||
|
||||
const App = ({ Component, pageProps }: AppProps) => {
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<Component {...pageProps} />
|
||||
<AppName />
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
@@ -6,6 +6,7 @@
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "yarn workspace yoshiki build",
|
||||
"next": "yarn workspace next-example dev",
|
||||
"expo": "yarn workspace expo-example dev",
|
||||
"lint": "eslint . --ext .ts,.tsx "
|
||||
|
||||
@@ -1,11 +1,28 @@
|
||||
{
|
||||
"name": "yoshiki",
|
||||
"version": "1.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"version": "0.1.0",
|
||||
"author": "Zoe Roux <zoe.roux@sdg.moe> (https://github.com/AnonymusRaccoon)",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native",
|
||||
"css-in-js"
|
||||
],
|
||||
"repository": "https://github.com/AnonymusRaccoon/yoshiki",
|
||||
"homepage": "https://github.com/AnonymusRaccoon/yoshiki",
|
||||
"bugs": {
|
||||
"url": "https://github.com/AnonymusRaccoon/yoshiki/issues"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"source": "src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./native": "./src/native/index.ts"
|
||||
".": "./dist/index.ts",
|
||||
"./native": "./dist/native/index.ts",
|
||||
"./web": "./dist/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --build ."
|
||||
},
|
||||
"packageManager": "yarn@3.2.4",
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
//
|
||||
|
||||
export { type Theme, breakpoints, useTheme } from "./theme";
|
||||
export { type Theme, breakpoints, useTheme, ThemeProvider, defaultTheme } from "./theme";
|
||||
|
||||
export {
|
||||
useYoshiki,
|
||||
@@ -11,5 +11,4 @@ export {
|
||||
StyleRegistryProvider,
|
||||
createStyleRegistry,
|
||||
type Stylable,
|
||||
type CssObject,
|
||||
} from "./react";
|
||||
|
||||
@@ -12,10 +12,6 @@ import { isBreakpoints } from "../utils";
|
||||
type EnhancedStyle<Properties> = {
|
||||
[key in keyof Properties]: YoshikiStyle<Properties[key]>;
|
||||
};
|
||||
export type CssObject =
|
||||
| EnhancedStyle<ViewStyle>
|
||||
| EnhancedStyle<TextStyle>
|
||||
| EnhancedStyle<ImageStyle>;
|
||||
type Properties = ViewStyle | TextStyle | ImageStyle;
|
||||
|
||||
const useBreakpoint = (): number => {
|
||||
@@ -51,10 +47,13 @@ export const useYoshiki = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
return {
|
||||
css: (css: CssObject, leftOvers?: { style?: Properties }) => {
|
||||
css: <Style extends ViewStyle | TextStyle | ImageStyle>(
|
||||
css: EnhancedStyle<Style>,
|
||||
leftOvers?: { style?: Style },
|
||||
): { style: Style } => {
|
||||
const { style, ...leftOverProps } = leftOvers ?? {};
|
||||
|
||||
const inline: Properties = Object.fromEntries(
|
||||
const inline: Style = Object.fromEntries(
|
||||
Object.entries(css)
|
||||
.map(([key, value]) => [key, propertyMapper(value, { breakpoint, theme })])
|
||||
.filter(([, value]) => value !== undefined),
|
||||
@@ -70,5 +69,5 @@ export const useYoshiki = () => {
|
||||
};
|
||||
|
||||
export type Stylable = {
|
||||
style: Properties
|
||||
}
|
||||
style: Properties;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
//
|
||||
|
||||
export { type Theme, breakpoints, useTheme } from "../theme";
|
||||
export { type Theme, breakpoints, useTheme, ThemeProvider, defaultTheme } from "../theme";
|
||||
|
||||
export { useYoshiki, type Stylable, type CssObject } from "./generator";
|
||||
export { useYoshiki, type Stylable } from "./generator";
|
||||
|
||||
@@ -8,7 +8,11 @@ import { YoshikiStyle } from "../type";
|
||||
import { isBreakpoints } from "../utils";
|
||||
import { CSSProperties, useInsertionEffect } from "react";
|
||||
import { useStyleRegistry } from "./registry";
|
||||
import type { CssObject } from ".";
|
||||
import { Properties } from "csstype";
|
||||
|
||||
export type CssObject = {
|
||||
[key in keyof Properties]: YoshikiStyle<Properties[key]>;
|
||||
};
|
||||
|
||||
const generateAtomicCss = <Property extends number | boolean | string | undefined>(
|
||||
key: string,
|
||||
|
||||
@@ -3,13 +3,7 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
//
|
||||
|
||||
import { Properties } from "csstype";
|
||||
import { CSSProperties } from "react";
|
||||
import { YoshikiStyle } from "../type";
|
||||
|
||||
export type CssObject = {
|
||||
[key in keyof Properties]: YoshikiStyle<Properties[key]>;
|
||||
};
|
||||
|
||||
export type Stylable = {
|
||||
className?: string;
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
//
|
||||
|
||||
import { createContext, ReactNode, useContext } from "react";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface Theme {};
|
||||
export interface Theme {}
|
||||
|
||||
export const breakpoints = {
|
||||
xs: 0,
|
||||
@@ -12,8 +14,12 @@ export const breakpoints = {
|
||||
md: 900,
|
||||
lg: 1200,
|
||||
xl: 1600,
|
||||
}
|
||||
|
||||
export const useTheme = () => {
|
||||
return {} as Theme;
|
||||
};
|
||||
|
||||
const ThemeContext = createContext({});
|
||||
|
||||
export const useTheme = () => useContext(ThemeContext);
|
||||
|
||||
export const ThemeProvider = ({ theme, children }: { theme: Theme; children?: ReactNode }) => {
|
||||
return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;
|
||||
};
|
||||
|
||||
6
packages/yoshiki/src/web.ts
Normal file
6
packages/yoshiki/src/web.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
//
|
||||
// Copyright (c) Zoe Roux and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
//
|
||||
|
||||
export * from "./index";
|
||||
@@ -4,7 +4,6 @@
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"noEmit": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
@@ -16,8 +15,9 @@
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-native",
|
||||
"incremental": true,
|
||||
"rootDir": "src/"
|
||||
"rootDir": "src/",
|
||||
"outDir": "dist/"
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx"],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user