Skip to content

Commit

Permalink
@shadergradient/react/stateless
Browse files Browse the repository at this point in the history
(ShaderGradientStateless)

----

StoreShaderGradient

----

fmt

start StoreShaderGradient

move other preset (object format)

fmt

restore

include zustand (it is already dependecny of react-three-fiber)

StoreShaderGradient

restore tsup.framer.config.ts

rename: ShaderGradientStateless (was StoreShaderGradient)

fmt

rm missing files config (need to be added)
  • Loading branch information
ruucm committed Nov 25, 2024
1 parent f54fa76 commit 2a9d6d9
Show file tree
Hide file tree
Showing 14 changed files with 554 additions and 3 deletions.
29 changes: 29 additions & 0 deletions apps/example-nextjs-dev/app/stateless-test/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use client'

import { ShaderGradientCanvas } from '@shadergradient/react'
import {
useQueryState,
ShaderGradientStateless,
} from '@shadergradient/react/stateless'

export default function Page() {
const [, setColor1] = useQueryState('color1')

return (
<div className='flex'>
<div className='flex-1 h-screen'>
<ShaderGradientCanvas>
{/* When control is 'query', the gradient props follows url query params */}
<ShaderGradientStateless control='query' />
</ShaderGradientCanvas>
</div>

<button
onClick={() => setColor1('#0D77E0')}
className='fixed bottom-5 right-5 z-20'
>
Change Color
</button>
</div>
)
}
9 changes: 9 additions & 0 deletions packages/shadergradient-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@
"files": [
"dist/**"
],
"exports": {
".": {
"import": "./dist/index.mjs",
"types": "./dist/index.d.mts"
},
"./stateless": {
"import": "./dist/ShaderGradientStateless/index.mjs"
}
},
"scripts": {
"build": "tsup",
"dev": "tsup --config tsup.config.ts --watch",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { GradientT } from '@/types'
import { ShaderGradient } from '../ShaderGradient/ShaderGradient'
import { useSearchParamToStore } from './store/useSearchParamToStore'
import { useControlValues } from './useControlValues'

export function ShaderGradientStateless(passedProps: GradientT): JSX.Element {
useSearchParamToStore() // init gradient state with url query
const props = useControlValues(passedProps.control, passedProps) // make props using url query, control and passed props

return <ShaderGradient {...props} />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './store/useQueryState'
export * from './ShaderGradientStateless'
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './store'
export * from './useQueryState'

export * from './presetURLs'
export * from './usePresetToStore'
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { presets } from '@/presets'

export const initialActivePreset = 0

export const PRESETS = convertPresets(presets)

// export const DEFAUlT_PRESET = '?pixelDensity=1&fov=45'
export const DEFAUlT_PRESET = PRESETS[0].url

function convertPresets(presets: Record<string, any>) {
const PRESETS = Object.entries(presets).map(([key, value]) => {
const { title, color, props } = value

const urlParams = new URLSearchParams(
Object.entries(props).reduce((acc, [propKey, propValue]) => {
acc[propKey] =
typeof propValue === 'boolean' ? String(propValue) : String(propValue)
return acc
}, {} as Record<string, string>)
).toString()

return {
title,
color,
url: `?${urlParams}`,
}
})

return PRESETS
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import * as qs from 'query-string'
import { create } from 'zustand'
import { combine } from 'zustand/middleware'
import { DEFAUlT_PRESET, initialActivePreset } from './presetURLs'

// without embedMode
// it renders without the dom & other gradient controls at first, and add it after the first updateGradientState() excuted.

const defaultState = { ...parseState() }
export const useQueryStore = create((set) => defaultState)

export const updateGradientState = (querystate: object | string) => {
const isString = typeof querystate === 'string'

let state = querystate
if (isString) state = parseState(querystate)

useQueryStore.setState(state, isString) // replace true if it's a string
}

// defaultGradient could be replaced by window.location.search
function parseState(search = DEFAUlT_PRESET) {
return qs.parse(search, {
parseNumbers: true,
parseBooleans: true,
arrayFormat: 'index',
})
}

export const useDomStore = create(() => {
return { dom: null }
})

// store for UI updates
export const useCursorStore = create((set) => ({
hoverState: 0,
hover: 'default',
updateHoverState: (payload) => set({ hoverState: payload }),
}))
export const useUIStore = create(
combine(
{ activePreset: initialActivePreset, mode: 'full', loadingPercentage: 0 },
(set) => ({
setActivePreset: (by: number) => set((state) => ({ activePreset: by })),
setMode: (data: any) => set((state) => ({ ...state, mode: data })),
setLoadingPercentage: (data: any) =>
set((state) => ({ ...state, loadingPercentage: data })),
})
)
)

// store for Figma Plugin
const useFigmaStore = create((set) => ({
figma: { selection: 0, user: null },
setFigma: (payload) =>
set((prev) => ({ figma: { ...prev.figma, ...payload } })),
}))
export function useFigma() {
const figma = useFigmaStore((state: any) => state.figma)
const setFigma = useFigmaStore((state: any) => state.setFigma)
return [figma, setFigma]
}

export const useBillingIntervalStore = create((set) => ({
billingInterval: 'year',
setBillingInterval: (payload) =>
set((state) => ({ billingInterval: payload })),
}))
export function useBillingInterval() {
const billingInterval = useBillingIntervalStore(
(state: any) => state.billingInterval
)
const setBillingInterval = useBillingIntervalStore(
(state: any) => state.setBillingInterval
)
return [billingInterval, setBillingInterval]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useEffect } from 'react'
import { PRESETS } from './presetURLs'
import { useUIStore, updateGradientState } from './store'

let pageLoaded = false
export function usePresetToStore() {
// ----------------------------- Preset to Custom Material ---------------------------------
const activePreset = useUIStore((state: any) => state.activePreset)
useEffect(() => {
let gradientURL

// CASE 1. use search params at the first load.
if (
!pageLoaded &&
window.location.search?.includes('pixelDensity') // checking just window.location.search existing is not valid for the Framer Preview search (?target=preview-web)
)
gradientURL = window.location.search
// CASE 2. When activePreset changes by UI buttons
else gradientURL = PRESETS[activePreset].url

updateGradientState(gradientURL)

pageLoaded = true
}, [activePreset])
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { useCallback } from 'react'
import * as qs from 'query-string'
import { useQueryStore } from './store'

export const useQueryState = (propName: any, defaultValue: any = null) => {
const selector = useCallback(
(state) =>
typeof state[propName] !== 'undefined' ? state[propName] : defaultValue,
[propName, defaultValue]
)
const globalValue = useQueryStore(selector)
const _setGlobalValue = useCallback(
(valueFun) =>
useQueryStore.setState({
[propName]: valueFun(useQueryStore.getState()[propName]),
}),
[propName]
)

const setQueryValue = useCallback(
(newVal) => {
_setGlobalValue((currentState: any) => {
if (typeof newVal === 'function') {
newVal = newVal(currentState || defaultValue)
}
if (Number.isFinite(newVal)) {
newVal = parseFloat(newVal.toFixed(2))
}

// defer update of URL
setTimeout(() => {
const query = useQueryStore.getState()
updateHistory(
qs.stringifyUrl(
// @ts-ignore
{ url: window.location.pathname, query },
{ skipNull: true, arrayFormat: 'index' }
)
)
}, 0)

return newVal
})
},
[_setGlobalValue]
)

return [globalValue, setQueryValue]
}

export const useURLQueryState = () => {
// it's weird, but need to wrap below func as a hook
const setQueryValue = (search) => {
// defer update of URL
setTimeout(() => {
const query: any = useQueryStore.getState()
updateHistory(
qs.stringifyUrl(
{ url: window.location.pathname, query },
{ skipNull: true, arrayFormat: 'index' }
)
)
}, 0)

const state = qs.parse(search, {
parseNumbers: true,
parseBooleans: true,
arrayFormat: 'index',
})

useQueryStore.setState(state)
}
return setQueryValue
}

function updateHistory(path: string) {
window.history.pushState(
{
prevUrls: [
...(window.history.state?.prevUrls || []),
window.location.origin + path,
],
},
document.title,
path
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useEffect } from 'react'
import { updateGradientState } from './store'

export function useSearchParamToStore() {
useEffect(() => {
// if (
// window.location.search?.includes('pixelDensity') // checking just window.location.search existing is not valid for the Framer Preview search (?target=preview-web)
// )
updateGradientState(window.location.search)
}, [])
}
Loading

0 comments on commit 2a9d6d9

Please sign in to comment.