Skip to content

Commit

Permalink
feat: create context with *use client* directive (#333)
Browse files Browse the repository at this point in the history
  • Loading branch information
castrogarciajs committed May 31, 2024
1 parent 3b71d59 commit d266119
Show file tree
Hide file tree
Showing 29 changed files with 339 additions and 261 deletions.
2 changes: 1 addition & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
strict-peer-dependencies=false
enable-pre-post-scripts=true
enable-pre-post-scripts=true
7 changes: 6 additions & 1 deletion apps/playground/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Metadata } from 'next'
import { OpenUIProvider } from '@openlabs/ui'
import { Inter } from 'next/font/google'
import './globals.css'

Expand All @@ -16,7 +17,11 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
<body className={inter.className}>
<OpenUIProvider>
{children}
</OpenUIProvider>
</body>
</html>
)
}
23 changes: 3 additions & 20 deletions apps/playground/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,9 @@
import { Button } from '@openlabs/ui'
import Playground from '@/components/playground'

export default function Home() {
return (
<Playground>
<h1 className="text-xl font-extrabold uppercase">Zone game</h1>
<div>
-------------------------------------
<br />
<small className="font-bold block text-center">
Down here is the playground
</small>
</div>
{/*
* The playground component is a wrapper that applies some styles to its children. This is a good example of how to create a reusable component in Next.js.
Rules
- Test UI components in isolation
- Add the component to the page.tsx file
- Remove the component from the page.tsx files
*/}
<Button>Hola</Button>
</Playground>
<main className="flex flex-col items-center justify-center w-screen h-screen">
<Button>Play</Button>
</main>
)
}
7 changes: 0 additions & 7 deletions apps/playground/components/playground.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion apps/playground/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@openlabs/playground",
"name": "playground",
"version": "0.1.0",
"private": true,
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion apps/playground/tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const config: Config = {
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
'./node_modules/@openlabs/theme/dist/**/*.{js,ts,jsx,tsx}',
'./node_modules/@openlabs/theme/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {},
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
"scripts": {
"dev": "pnpm web",
"lint": "eslint . --fix",
"dev:playground": "turbo dev --filter=@openlabs/playground",
"dev:playground": "turbo dev --filter=playground",
"dev:web": "turbo dev --filter=@openlabs/web",
"sb": "pnpm --filter @openlabs/storybook dev --filter @openlabs/web",
"clean": "rimraf .turbo pnpm-lock.yaml node_modules",
"turbo:graph": "pnpm build --graph=dependency-graph.png",
"clean:lock": "rimraf ./pnpm-lock.yaml",
"compile": "turbo compile --filter=!@openlabs/web --filter=!@openlabs/storybook",
"build": "turbo build --filter=!@openlabs/storybook --filter=!@openlabs/playground --filter=!@openlabs/web",
"build": "turbo build --filter=!@openlabs/storybook --filter=!playground --filter=!@openlabs/web",
"build:components": "turbo build --filter=@openlabs/ui",
"build:web": "turbo build --filter=@openlabs/web",
"postinstall": "esno ./.github/prepare.ts && pnpm --filter @openlabs/theme build"
Expand Down
2 changes: 2 additions & 0 deletions packages/system/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
},
"devDependencies": {
"@openlabs/tsconfig": "workspace:*",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tsup": "^8.0.2",
Expand Down
52 changes: 52 additions & 0 deletions packages/system/src/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as React from 'react'

export interface CreateContextOptions {
/**
* If `true`, React will throw if context is `null` or `undefined`
* In some cases, you might want to support nested context, so you can set it to `false`
*/
strict?: boolean
/**
* Error message to throw if the context is `undefined`
*/
errorMessage?: string
/**
* The display name of the context
*/
name?: string
}

export type CreateContextReturn<T> = [React.Provider<T>, () => T, React.Context<T>]

/**
* Creates a named context, provider, and hook.
*
* @param options create context options
*/
export function createContext<ContextType>(options: CreateContextOptions = {}) {
const {
strict = true,
errorMessage = 'useContext: `context` is undefined. Seems you forgot to wrap component within the Provider',
name,
} = options

const Context = React.createContext<ContextType | undefined>(undefined)

Context.displayName = name

function useContext() {
const context = React.useContext(Context)

if (!context && strict) {
const error = new Error(errorMessage)

error.name = 'ContextError'
Error.captureStackTrace?.(error, useContext)
throw error
}

return context
}

return [Context.Provider, useContext, Context] as CreateContextReturn<ContextType>
}
8 changes: 8 additions & 0 deletions packages/system/src/create-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createContext } from './context'

export interface ProviderContextProps {}

export const [ProviderContext, useProviderContext] = createContext<ProviderContextProps>({
name: 'ProviderContext',
strict: false,
})
10 changes: 9 additions & 1 deletion packages/system/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,12 @@ export type {
Merge,
HTMLOpenUIProps,
PropGetter,
} from '@system/types'
} from './types'

