From d967add90acbdb3080af5eee647b23eeb00955a4 Mon Sep 17 00:00:00 2001 From: Navin Moorthy Date: Tue, 26 Jul 2022 20:00:12 +0530 Subject: [PATCH 1/6] =?UTF-8?q?refactor(badge):=20=E2=99=BB=EF=B8=8F=20mov?= =?UTF-8?q?e=20to=20ariakit=20system?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/src/AppRoot.tsx | 2 +- .../src/modules/primitives/BadgeScreen.tsx | 66 ++--- src/components/badge-new/Badge.tsx | 18 ++ src/components/badge-new/BadgeProps.tsx | 67 +++++ src/components/badge-new/BadgeText.tsx | 47 ++++ src/components/badge-new/BadgeUIState.ts | 30 ++ src/components/badge-new/BadgeWrapper.tsx | 48 ++++ src/components/badge-new/index.ts | 5 + src/components/index.ts | 1 + src/primitives/box/Box.tsx | 25 +- src/primitives/text/Text.tsx | 25 +- src/theme/index.ts | 1 + src/utils/global-types.ts | 95 +++++++ src/utils/system.tsx | 261 ++++++++++++++++++ 14 files changed, 629 insertions(+), 62 deletions(-) create mode 100644 src/components/badge-new/Badge.tsx create mode 100644 src/components/badge-new/BadgeProps.tsx create mode 100644 src/components/badge-new/BadgeText.tsx create mode 100644 src/components/badge-new/BadgeUIState.ts create mode 100644 src/components/badge-new/BadgeWrapper.tsx create mode 100644 src/components/badge-new/index.ts create mode 100644 src/utils/global-types.ts create mode 100644 src/utils/system.tsx diff --git a/example/src/AppRoot.tsx b/example/src/AppRoot.tsx index cd083e6a..02e64eb6 100644 --- a/example/src/AppRoot.tsx +++ b/example/src/AppRoot.tsx @@ -24,7 +24,7 @@ const Drawer = createDrawerNavigator(); const AppRoot = () => { return ( - + { - - Scheduled - - - Assigned - - } />} - > - On Progress - - - Confirmed - - } />} > - Cancelled - - } />} - > - Completed - - - */} + - Done + <>Badge + + + + + Scheduled ); diff --git a/src/components/badge-new/Badge.tsx b/src/components/badge-new/Badge.tsx new file mode 100644 index 00000000..b7c18925 --- /dev/null +++ b/src/components/badge-new/Badge.tsx @@ -0,0 +1,18 @@ +import * as React from "react"; +import { View } from "react-native"; + +import { BadgeNewProps, useBadgeProps } from "./BadgeProps"; +import { BadgeText } from "./BadgeText"; +import { BadgeWrapper } from "./BadgeWrapper"; + +export const BadgeNew = React.forwardRef((props, ref) => { + const { wrapperProps, textProps } = useBadgeProps(props); + + return ( + + + + ); +}); + +BadgeNew.displayName = "BadgeNew"; diff --git a/src/components/badge-new/BadgeProps.tsx b/src/components/badge-new/BadgeProps.tsx new file mode 100644 index 00000000..c5f10b30 --- /dev/null +++ b/src/components/badge-new/BadgeProps.tsx @@ -0,0 +1,67 @@ +import { useMemo } from "react"; + +import { getComponentProps, RenderProp } from "../../utils/system"; + +import { BadgeTextProps } from "./BadgeText"; +import { + BadgeUIState, + BadgeUIStateProps, + useBadgeUIState, +} from "./BadgeUIState"; +import { BadgeWrapperProps } from "./BadgeWrapper"; + +const componentMap = { + BadgeWrapper: "wrapperProps", + BadgeText: "textProps", +}; + +export function useBadgeProps(props: BadgeNewProps): BadgePropsReturn { + let { size, themeColor, variant, children, ...restProps } = props; + + const uiState = useBadgeUIState({ + size, + themeColor, + variant, + }); + let uiProps: BadgeUIProps = useMemo(() => ({ ...uiState }), [uiState]); + + const { componentProps, finalChildren } = getComponentProps( + componentMap, + children, + uiProps, + ); + const _finalChildren = componentProps?.textProps?.children || finalChildren; + const wrapperProps: BadgeWrapperProps = useMemo( + () => ({ + ...uiProps, + ...restProps, + ...componentProps.wrapperProps, + }), + [componentProps.wrapperProps, restProps, uiProps], + ); + + console.log("%c_finalChildren", "color: #0088cc", _finalChildren); + const textProps: BadgeTextProps = useMemo( + () => ({ + ...uiProps, + ...componentProps.textProps, + children: _finalChildren, + }), + [componentProps.textProps, uiProps, _finalChildren], + ); + + return { uiProps, wrapperProps, textProps }; +} + +export type BadgeUIProps = BadgeUIState & {}; + +export type BadgeNewProps = Omit & + BadgeUIStateProps & { + children?: RenderProp; + }; + +export type BadgePropsReturn = { + uiProps: BadgeUIProps; + wrapperProps: BadgeWrapperProps; + textProps: BadgeTextProps; +}; diff --git a/src/components/badge-new/BadgeText.tsx b/src/components/badge-new/BadgeText.tsx new file mode 100644 index 00000000..f778640d --- /dev/null +++ b/src/components/badge-new/BadgeText.tsx @@ -0,0 +1,47 @@ +import { Text, TextOptions, useText } from "../../primitives/text"; +import { useTheme } from "../../theme"; +import { cx, styleAdapter } from "../../utils"; +import { + As, + createComponentType, + createElement, + createHook, + Props, +} from "../../utils/system"; + +import { BadgeUIProps } from "./BadgeProps"; + +export const useBadgeText = createHook( + ({ size, themeColor, variant, ...props }) => { + const tailwind = useTheme(); + const badgeStyles = useTheme("badge"); + const style = [ + tailwind.style( + cx( + size ? badgeStyles.size[size]?.text : "", + themeColor && variant + ? badgeStyles.themeColor[themeColor]?.[variant]?.text + : "", + ), + ), + styleAdapter(props.style), + ]; + + props = useText({ ...props, style }); + + return props; + }, +); + +export const BadgeText = createComponentType(props => { + const htmlProps = useBadgeText(props); + + return createElement(Text, htmlProps); +}, "BadgeText"); + +export type BadgeTextOptions = TextOptions & + Partial & {}; + +export type BadgeTextProps = Props< + BadgeTextOptions +>; diff --git a/src/components/badge-new/BadgeUIState.ts b/src/components/badge-new/BadgeUIState.ts new file mode 100644 index 00000000..c5b5ac9c --- /dev/null +++ b/src/components/badge-new/BadgeUIState.ts @@ -0,0 +1,30 @@ +export const useBadgeUIState = (props: BadgeUIStateProps): BadgeUIState => { + const { size = "md", themeColor = "base", variant = "solid" } = props; + + return { size, themeColor, variant }; +}; + +export type BadgeUIState = { + /** + * How large should the badge be? + * + * @default md + */ + size: keyof AdaptUI.GetThemeValue<"badge", "size">; + /** + * How the badge should be themed? + * + * @default base + */ + themeColor: keyof AdaptUI.GetThemeValue<"badge", "themeColor">; + /** + * How the badge should look? + * + * @default solid + */ + variant: keyof AdaptUI.GetThemeValue<"badge", "themeColor", "base">; +}; + +export type BadgeUIStateProps = Partial< + Pick +> & {}; diff --git a/src/components/badge-new/BadgeWrapper.tsx b/src/components/badge-new/BadgeWrapper.tsx new file mode 100644 index 00000000..422ae3a1 --- /dev/null +++ b/src/components/badge-new/BadgeWrapper.tsx @@ -0,0 +1,48 @@ +import { Box, BoxOptions, useBox } from "../../primitives/box"; +import { useTheme } from "../../theme"; +import { cx, styleAdapter } from "../../utils"; +import { + As, + createComponentType, + createElement, + createHook, + Props, +} from "../../utils/system"; + +import { BadgeUIProps } from "./BadgeProps"; + +export const useBadgeWrapper = createHook( + ({ size, themeColor, variant, ...props }) => { + const tailwind = useTheme(); + const badgeStyles = useTheme("badge"); + const style = [ + tailwind.style( + cx( + badgeStyles.baseContainer, + size ? badgeStyles.size[size]?.container : "", + themeColor && variant + ? badgeStyles.themeColor[themeColor]?.[variant]?.container + : "", + ), + ), + styleAdapter(props.style), + ]; + + props = useBox({ ...props, style }); + + return props; + }, +); + +export const BadgeWrapper = createComponentType(props => { + const htmlProps = useBadgeWrapper(props); + + return createElement(Box, htmlProps); +}, "BadgeWrapper"); + +export type BadgeWrapperOptions = BoxOptions & + Partial & {}; + +export type BadgeWrapperProps = Props< + BadgeWrapperOptions +>; diff --git a/src/components/badge-new/index.ts b/src/components/badge-new/index.ts new file mode 100644 index 00000000..a15db693 --- /dev/null +++ b/src/components/badge-new/index.ts @@ -0,0 +1,5 @@ +export * from "./Badge"; +export * from "./BadgeProps"; +export * from "./BadgeText"; +export * from "./BadgeUIState"; +export * from "./BadgeWrapper"; diff --git a/src/components/index.ts b/src/components/index.ts index 14989347..f1d53ee9 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,6 +1,7 @@ export * from "./avatar"; export * from "./avatar-group"; export * from "./badge"; +export * from "./badge-new"; export * from "./button"; export * from "./checkbox"; export * from "./circular-progress"; diff --git a/src/primitives/box/Box.tsx b/src/primitives/box/Box.tsx index d97355a5..a898b0e7 100644 --- a/src/primitives/box/Box.tsx +++ b/src/primitives/box/Box.tsx @@ -1,10 +1,23 @@ -import { View, ViewProps as RNViewProps } from "react-native"; +import { View } from "react-native"; -import { createComponent } from "../../utils/createComponent"; -import type { Dict } from "../../utils/types"; +import { + As, + createComponent, + createElement, + createHook, + Options, + Props, +} from "../../utils/system"; -export type LibraryBoxProps = Dict; +export const useBox = createHook(props => { + return props; +}); -export type BoxProps = RNViewProps & LibraryBoxProps; +export const Box = createComponent(props => { + const htmlProps = useBox(props); + return createElement(View, htmlProps); +}); -export const Box = createComponent(View, { shouldMemo: true }); +export type BoxOptions = Options; + +export type BoxProps = Props>; diff --git a/src/primitives/text/Text.tsx b/src/primitives/text/Text.tsx index 8eca3051..24bb8f5d 100644 --- a/src/primitives/text/Text.tsx +++ b/src/primitives/text/Text.tsx @@ -1,10 +1,23 @@ -import { Text as RNText, TextProps as RNTextProps } from "react-native"; +import { Text as RNText } from "react-native"; -import { createComponent } from "../../utils/createComponent"; -import type { Dict } from "../../utils/types"; +import { + As, + createComponent, + createElement, + createHook, + Options, + Props, +} from "../../utils/system"; -export type LibraryTextProps = Dict; +export const useText = createHook(props => { + return props; +}); -export type TextProps = RNTextProps & LibraryTextProps; +export const Text = createComponent(props => { + const htmlProps = useText(props); + return createElement(RNText, htmlProps); +}); -export const Text = createComponent(RNText, { shouldMemo: true }); +export type TextOptions = Options; + +export type TextProps = Props>; diff --git a/src/theme/index.ts b/src/theme/index.ts index 2edd280c..69db159d 100644 --- a/src/theme/index.ts +++ b/src/theme/index.ts @@ -1 +1,2 @@ export * from "./context"; +export * from "./defaultTheme"; diff --git a/src/utils/global-types.ts b/src/utils/global-types.ts new file mode 100644 index 00000000..cd9f08dd --- /dev/null +++ b/src/utils/global-types.ts @@ -0,0 +1,95 @@ +import { DefaultTheme } from "../theme"; + +// https://stackoverflow.com/questions/60795256/typescript-type-merging +// https://dev.to/svehla/typescript-how-to-deep-merge-170c + +/** + * Take two objects T and U and create the new one with uniq keys for T a U objectI + * helper generic for `DeepMergeTwoTypes` + */ +type GetObjDifferentKeys = Omit & Omit; + +/** + * Take two objects T and U and create the new one with the same objects keys + * helper generic for `DeepMergeTwoTypes` + */ +type GetObjSameKeys = Omit>; + +type Merge = + // non shared keys are optional + Partial> & { + // shared keys are recursively resolved by `DeepMergeTwoTypes<...>` + [K in keyof GetObjSameKeys]: DeepMerge; + }; + +// it merge 2 static types and try to avoid of unnecessary options (`'`) +/** + * @template T source object + * @template U target object + * + * @description Deep merge two theme objects + */ +export type DeepMerge = + // check if generic types are arrays and unwrap it and do the recursion + [T, U] extends [(infer TItem)[], (infer UItem)[]] + ? DeepMerge[] + : // check if generic types are objects + [T, U] extends [{ [key: string]: unknown }, { [key: string]: unknown }] + ? Merge + : [T, U] extends [ + { [key: string]: unknown } | undefined, + { [key: string]: unknown } | undefined, + ] + ? Merge, NonNullable> | undefined + : T | U; + +interface _ComponentDefaultTheme { + components: DefaultTheme; +} + +declare const _brand: unique symbol; + +declare global { + namespace AdaptUI { + export interface Theme extends _ComponentDefaultTheme {} + /** + * @template T default theme + * @template U user theme + * + * @description Safely Deep merges default theme with user theme + */ + export type MergeTheme = DeepMerge< + T, + U extends { [x: string]: any } ? U : {} + >; + + type Brand = Type & { [_brand]: Name }; + + type Comps = AdaptUI.Theme["components"]; + + /** + * @template C component name + * @template K theme key + * @template L theme key + * @template M theme key + * @template N theme key + */ + export type GetThemeValue< + C extends keyof Comps, + K extends keyof Comps[C] = Brand, + L extends keyof Comps[C][K] = Brand, + M extends keyof Comps[C][K][L] = Brand, + N extends keyof Comps[C][K][L][M] = Brand, + > = [C, K, L, M, N] extends [string, Brand, Brand, Brand, Brand] + ? Comps[C] + : [C, K, L, M, N] extends [string, string, Brand, Brand, Brand] + ? Comps[C][K] + : [C, K, L, M, N] extends [string, string, string, Brand, Brand] + ? Comps[C][K][L] + : [C, K, L, M, N] extends [string, string, string, string, Brand] + ? Comps[C][K][L][M] + : [C, K, L, M, N] extends [string, string, string, string, string] + ? Comps[C][K][L][M][N] + : never; + } +} diff --git a/src/utils/system.tsx b/src/utils/system.tsx new file mode 100644 index 00000000..796db6e1 --- /dev/null +++ b/src/utils/system.tsx @@ -0,0 +1,261 @@ +import React, { + ComponentPropsWithRef, + ElementType, + forwardRef, + HTMLAttributes, + ReactElement, + ReactNode, + RefAttributes, +} from "react"; + +import { Dict } from "./types"; + +export function createComponent( + render: (props: Props) => ReactElement, +) { + const Role = (props: Props, ref: React.Ref) => + render({ ref, ...props }); + return forwardRef(Role) as unknown as Component; +} + +export function createComponentType( + render: (props: Props) => React.ReactElement, + type: string, +) { + const Role = (props: Props, ref: React.Ref) => + render({ ref, ...props, __TYPE__: type }); + + const Component = forwardRef(Role) as unknown as ComponentProps; + Component.defaultProps = { __TYPE__: type }; + + return Component; +} + +export function createElement(Type: ElementType, props: HTMLProps) { + const { as: As, wrapElement, ...rest } = props; + let element: ReactElement; + if (As && typeof As !== "string") { + element = ; + } else if (isRenderProp(props.children)) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { children, ...otherProps } = rest; + element = props.children(otherProps) as ReactElement; + } else if (As) { + element = ; + } else { + element = ; + } + if (wrapElement) { + return wrapElement(element); + } + return element; +} + +function isRenderProp(children: any): children is AriakitRenderProp { + return typeof children === "function"; +} + +export function createHook( + useProps: (props: Props) => HTMLProps, +) { + const useRole = (props: Props = {} as Props) => { + const htmlProps = useProps(props); + const copy = {} as typeof htmlProps; + for (const prop in htmlProps) { + if (hasOwnProperty(htmlProps, prop) && htmlProps[prop] !== undefined) { + copy[prop] = htmlProps[prop]; + } + } + return copy; + }; + return useRole as Hook; +} + +/** + * Checks whether `prop` is an own property of `obj` or not. + */ +export function hasOwnProperty( + object: T, + prop: keyof any, +): prop is keyof T { + return Object.prototype.hasOwnProperty.call(object, prop); +} + +export type ComponentProps = Component & { + defaultProps?: { __TYPE__: string }; +}; + +export type ComponentOptions = Options & { + __TYPE__?: string; +}; + +/** + * A component hook that supports the `as` prop and the `children` prop as a + * function. + * @template O Options + * @example + * type ButtonHook = Hook>; + */ +export type Hook = { + >( + props?: Omit & Omit>, keyof O> & Options, + ): HTMLProps>; + displayName?: string; +}; + +/** + * Props with the `as` prop. + * @template T The `as` prop + * @example + * type ButtonOptions = Options<"button">; + */ +export type Options = { as?: T }; + +/** + * Options & HTMLProps + * @template O Options + * @example + * type ButtonProps = Props>; + */ +export type Props = O & HTMLProps; + +export type Component = { + ( + props: Omit & + Omit>, keyof O> & + Required>, + ): JSX.Element | null; + (props: Props): JSX.Element | null; + displayName?: string; +}; + +/** + * The `as` prop. + * @template P Props + */ +export type As

