Open17

Park UIでSlot Recipeを使用しているコンポーネントについて調べる

けけずんけけずん

Park UIのSlot Recipeを使用しているコンポーネント、主にFieldコンポーネントを使う際に混乱した部分を整理する。

けけずんけけずん

Park UIのFieldコンポーネントはField.Rootの中にField.LabelField.Inputなどを記述することでラベルと入力欄を紐づけることができる。また、requiredやinvalidなどのプロパティをFiled.Rootのpropsに指定することで、Field.Rootの子要素にその状態を伝播することができる。

https://park-ui.com/react/docs/components/field

なお、この挙動自体はPark UIではなく、大元のArk UIの方で実装されている。

https://github.com/chakra-ui/ark/blob/main/packages/react/src/components/field/use-field.ts

けけずんけけずん

まず、Fieldコンポーネントに自前で用意したスタイルをあてこむ際に若干混乱した。park-ui/cliを用いて追加したFieldコンポーネントを見てみる。

けけずんけけずん

createStyleContext関数は引数としてpandaのrecipeを受け取り、withRootProvider、withProvider、withContextという3つの関数を返す。
https://github.com/cschroeter/park-ui/blob/main/components/react/src/components/ui/styled/utils/create-style-context.tsx

また、createStyleContext関数はジェネリック型としてRを宣言し、recipeの型をRとして扱う。recipeおよび型RはcreateStyleContext関数内で定義される各関数で使用される。

けけずんけけずん

createStyleContext関数ではreactのContextが作成され、withProvider関数が返すコンポーネントをProviderでラップしている。

https://github.com/cschroeter/park-ui/blob/53235fbaf69e5f6b8a1bb8d2518a5816475a1f3c/components/react/src/components/ui/styled/utils/create-style-context.tsx#L25
https://github.com/cschroeter/park-ui/blob/53235fbaf69e5f6b8a1bb8d2518a5816475a1f3c/components/react/src/components/ui/styled/utils/create-style-context.tsx#L58-L64

これによりwithContext関数が返すコンポーネントが、StyleContextの値にアクセスすることができるようになる。StyleContextの中に入る具体的な値はpandaのrecipe関数の返り値である。

けけずんけけずん

withProvider関数は引数としてComponent、slot、optionsを受け取る。Componentはpandaのrecipeを適用する対象となるコンポーネント、slotはrecipeのslotsに定義された任意のslotを受け取る。optionsはforwardPropsというフィールドを持ったオブジェクトで、これによりスタイリング対象となる要素ではなく、その子要素にpropsを伝播させることができる。

返り値の型はForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>となっており、forwardRef関数でラップされたrefを受け取るコンポーネントであることを示す。

型引数のTはHTMLDivElementなどDOMノードの型を指定でき、forwardRef関数の型引数として使用される。型引数のPはwithProvider関数が返すコンポーネントが受け取るpropsの型を示す。

https://github.com/cschroeter/park-ui/blob/53235fbaf69e5f6b8a1bb8d2518a5816475a1f3c/components/react/src/components/ui/styled/utils/create-style-context.tsx#L41-L45

けけずんけけずん

StyledComponentはpanda codegen実行時に生成されるstyled-systemフォルダ(デフォルトの名前)からexportされたstyled関数を用いてComponentをラップしている。おそらくpandaのpresetやconfigに定義した型を参照できるようにするために、withProvider関数の引数として渡されたComponentをstyled関数でラップしているのかもしれない。

https://github.com/cschroeter/park-ui/blob/53235fbaf69e5f6b8a1bb8d2518a5816475a1f3c/components/react/src/components/ui/styled/utils/create-style-context.tsx#L46-L52

けけずんけけずん

variantProps、otherPropsはrecipeのsplitVariantPropsの返り値として定義される。splitVariantPropsにコンポーネントのpropsを渡すことで、recipeで定義したvariant用propsと、それ以外のpropsとで区別している。

slotStylesはvariantPropsを引数としたrecipeを関数として実行して返り値を受け取る。slotStylesにrecipeのslotsで定義したslot名を指定してアクセスすることで、recipeで定義したslot名に対応するクラスを参照することができる。

https://github.com/cschroeter/park-ui/blob/53235fbaf69e5f6b8a1bb8d2518a5816475a1f3c/components/react/src/components/ui/styled/utils/create-style-context.tsx#L54-L55

けけずんけけずん

createStyleContext関数の最初の方で作成したStyleContextを用いてStyledComponentをラップしたコンポーネントを返している。StyleContext.ProviderのvalueにはslotStylesを代入し、Context内のコンポーネントが共通のrecipeを参照できるようにしている。

StyledComponentにはrecipeのsplitVariantProps関数を実行して取り出したotherPropsと、ref、classNameをpropsとして渡している。classNameはpandaのcx関数を使用してslotStylesの特定のslot名に該当するクラスと、props.classNameとして渡されたクラスをマージした値を渡している。

https://github.com/cschroeter/park-ui/blob/53235fbaf69e5f6b8a1bb8d2518a5816475a1f3c/components/react/src/components/ui/styled/utils/create-style-context.tsx#L57-L65

けけずんけけずん

ここまで読んでみて、createStyleContext関数はSlot Recipeで定義したスタイルを適用した状態のコンポーネントを返すために実装された関数であることが分かる。refやotherPropsをStyledComponentにpropsとして渡しているのはArk UIの挙動を実現するためで、Park UIが独自で挙動を追加したいからというわけではなさそう。