React, Next.js環境下で『UIコンポーネントライブラリを構築するための』自作フレームワークです。
MUIライクにスタイリングを行うことを目的としており、下記のような記述でUIを構築する仕組みを提供しています。
※下記のようなコンポーネント群そのものを提供しているわけではないことにご注意ください
<Box w={{ sm: "100px", md: "200px"}} py={ 4 } mb={ 6 }>
デザイン設計はAtomic Designを前提としており、デモとしてコンポーネント設計の参考になるよう、いくつかサンプルのUIを作成しています。
npmパッケージとしてUIライブラリを提供しているわけではなく、単なる設計思想です。
そのため、みなさまがYUISをもとにオリジナルのUIライブラリを構築することを前提としています。
サンプルページは下記リンクからご覧いただけます。
サンプルページ: https://yuis.vercel.app/
本リポジトリのsrc配下を丸ごとコピーしてソース群と置換していただければOKです。
下記コマンドを実行してプロジェクトを立ち上げます。
{app-name}
部分は自由に入力してください。
npx create-next-app {app-name} --use-npm --ts
その後、pages, stylesを削除し本リポジトリのsrcを移植します。
下記コマンドを実行し、ふたつのパッケージをインストールします。
npm i @emotion/react @emotion/styled
import文で絶対パスを利用するため、tsconfig.json
に下記を追記します。
{
"compilerOptions": {
// 省略
"baseUrl": "./src",
"paths": {
"@/*": ["./*"]
}
},
// 省略
}
以上です。
基本的にはあらかじめ定義されたMixinや型を利用し、独自にコンポーネントを定義していきます。
冒頭にあったような、w
というpropsをもってwidth
を指定できる機能を持った<Box>
コンポーネントを作成したい場合。
import styled from "@emotion/styled";
import { LayoutProps, layoutMixin } from "@/styles/mixins/layout";
export type BoxStyleProps = Partial<LayoutProps>
export const BoxStyled = styled.div<BoxStyleProps>(
layoutMixin
);
export type BoxProps = BoxStyleProps & {
children?: React.ReactNode;
};
export const Box = (props: BoxProps) => {
return <BoxStyled {...props} />;
};
layoutMixin
にはw
やh
などのレイアウトに関するCSS Mixinが定義されており、それに必要な型がLayoutProps
です。
それらを使いstyled-componentsを生成することで、簡単にpropsによりスタイリングが可能なコンポーネントになります。
あとはこれを応用していくだけで、たとえばborderも与えられるようにしたい場合はborderMixin
とそれに対応するBorderProps
をimportし、追加で与えればよいだけです。
上記で作成した<Box>
はこのように扱うことができます。
<Box w="100px" maxW="800px" h="180px" >
{contents}
</Box>
これらに必要なものはspaceMixin
にまとめられています。SpaceProps
を合わせてstyled-componentsに渡します。
import styled from "@emotion/styled";
import { SpaceProps, spaceMixin } from "@/styles/mixins/space";
export type BoxStyleProps = Partial<SpaceProps>;
export const BoxStyled = styled.div<BoxStyleProps>(spaceMixin);
export type BoxProps = BoxStyleProps & {
children?: React.ReactNode;
};
export const Box = (props: BoxProps) => {
return <BoxStyled {...props} />;
};
使用時、spaceに関しては特殊な指定ができます。
<Box py="20px" mb={4} >
py
ではpadding-top
,padding-bottom
に20pxを指定しています。
mb
ではmargin-bottom
に24pxを指定しています。
24という値はnumber型で渡された4
を6倍した処理結果であり、6倍という数値はspace.ts
内に定義してあります。
渡された値がstring型かnumber型かで判別し、number型であれば6倍した数値をpxとし、CSSに変換されます。
基本設計として6の倍数で余白を取るようにしているため、それを改修したい場合はspace.ts
内の値を変更することでカスタマイズすることができます。
オブジェクトで値を渡すことでスマホ用とPC用のスタイルを一度に指定することができます。 デフォルトではブレイクポイントは900pxに指定されています。
<Box w={{ sm: "100px", md: "200px"}} >
{contents}
</Box>
<Box p={{ sm: 4, md: 8}} >
{contents}
</Box>
<Box m={{ sm: "15px", md: "30px"}} >
{contents}
</Box>
項目を増やすにはtypes/responsive.d.ts
に追加します。
たとえば、lg
などを加えてさらに大きなディスプレイに対応したい場合などです。
// w, h, などのCSS用propsが受け付けるレスポンシブ用のオブジェクトの型
export type BreakPointProps = {
sm?: CSS.Properties;
md?: CSS.Properties;
};
ブレイクポイントの数値を変更したい場合はtheme/settings/breakpoints.ts
を変更します。
import { css } from "@emotion/react";
export const breakpoints = {
sm: (sm: any) =>
sm != null && css({ "@media (max-width: 899px)": sm }),
md: (md: any) => md != null && css({ "@media (min-width: 899px)": md }),
};
@media (max-width: 899px)
の数値を変更すればブレイクポイントが変わります。
styled-components内では引数としてthemeを取ることができます。
最大の注意点は、theme.ts
をimportしてはいけないという点です。
styled()
の中でthemeを受け取ると自動的にグローバルに定義されているthemeから値を参照できるように、すでに型定義と_app.tsx
での読み込みを行なっています。
Typographyを例に見ます。
export const TypographyStyled = styled.span<TypographyProps>(
({ theme }) => `color: ${theme.color.typography};`,
)
こうすることでTypographyはデフォルトでtheme.color.typography
に定義されたカラーコードが代入され、テーマ切り替えを行なった際も切り替え先のカラーコードに動的に変更されます。
前述したように、このときtheme.ts
をimportする必要はありません。
if文やswitch文と組み合わせることで、propsによるテーマカラーの出し分けも実装できます。
theme/theme.ts
にまとめられています。
各設定ファイルはtheme/settings
配下にあるのでそこで値を追加・変更することができます。
import { palette, nightPalette } from "./settings/palettes";
import { fonts } from "./settings/fonts";
import { breakpoints } from "./settings/breakpoints";
import { spacings } from "./settings/spacings";
export const theme = {
color: palette,
font: fonts,
breakpoint: breakpoints,
spacing: spacings,
};
export const nightTheme = {
color: nightPalette,
font: fonts,
breakpoint: breakpoints,
spacing: spacings,
};
サンプルで用意しているThemeToggle.tsx
を参考にしてください。
useContextを利用し、グローバルステートを切り替えることで<ThemeProvider>
に渡すthemeを切り替えています。
import { Box } from "@/components/atoms/Box/Box";
import { Toggle } from "@/components/molecules/Toggle/Toggle";
import { ThemeContext } from "@/lib/store/theme";
import { useContext } from "react";
export const ThemeToggle = () => {
const { isNightMode, setIsNightMode } = useContext(ThemeContext);
return (
<Box onClick={() => setIsNightMode(!isNightMode)}>
<Toggle />
</Box>
);
};
<ThemeProvider>
の定義元は_app.tsx
になります。
下記で語っているのでコンポーネント設計の参考になれば幸いです。