= ElementType

; + +/** + * Props that automatically includes HTML props based on the `as` prop. + * @template O Options + * @example + * type ButtonHTMLProps = HTMLProps>; + */ +export type HTMLProps = { + wrapElement?: WrapElement; + children?: Children; + [index: `data-${string}`]: unknown; +} & Omit>, keyof O | "children">; + +/** + * The `wrapElement` prop. + */ +export type WrapElement = (element: ReactElement) => ReactElement; + +/** + * The `children` prop that supports a function. + * @template T Element type. + */ +export type Children = + | ReactNode + | AriakitRenderProp & RefAttributes>; + +/** + * Render prop type. + * @template P Props + * @example + * const children: RenderProp = (props) =>

; + */ +export type AriakitRenderProp

= (props: P) => ReactNode; + +/** + * Any object. + */ +export type AnyObject = Record; + +// Function assertions +export function isFunction(value: any): value is Function { + return typeof value === "function"; +} + +export function runIfFnChildren( + valueOrFn: T, + ...args: U[] +): React.ReactNode | React.ReactNode[] { + if (!isFunction(valueOrFn)) { + return valueOrFn as unknown as React.ReactNode; + } + + if (valueOrFn(...args).type.toString() !== "Symbol(react.fragment)") { + return [valueOrFn(...args)]; + } + + return valueOrFn(...args).props.children; +} + +/** + * Gets only the valid children of a component, + * and ignores any nullish or falsy child. + * + * @param children the children + */ +export function getValidChildren( + children: React.ReactNode | React.ReactNode[], +) { + return React.Children.toArray(children as React.ReactNode).filter(child => + React.isValidElement(child), + ); +} + +export const getComponentProps = ( + componentMaps: Dict, + children: RenderProp, + props: P, +) => { + const normalizedChildren = runIfFnChildren(children, props); + const validChildren = getValidChildren(normalizedChildren); + const componentProps: AnyObject = {}; + const finalChildren: React.ReactNode[] = []; + + if (validChildren.length > 0) { + validChildren.forEach(function (child) { + // @ts-ignore + if (componentMaps[child?.props?.__TYPE__]) { + // @ts-ignore + componentProps[componentMaps[child?.props?.__TYPE__]] = child.props; + } else { + finalChildren.push(child); + } + }); + } else { + finalChildren.push(normalizedChildren); + } + + return { componentProps, finalChildren }; +}; + +export function runIfFn( + component: RenderProp, + props: T, +): React.ReactNode { + return isFunction(component) ? component({ ...props }) : component; +} + +// Merge library & user prop +export const passProps = ( + component: RenderProp, + stateProps: S, + props?: T, +) => { + return React.isValidElement(component) + ? React.cloneElement(component, { + ...props, + // @ts-ignore + ...component?.props, + }) + : runIfFn(component, { ...stateProps, ...props }); +}; + +export declare type RenderProp = + | React.ReactNode + | AriakitRenderProp; From aaf2871cdc1bfdebbb086cfb41b63b62d37394e1 Mon Sep 17 00:00:00 2001 From: Navin Moorthy Date: Tue, 26 Jul 2022 20:28:25 +0530 Subject: [PATCH 2/6] =?UTF-8?q?refactor(badge):=20=E2=99=BB=EF=B8=8F=20rem?= =?UTF-8?q?ove=20console.logs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/src/modules/primitives/BadgeScreen.tsx | 4 ++-- src/components/badge-new/BadgeProps.tsx | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/example/src/modules/primitives/BadgeScreen.tsx b/example/src/modules/primitives/BadgeScreen.tsx index b9b237f7..fbf7c240 100644 --- a/example/src/modules/primitives/BadgeScreen.tsx +++ b/example/src/modules/primitives/BadgeScreen.tsx @@ -14,13 +14,13 @@ export const BadgeScreen = () => { - {/* Scheduled - */} + ({ ...uiProps, From 1868dd154c0fe7eae61781fee70a7575dde41dcc Mon Sep 17 00:00:00 2001 From: Karthik-B-06 Date: Wed, 27 Jul 2022 12:02:12 +0530 Subject: [PATCH 3/6] refactor(badge): :sparkles: remove __TYPE__ from props --- src/primitives/box/Box.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/primitives/box/Box.tsx b/src/primitives/box/Box.tsx index a898b0e7..6af61d0a 100644 --- a/src/primitives/box/Box.tsx +++ b/src/primitives/box/Box.tsx @@ -2,14 +2,14 @@ import { View } from "react-native"; import { As, + ComponentOptions, createComponent, createElement, createHook, - Options, Props, } from "../../utils/system"; -export const useBox = createHook(props => { +export const useBox = createHook(({ __TYPE__, ...props }) => { return props; }); @@ -18,6 +18,6 @@ export const Box = createComponent(props => { return createElement(View, htmlProps); }); -export type BoxOptions = Options; +export type BoxOptions = ComponentOptions; export type BoxProps = Props>; From 7dbeb47e1d36ddb66dad709943337da12364a916 Mon Sep 17 00:00:00 2001 From: Navin Moorthy Date: Mon, 1 Aug 2022 12:48:56 +0530 Subject: [PATCH 4/6] =?UTF-8?q?refactor(types):=20=F0=9F=8F=B7=EF=B8=8F=20?= =?UTF-8?q?update=20global=20namespace=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/tsconfig.json | 1 + src/components/badge-new/BadgeUIState.ts | 8 ++- src/utils/global-types.ts | 78 +++++++++++------------- tsconfig.json | 4 -- 4 files changed, 43 insertions(+), 48 deletions(-) diff --git a/example/tsconfig.json b/example/tsconfig.json index 84fd5b6a..137260b8 100644 --- a/example/tsconfig.json +++ b/example/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "baseUrl": ".", "paths": { "@adaptui/react-native-tailwind": ["../src/index"] } diff --git a/src/components/badge-new/BadgeUIState.ts b/src/components/badge-new/BadgeUIState.ts index c5b5ac9c..44b884f0 100644 --- a/src/components/badge-new/BadgeUIState.ts +++ b/src/components/badge-new/BadgeUIState.ts @@ -1,3 +1,5 @@ +import { GetThemeValue } from "../../utils/global-types"; + export const useBadgeUIState = (props: BadgeUIStateProps): BadgeUIState => { const { size = "md", themeColor = "base", variant = "solid" } = props; @@ -10,19 +12,19 @@ export type BadgeUIState = { * * @default md */ - size: keyof AdaptUI.GetThemeValue<"badge", "size">; + size: keyof GetThemeValue<"badge", "size">; /** * How the badge should be themed? * * @default base */ - themeColor: keyof AdaptUI.GetThemeValue<"badge", "themeColor">; + themeColor: keyof GetThemeValue<"badge", "themeColor">; /** * How the badge should look? * * @default solid */ - variant: keyof AdaptUI.GetThemeValue<"badge", "themeColor", "base">; + variant: keyof GetThemeValue<"badge", "themeColor", "base">; }; export type BadgeUIStateProps = Partial< diff --git a/src/utils/global-types.ts b/src/utils/global-types.ts index cd9f08dd..31b6c81d 100644 --- a/src/utils/global-types.ts +++ b/src/utils/global-types.ts @@ -49,47 +49,43 @@ interface _ComponentDefaultTheme { declare const _brand: unique symbol; -declare global { - namespace AdaptUI { - export interface Theme extends _ComponentDefaultTheme {} - /** - * @template T default theme - * @template U user theme - * - * @description Safely Deep merges default theme with user theme - */ - export type MergeTheme = DeepMerge< - T, - U extends { [x: string]: any } ? U : {} - >; +export interface Theme extends _ComponentDefaultTheme {} +/** + * @template T default theme + * @template U user theme + * + * @description Safely Deep merges default theme with user theme + */ +export type MergeTheme = DeepMerge< + T, + U extends { [x: string]: any } ? U : {} +>; - type Brand = Type & { [_brand]: Name }; +type Brand = Type & { [_brand]: Name }; - type Comps = AdaptUI.Theme["components"]; +type Comps = Theme["components"]; - /** - * @template C component name - * @template K theme key - * @template L theme key - * @template M theme key - * @template N theme key - */ - export type GetThemeValue< - C extends keyof Comps, - K extends keyof Comps[C] = Brand, - L extends keyof Comps[C][K] = Brand, - M extends keyof Comps[C][K][L] = Brand, - N extends keyof Comps[C][K][L][M] = Brand, - > = [C, K, L, M, N] extends [string, Brand, Brand, Brand, Brand] - ? Comps[C] - : [C, K, L, M, N] extends [string, string, Brand, Brand, Brand] - ? Comps[C][K] - : [C, K, L, M, N] extends [string, string, string, Brand, Brand] - ? Comps[C][K][L] - : [C, K, L, M, N] extends [string, string, string, string, Brand] - ? Comps[C][K][L][M] - : [C, K, L, M, N] extends [string, string, string, string, string] - ? Comps[C][K][L][M][N] - : never; - } -} +/** + * @template C component name + * @template K theme key + * @template L theme key + * @template M theme key + * @template N theme key + */ +export type GetThemeValue< + C extends keyof Comps, + K extends keyof Comps[C] = Brand, + L extends keyof Comps[C][K] = Brand, + M extends keyof Comps[C][K][L] = Brand, + N extends keyof Comps[C][K][L][M] = Brand, +> = [C, K, L, M, N] extends [string, Brand, Brand, Brand, Brand] + ? Comps[C] + : [C, K, L, M, N] extends [string, string, Brand, Brand, Brand] + ? Comps[C][K] + : [C, K, L, M, N] extends [string, string, string, Brand, Brand] + ? Comps[C][K][L] + : [C, K, L, M, N] extends [string, string, string, string, Brand] + ? Comps[C][K][L][M] + : [C, K, L, M, N] extends [string, string, string, string, string] + ? Comps[C][K][L][M][N] + : never; diff --git a/tsconfig.json b/tsconfig.json index ecd929c0..2c998b00 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,5 @@ { "compilerOptions": { - "baseUrl": "./", - "paths": { - "@adaptui/react-native-tailwind": ["./src/index"] - }, "target": "esnext", "lib": ["esnext"], "module": "esnext", From f8b6b2fbd7936f7d2076740ef68700245a1e18c4 Mon Sep 17 00:00:00 2001 From: Navin Moorthy Date: Mon, 1 Aug 2022 12:50:55 +0530 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20=E2=9C=A8=20add=20classnames=20supp?= =?UTF-8?q?ort=20with=20tailwind.style?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/modules/primitives/BadgeScreen.tsx | 26 +++---------- src/components/badge-new/BadgeText.tsx | 39 +++++++++---------- src/components/badge-new/BadgeWrapper.tsx | 27 ++++++------- src/primitives/box/Box.tsx | 17 ++++++-- src/primitives/text/BoxedText.tsx | 26 +++++++++++++ src/primitives/text/index.ts | 1 + 6 files changed, 75 insertions(+), 61 deletions(-) create mode 100644 src/primitives/text/BoxedText.tsx diff --git a/example/src/modules/primitives/BadgeScreen.tsx b/example/src/modules/primitives/BadgeScreen.tsx index fbf7c240..5d37970c 100644 --- a/example/src/modules/primitives/BadgeScreen.tsx +++ b/example/src/modules/primitives/BadgeScreen.tsx @@ -1,38 +1,22 @@ import React from "react"; import { - Badge, BadgeNew, BadgeText, BadgeWrapper, Box, - useTheme, } from "@adaptui/react-native-tailwind"; export const BadgeScreen = () => { - const tailwind = useTheme(); return ( - - + + Scheduled - + <>Badge - - + + - - Scheduled - ); }; diff --git a/src/components/badge-new/BadgeText.tsx b/src/components/badge-new/BadgeText.tsx index f778640d..f028b736 100644 --- a/src/components/badge-new/BadgeText.tsx +++ b/src/components/badge-new/BadgeText.tsx @@ -1,6 +1,10 @@ -import { Text, TextOptions, useText } from "../../primitives/text"; +import { + BoxedText, + BoxedTextOptions, + useBoxedText, +} from "../../primitives/text"; import { useTheme } from "../../theme"; -import { cx, styleAdapter } from "../../utils"; +import { cx } from "../../utils"; import { As, createComponentType, @@ -13,21 +17,16 @@ import { BadgeUIProps } from "./BadgeProps"; export const useBadgeText = createHook( ({ size, themeColor, variant, ...props }) => { - const tailwind = useTheme(); const badgeStyles = useTheme("badge"); - const style = [ - tailwind.style( - cx( - size ? badgeStyles.size[size]?.text : "", - themeColor && variant - ? badgeStyles.themeColor[themeColor]?.[variant]?.text - : "", - ), - ), - styleAdapter(props.style), - ]; - - props = useText({ ...props, style }); + const className = cx( + size ? badgeStyles.size[size]?.text : "", + themeColor && variant + ? badgeStyles.themeColor[themeColor]?.[variant]?.text + : "", + props.className, + ); + + props = useBoxedText({ ...props, className }); return props; }, @@ -36,12 +35,12 @@ export const useBadgeText = createHook( export const BadgeText = createComponentType(props => { const htmlProps = useBadgeText(props); - return createElement(Text, htmlProps); + return createElement(BoxedText, htmlProps); }, "BadgeText"); -export type BadgeTextOptions = TextOptions & - Partial & {}; +export type BadgeTextOptions = + BoxedTextOptions & Partial & {}; -export type BadgeTextProps = Props< +export type BadgeTextProps = Props< BadgeTextOptions >; diff --git a/src/components/badge-new/BadgeWrapper.tsx b/src/components/badge-new/BadgeWrapper.tsx index 422ae3a1..8a975722 100644 --- a/src/components/badge-new/BadgeWrapper.tsx +++ b/src/components/badge-new/BadgeWrapper.tsx @@ -1,6 +1,6 @@ import { Box, BoxOptions, useBox } from "../../primitives/box"; import { useTheme } from "../../theme"; -import { cx, styleAdapter } from "../../utils"; +import { cx } from "../../utils"; import { As, createComponentType, @@ -13,22 +13,17 @@ import { BadgeUIProps } from "./BadgeProps"; export const useBadgeWrapper = createHook( ({ size, themeColor, variant, ...props }) => { - const tailwind = useTheme(); const badgeStyles = useTheme("badge"); - const style = [ - tailwind.style( - cx( - badgeStyles.baseContainer, - size ? badgeStyles.size[size]?.container : "", - themeColor && variant - ? badgeStyles.themeColor[themeColor]?.[variant]?.container - : "", - ), - ), - styleAdapter(props.style), - ]; - - props = useBox({ ...props, style }); + const className = cx( + badgeStyles.baseContainer, + size ? badgeStyles.size[size]?.container : "", + themeColor && variant + ? badgeStyles.themeColor[themeColor]?.[variant]?.container + : "", + props.className, + ); + + props = useBox({ ...props, className }); return props; }, diff --git a/src/primitives/box/Box.tsx b/src/primitives/box/Box.tsx index 6af61d0a..976597a8 100644 --- a/src/primitives/box/Box.tsx +++ b/src/primitives/box/Box.tsx @@ -1,5 +1,7 @@ import { View } from "react-native"; +import { useTheme } from "../../theme"; +import { styleAdapter } from "../../utils"; import { As, ComponentOptions, @@ -9,15 +11,22 @@ import { Props, } from "../../utils/system"; -export const useBox = createHook(({ __TYPE__, ...props }) => { - return props; -}); +export const useBox = createHook( + ({ __TYPE__, className, ...props }) => { + const tailwind = useTheme(); + const style = [tailwind.style(className), styleAdapter(props.style)]; + + return { ...props, style }; + }, +); export const Box = createComponent(props => { const htmlProps = useBox(props); return createElement(View, htmlProps); }); -export type BoxOptions = ComponentOptions; +export type BoxOptions = ComponentOptions & { + className?: string; +}; export type BoxProps = Props>; diff --git a/src/primitives/text/BoxedText.tsx b/src/primitives/text/BoxedText.tsx new file mode 100644 index 00000000..6113ac56 --- /dev/null +++ b/src/primitives/text/BoxedText.tsx @@ -0,0 +1,26 @@ +import { Text as RNText } from "react-native"; + +import { + As, + createComponent, + createElement, + createHook, + Props, +} from "../../utils/system"; +import { BoxOptions, useBox } from "../box"; + +export const useBoxedText = createHook(props => { + props = useBox(props); + return props; +}); + +export const BoxedText = createComponent(props => { + const htmlProps = useBoxedText(props); + return createElement(RNText, htmlProps); +}); + +export type BoxedTextOptions = BoxOptions; + +export type BoxedTextProps = Props< + BoxedTextOptions +>; diff --git a/src/primitives/text/index.ts b/src/primitives/text/index.ts index 22e10b67..f46cb691 100644 --- a/src/primitives/text/index.ts +++ b/src/primitives/text/index.ts @@ -1 +1,2 @@ +export * from "./BoxedText"; export * from "./Text"; From 3bc2a5801c3b4d30b4d7fa01d02b7c1f4cce411c Mon Sep 17 00:00:00 2001 From: Navin Moorthy Date: Mon, 1 Aug 2022 13:32:32 +0530 Subject: [PATCH 6/6] =?UTF-8?q?refactor(Text):=20=E2=99=BB=EF=B8=8F=20move?= =?UTF-8?q?=20BoxedText=20to=20AdaptText?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/badge-new/BadgeText.tsx | 16 +++++++------- src/primitives/box/Box.tsx | 6 +++--- src/primitives/text/AdaptText.tsx | 29 ++++++++++++++++++++++++++ src/primitives/text/BoxedText.tsx | 26 ----------------------- src/primitives/text/index.ts | 2 +- 5 files changed, 41 insertions(+), 38 deletions(-) create mode 100644 src/primitives/text/AdaptText.tsx delete mode 100644 src/primitives/text/BoxedText.tsx diff --git a/src/components/badge-new/BadgeText.tsx b/src/components/badge-new/BadgeText.tsx index f028b736..46c06934 100644 --- a/src/components/badge-new/BadgeText.tsx +++ b/src/components/badge-new/BadgeText.tsx @@ -1,7 +1,7 @@ import { - BoxedText, - BoxedTextOptions, - useBoxedText, + AdaptText, + AdaptTextOptions, + useAdaptText, } from "../../primitives/text"; import { useTheme } from "../../theme"; import { cx } from "../../utils"; @@ -26,7 +26,7 @@ export const useBadgeText = createHook( props.className, ); - props = useBoxedText({ ...props, className }); + props = useAdaptText({ ...props, className }); return props; }, @@ -35,12 +35,12 @@ export const useBadgeText = createHook( export const BadgeText = createComponentType(props => { const htmlProps = useBadgeText(props); - return createElement(BoxedText, htmlProps); + return createElement(AdaptText, htmlProps); }, "BadgeText"); -export type BadgeTextOptions = - BoxedTextOptions & Partial & {}; +export type BadgeTextOptions = + AdaptTextOptions & Partial & {}; -export type BadgeTextProps = Props< +export type BadgeTextProps = Props< BadgeTextOptions >; diff --git a/src/primitives/box/Box.tsx b/src/primitives/box/Box.tsx index 976597a8..fd8ec9a6 100644 --- a/src/primitives/box/Box.tsx +++ b/src/primitives/box/Box.tsx @@ -5,7 +5,7 @@ import { styleAdapter } from "../../utils"; import { As, ComponentOptions, - createComponent, + createComponentType, createElement, createHook, Props, @@ -20,10 +20,10 @@ export const useBox = createHook( }, ); -export const Box = createComponent(props => { +export const Box = createComponentType(props => { const htmlProps = useBox(props); return createElement(View, htmlProps); -}); +}, "Box"); export type BoxOptions = ComponentOptions & { className?: string; diff --git a/src/primitives/text/AdaptText.tsx b/src/primitives/text/AdaptText.tsx new file mode 100644 index 00000000..dc67459a --- /dev/null +++ b/src/primitives/text/AdaptText.tsx @@ -0,0 +1,29 @@ +import { + As, + createComponent, + createElement, + createHook, + Props, +} from "../../utils/system"; +import { BoxOptions, useBox } from "../box"; + +import { Text, TextOptions, useText } from "./Text"; + +export const useAdaptText = createHook(props => { + props = useText(props); + props = useBox(props); + + return props; +}); + +export const AdaptText = createComponent(props => { + const htmlProps = useAdaptText(props); + return createElement(Text, htmlProps); +}); + +export type AdaptTextOptions = TextOptions & + BoxOptions; + +export type AdaptTextProps = Props< + AdaptTextOptions +>; diff --git a/src/primitives/text/BoxedText.tsx b/src/primitives/text/BoxedText.tsx deleted file mode 100644 index 6113ac56..00000000 --- a/src/primitives/text/BoxedText.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Text as RNText } from "react-native"; - -import { - As, - createComponent, - createElement, - createHook, - Props, -} from "../../utils/system"; -import { BoxOptions, useBox } from "../box"; - -export const useBoxedText = createHook(props => { - props = useBox(props); - return props; -}); - -export const BoxedText = createComponent(props => { - const htmlProps = useBoxedText(props); - return createElement(RNText, htmlProps); -}); - -export type BoxedTextOptions = BoxOptions; - -export type BoxedTextProps = Props< - BoxedTextOptions ->; diff --git a/src/primitives/text/index.ts b/src/primitives/text/index.ts index f46cb691..49e54ba1 100644 --- a/src/primitives/text/index.ts +++ b/src/primitives/text/index.ts @@ -1,2 +1,2 @@ -export * from "./BoxedText"; +export * from "./AdaptText"; export * from "./Text";