export { forwardRef, isOpenUIEl, toIterator } from './utils'

export type { OpenUIProviderProps } from './provider'
export type { ProviderContextProps } from './create-provider'

export { OpenUIProvider } from './provider'
export { ProviderContext, useProviderContext } from './create-provider'
14 changes: 14 additions & 0 deletions packages/system/src/provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { ProviderContextProps } from './create-provider'
import { ProviderContext } from './create-provider'

export interface OpenUIProviderProps extends ProviderContextProps {
children: React.ReactNode
}

export const OpenUIProvider: React.FC<OpenUIProviderProps> = ({ children }) => {
return (
<ProviderContext value={{}}>
{children}
</ProviderContext>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@ export type MergeWithAs<
as?: AsComponent
}

export interface InternalForwardRefRenderFunction<
Component extends As,
Props extends object = object,
OmitKeys extends keyof any = never,
> {
export interface InternalForwardRefRenderFunction<Component extends As, Props extends object = object, OmitKeys extends keyof any = never> {
<AsComponent extends As = Component>(
props: MergeWithAs<
React.ComponentPropsWithoutRef<Component>,
Expand Down
42 changes: 42 additions & 0 deletions packages/system/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type * as React from 'react'
import { forwardRef as baseForwardRef } from 'react'
import type {
As,
InternalForwardRefRenderFunction,
PropsOf,
RightJoinProps,
} from './types'

export function forwardRef<Component extends As, Props extends object, OmitKeys extends keyof any = never>(
component: React.ForwardRefRenderFunction<any, RightJoinProps<PropsOf<Component>, Props> & { as?: As }>,
) {
return baseForwardRef(component) as InternalForwardRefRenderFunction<Component, Props, OmitKeys>
}

export function toIterator(obj: any) {
return {
...obj,
[Symbol.iterator]() {
const keys = Object.keys(this)
let index = 0

return {
next: () => {
if (index >= keys.length) {
return { done: true }
}
const key = keys[index]
const value = this[key]

index++

return { value: { key, value }, done: false }
},
}
},
}
}

export function isOpenUIEl(component: React.ReactComponentElement<any>) {
return !!component.type?.render?.displayName?.includes('OpenUI')
}
8 changes: 1 addition & 7 deletions packages/system/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
{
"extends": "@openlabs/tsconfig/base.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@system/types": ["types/index.d.ts"]
}
},
"extends": "@openlabs/tsconfig/react-lib.json",
"include": ["."],
"exclude": ["dist", "build", "node_modules"]
}
2 changes: 1 addition & 1 deletion packages/system/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { defineConfig } from 'tsup'

export default defineConfig({
name: 'system',
entry: ['src/index.ts'],
entry: ['./src/index.ts'],
clean: true,
target: 'es2019',
format: ['cjs', 'esm'],
Expand Down
4 changes: 2 additions & 2 deletions packages/theme/src/base.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { LayoutTheme } from '@theme/types'
import type { LayoutTheme } from './types'

export const defatulTheme: LayoutTheme = {
export const baseTheme: LayoutTheme = {
fontSize: {
small: '0.875rem',
medium: '1rem',
Expand Down
2 changes: 1 addition & 1 deletion packages/theme/src/colors/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { BaseColors } from '@theme/types'
import type { BaseColors } from '../types'

export const colors: BaseColors = {
light: {
Expand Down
2 changes: 1 addition & 1 deletion packages/theme/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import Color from 'color'
import kebabCase from 'lodash.kebabcase'
import mapKeys from 'lodash.mapkeys'
import type { ConfigTheme, DefaultThemeType, Resolved } from '@theme/types'
import type { ConfigTheme, DefaultThemeType, Resolved } from './types'
import { flattenThemeObject } from './utils/functions'

const parsedColorsCache: Record<string, number[]> = {}
Expand Down
2 changes: 1 addition & 1 deletion packages/theme/src/create-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import plugin from 'tailwindcss/plugin.js'
import type { DefaultThemeType } from '@theme/types'
import type { DefaultThemeType } from './types'
import type { ConfigThemes } from './tailwindcss'
import { config } from './config'
import { animations, baseStyles, tailwind, utilities } from './theme'
Expand Down
36 changes: 18 additions & 18 deletions packages/theme/src/tailwindcss.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type plugin from 'tailwindcss/plugin.js'
import type { ConfigTheme, OpenUIPluginConfig } from '@theme/types'
import deepMerge from 'deepmerge'
import get from 'lodash.get'
import omit from 'lodash.omit'
import forEach from 'lodash.foreach'
import type { ConfigTheme, OpenUIPluginConfig } from './types'
import { baseTheme } from './base'
import {
darkTheme,
defatulTheme,
lightTheme,
colors as semanticColors,
} from './theme'
Expand All @@ -26,44 +26,44 @@ export function openui(config: OpenUIPluginConfig = {}): ReturnType<typeof plugi
prefix: defaultPrefix = DEFAULT_PREFIX,
} = config

const customLightColors = get(themeObject, 'light.colors', {})
const customDarkColors = get(themeObject, 'dark.colors', {})
const userLightColors = get(themeObject, 'light.colors', {})
const userDarkColors = get(themeObject, 'dark.colors', {})

const mergedLayout = userLayout && typeof userLayout === 'object'
? deepMerge(defatulTheme, userLayout)
: defatulTheme
const defaultLayoutObj = userLayout && typeof userLayout === 'object'
? deepMerge(baseTheme, userLayout)
: baseTheme

const mergedThemeLayouts = {
const baseLayouts = {
light: {
...mergedLayout,
...defaultLayoutObj,
...lightTheme,
},
dark: {
...mergedLayout,
...defaultLayoutObj,
...darkTheme,
},
}

const otherThemes = omit(themeObject, ['light', 'dark']) || {}

forEach(otherThemes, ({ extend, colors, layout }, themeName) => {
const defaultExtensionTheme = extend && isBaseTheme(extend) ? extend : defaultExtendTheme
const baseTheme = extend && isBaseTheme(extend) ? extend : defaultExtendTheme

if (colors && typeof colors === 'object')
otherThemes[themeName].colors = deepMerge(semanticColors[defaultExtensionTheme], colors)
otherThemes[themeName].colors = deepMerge(semanticColors[baseTheme], colors)

if (layout && typeof layout === 'object')
otherThemes[themeName].layout = deepMerge(extend ? mergedThemeLayouts[extend] : mergedLayout, layout)
otherThemes[themeName].layout = deepMerge(extend ? baseLayouts[extend] : defaultLayoutObj, layout)
})

const light: ConfigTheme = {
layout: deepMerge(mergedThemeLayouts.light, get(themeObject, 'light.layout', {})),
colors: deepMerge(semanticColors.light, customLightColors),
layout: deepMerge(baseLayouts.light, get(themeObject, 'light.layout', {})),
colors: deepMerge(semanticColors.light, userLightColors),
}

const dark: ConfigTheme = {
layout: deepMerge(mergedThemeLayouts.dark, get(themeObject, 'dark.layout'), {}),
colors: deepMerge(semanticColors.dark, customDarkColors),
const dark = {
layout: deepMerge(baseLayouts.dark, get(themeObject, 'dark.layout', {})),
colors: deepMerge(semanticColors.dark, userDarkColors),
}

const themes = {
Expand Down
Loading

0 comments on commit d266119

Please sign in to comment.