mirror of
https://github.com/hanzoai/gui.git
synced 2026-10-25 02:31:41 +00:00
Both old names are owned by a different npm account (artemis-prime), so publishing them returns 403 — which is how this surfaced: the 8.0.0 run shipped @hanzogui/logo and @hanzogui/demos depending on [email protected], a version that cannot be published and therefore does not exist. Those two were uninstallable. @hanzo/gui and @hanzogui/* are ours, so the framework now carries the name it is imported by everywhere downstream, and the loader moves under the scope we control. The rewrite is limited to package references. `DEBUG?.startsWith('hanzogui')` is a log-namespace filter and join(dir, 'hanzogui') is a path — rewriting either would change behaviour, so both are untouched. Co-authored-by: Hanzo Dev <[email protected]>
390 lines
11 KiB
TypeScript
390 lines
11 KiB
TypeScript
import { memo, useEffect } from 'react'
|
|
import {
|
|
Button,
|
|
H4,
|
|
H6,
|
|
ListItem,
|
|
Paragraph,
|
|
SizableText,
|
|
XStack,
|
|
YGroup,
|
|
YStack,
|
|
} from '@hanzo/gui'
|
|
|
|
import { useThemeBuilderStore } from '~/features/studio/theme/store/ThemeBuilderStore'
|
|
import { NoticeParagraph, StudioNotice } from '../../../StudioNotice'
|
|
import { defaultBaseTheme } from '../../constants/defaultBaseTheme'
|
|
import { getUniqueId } from '../../helpers/getUniqueId'
|
|
import type { BuildTheme } from '../../types'
|
|
import type { BuildPalette } from '@hanzogui/theme-builder'
|
|
import { AddDropdown } from '../../views/AddDropdown'
|
|
import { ColorThemeIndicator } from '../../views/ColorThemeIndicator'
|
|
import { BuildThemeItem } from '../views/BuildThemeItem'
|
|
import { Stage, StageButtonBar, useSteps } from '../views/Stage'
|
|
import { ThemeBuilderPalettesPane } from '../views/ThemeBuilderPalettesPane'
|
|
|
|
// type StepSubThemesProps = {
|
|
// previewMode?: boolean
|
|
// }
|
|
|
|
const useSubThemesSteps = () => {
|
|
return useSteps({ id: 'step-sub-themes', total: 2, initial: 1 })
|
|
}
|
|
|
|
export const StepSubThemes = memo(() => {
|
|
const store = useThemeBuilderStore()
|
|
const steps = useSubThemesSteps()
|
|
|
|
// ensure first is always selected
|
|
useEffect(() => {
|
|
// timeout prevents the bug where we're left with a selected, but not applies theme
|
|
const timeoutId = setTimeout(() => {
|
|
if (store.subThemes.length > 0 && !store.selectedSubTheme) {
|
|
const id = store.subThemes[0].id
|
|
store.setSelectedSubTheme(id)
|
|
}
|
|
}, 500)
|
|
return () => {
|
|
clearTimeout(timeoutId)
|
|
}
|
|
}, [store.subThemes.length, store.selectedSubTheme])
|
|
|
|
useEffect(() => {
|
|
if (!store.subThemes.length) {
|
|
const tm = setTimeout(() => {
|
|
store.showAddThemeMenu = true
|
|
}, 500)
|
|
return () => clearTimeout(tm)
|
|
}
|
|
}, [store.subThemes.length])
|
|
|
|
return (
|
|
<YStack mx="$-5" flex={1}>
|
|
<StageButtonBar steps={steps} />
|
|
<Stage
|
|
current={steps.index}
|
|
steps={[<ThemeBuilderPalettesPane key="palettes" />, <Themes key="themes" />]}
|
|
/>
|
|
</YStack>
|
|
)
|
|
})
|
|
|
|
export const Themes = memo(() => {
|
|
const store = useThemeBuilderStore()
|
|
|
|
return (
|
|
<YStack flex={1} gap="$4" py="$4" px="$2">
|
|
{store.subThemes
|
|
// .sort((a, b) => (a.id === store.selectedSubTheme ? -1 : 1))
|
|
.map((theme) => {
|
|
const handleUpdate = (next: Partial<BuildTheme>) => {
|
|
store.updateSubTheme({
|
|
...theme,
|
|
...next,
|
|
})
|
|
}
|
|
|
|
const handleDelete = () => {
|
|
store.deleteSubTheme(theme)
|
|
}
|
|
|
|
return (
|
|
<BuildThemeItem
|
|
key={theme.id}
|
|
isActive={store.selectedSubTheme === theme.id}
|
|
enableEditLabel
|
|
label={theme.name}
|
|
theme={theme}
|
|
onUpdate={handleUpdate}
|
|
onDelete={handleDelete}
|
|
onPress={() => {
|
|
store.setSelectedSubTheme(theme.id)
|
|
}}
|
|
/>
|
|
)
|
|
})}
|
|
|
|
{!store.subThemes.length && (
|
|
<YStack flex={1} items="center" justify="center" gap="$4">
|
|
<Paragraph color="$color10">Add a theme to get started</Paragraph>
|
|
<Button
|
|
theme="accent"
|
|
size="$4"
|
|
onPress={() => {
|
|
store.showAddThemeMenu = true
|
|
}}
|
|
>
|
|
Add theme
|
|
</Button>
|
|
</YStack>
|
|
)}
|
|
</YStack>
|
|
)
|
|
})
|
|
|
|
export function StepSubThemesActions() {
|
|
const store = useThemeBuilderStore()
|
|
const show = store.showAddThemeMenu
|
|
const setShow = (val: boolean) => (store.showAddThemeMenu = val)
|
|
|
|
return (
|
|
<XStack items="center" gap="$4">
|
|
<AddDropdown open={show} onOpenChange={setShow}>
|
|
<YGroup>
|
|
<AddDropdown.Title>Palette Themes</AddDropdown.Title>
|
|
|
|
{colorThemePresets.map(({ theme, palette }) => (
|
|
<AddDropdown.Item
|
|
size="$3"
|
|
key={theme.name}
|
|
onPress={() => {
|
|
store.addPalette(palette)
|
|
store.addSubTheme(theme)
|
|
setShow(false)
|
|
}}
|
|
iconAfter={<ColorThemeIndicator size={10} primary={palette} />}
|
|
title={theme.name}
|
|
/>
|
|
))}
|
|
|
|
<AddDropdown.Separator />
|
|
|
|
<H6 size="$1" pt="$2" px="$3" opacity={0.5}>
|
|
Custom
|
|
</H6>
|
|
|
|
<AddDropdown.Item
|
|
size="$3"
|
|
onPress={() => {
|
|
store.addSubTheme(defaultBaseTheme)
|
|
setShow(false)
|
|
}}
|
|
title="Palette Theme"
|
|
></AddDropdown.Item>
|
|
</YGroup>
|
|
</AddDropdown>
|
|
</XStack>
|
|
)
|
|
}
|
|
|
|
const colorThemePresets: { theme: BuildTheme; palette: BuildPalette }[] = [
|
|
{
|
|
theme: {
|
|
id: getUniqueId(),
|
|
name: 'success',
|
|
template: 'base',
|
|
type: 'theme',
|
|
palette: 'green',
|
|
},
|
|
palette: {
|
|
name: 'green',
|
|
anchors: [
|
|
{
|
|
index: 0,
|
|
hue: { sync: true, light: 153, dark: 153 },
|
|
sat: { sync: true, light: 0.7, dark: 0.7 },
|
|
lum: { light: 0.985, dark: 0.1 },
|
|
alpha: { light: 1, dark: 1 },
|
|
},
|
|
{
|
|
index: 9,
|
|
hue: { syncLeft: true, sync: true, light: 153, dark: 153 },
|
|
sat: { syncLeft: true, sync: true, light: 0.7, dark: 0.7 },
|
|
lum: { light: 0.5, dark: 0.5 },
|
|
alpha: { light: 1, dark: 1 },
|
|
},
|
|
{
|
|
index: 10,
|
|
hue: { sync: true, light: 153, dark: 153 },
|
|
sat: { sync: true, light: 0.7, dark: 0.7 },
|
|
lum: { light: 0.15, dark: 0.925 },
|
|
alpha: { light: 1, dark: 1 },
|
|
},
|
|
{
|
|
index: 11,
|
|
hue: { syncLeft: true, sync: true, light: 153, dark: 153 },
|
|
sat: { syncLeft: true, sync: true, light: 0.7, dark: 0.7 },
|
|
lum: { light: 0.1, dark: 0.95 },
|
|
alpha: { light: 1, dark: 1 },
|
|
},
|
|
],
|
|
},
|
|
},
|
|
|
|
{
|
|
theme: {
|
|
id: getUniqueId(),
|
|
name: 'warning',
|
|
template: 'base',
|
|
type: 'theme',
|
|
palette: 'yellow',
|
|
},
|
|
palette: {
|
|
name: 'yellow',
|
|
anchors: [
|
|
{
|
|
index: 0,
|
|
hue: { sync: true, light: 48, dark: 48 },
|
|
sat: { sync: true, light: 0.7, dark: 0.7 },
|
|
lum: { light: 0.985, dark: 0.1 },
|
|
alpha: { light: 1, dark: 1 },
|
|
},
|
|
{
|
|
index: 9,
|
|
hue: { syncLeft: true, sync: true, light: 48, dark: 48 },
|
|
sat: { syncLeft: true, sync: true, light: 0.7, dark: 0.7 },
|
|
lum: { light: 0.5, dark: 0.5 },
|
|
alpha: { light: 1, dark: 1 },
|
|
},
|
|
{
|
|
index: 10,
|
|
hue: { sync: true, light: 48, dark: 48 },
|
|
sat: { sync: true, light: 0.7, dark: 0.7 },
|
|
lum: { light: 0.15, dark: 0.925 },
|
|
alpha: { light: 1, dark: 1 },
|
|
},
|
|
{
|
|
index: 11,
|
|
hue: { syncLeft: true, sync: true, light: 48, dark: 48 },
|
|
sat: { syncLeft: true, sync: true, light: 0.7, dark: 0.7 },
|
|
lum: { light: 0.1, dark: 0.95 },
|
|
alpha: { light: 1, dark: 1 },
|
|
},
|
|
],
|
|
},
|
|
},
|
|
|
|
{
|
|
theme: {
|
|
id: getUniqueId(),
|
|
name: 'error',
|
|
template: 'base',
|
|
type: 'theme',
|
|
palette: 'red',
|
|
},
|
|
palette: {
|
|
name: 'red',
|
|
anchors: [
|
|
{
|
|
index: 0,
|
|
hue: { sync: true, light: 0, dark: 0 },
|
|
sat: { sync: true, light: 0.7, dark: 0.7 },
|
|
lum: { light: 0.985, dark: 0.1 },
|
|
alpha: { light: 1, dark: 1 },
|
|
},
|
|
{
|
|
index: 9,
|
|
hue: { syncLeft: true, sync: true, light: 0, dark: 0 },
|
|
sat: { syncLeft: true, sync: true, light: 0.7, dark: 0.7 },
|
|
lum: { light: 0.5, dark: 0.5 },
|
|
alpha: { light: 1, dark: 1 },
|
|
},
|
|
{
|
|
index: 10,
|
|
hue: { sync: true, light: 0, dark: 0 },
|
|
sat: { sync: true, light: 0.7, dark: 0.7 },
|
|
lum: { light: 0.15, dark: 0.925 },
|
|
alpha: { light: 1, dark: 1 },
|
|
},
|
|
{
|
|
index: 11,
|
|
hue: { syncLeft: true, sync: true, light: 0, dark: 0 },
|
|
sat: { syncLeft: true, sync: true, light: 0.7, dark: 0.7 },
|
|
lum: { light: 0.1, dark: 0.95 },
|
|
alpha: { light: 1, dark: 1 },
|
|
},
|
|
],
|
|
},
|
|
},
|
|
]
|
|
|
|
export function BaseThemesStepPreviewThemes() {
|
|
return <StepSubThemes />
|
|
}
|
|
|
|
export function BaseThemesStepPreview() {
|
|
return (
|
|
<YStack py="$4" gap="$4">
|
|
<H4>Alright, thats our base and sub-themes 🙌</H4>
|
|
|
|
<Paragraph color="$color10" size="$5">
|
|
We've made the foundation of our theme suite.
|
|
</Paragraph>
|
|
|
|
<StudioNotice my="$3" title="Coming later...">
|
|
<NoticeParagraph>
|
|
You'll be able to customize <em>component themes</em> later, so don't worry if
|
|
some specific components look a little off (like if you want brighter
|
|
backgrounds on buttons).
|
|
</NoticeParagraph>
|
|
</StudioNotice>
|
|
</YStack>
|
|
)
|
|
}
|
|
|
|
export function BaseThemesStepPreview2() {
|
|
const { baseTheme, subThemes, schemes: userSchemes, palettes } = useThemeBuilderStore()
|
|
const schemes = Object.keys(userSchemes)
|
|
const fullThemes = schemes.flatMap((scheme) => {
|
|
return [
|
|
{
|
|
...baseTheme,
|
|
name: scheme || '?',
|
|
},
|
|
baseTheme.accent
|
|
? {
|
|
...baseTheme.accent,
|
|
name: `${scheme}_${baseTheme.accent?.name}`,
|
|
}
|
|
: null,
|
|
...subThemes.map((theme) => {
|
|
return {
|
|
...theme,
|
|
name: `${scheme}_${theme.name}`,
|
|
}
|
|
}),
|
|
].filter(Boolean)
|
|
})
|
|
|
|
return (
|
|
<YStack py="$4" gap="$3">
|
|
<H4>The full list, so far:</H4>
|
|
|
|
<Paragraph size="$5" color="$color10">
|
|
Congrats! That's the core of your theme suite.
|
|
</Paragraph>
|
|
|
|
<Paragraph size="$5" color="$color10">
|
|
In the future, we'll allow customizing component themes as well, but for now this
|
|
gives you everything you need. You can of course manually override the component
|
|
styles very easily inline.
|
|
</Paragraph>
|
|
|
|
<Paragraph size="$5" color="$color10">
|
|
We've now generated the following themes:
|
|
</Paragraph>
|
|
|
|
<YGroup borderWidth={1} borderColor="$borderColor" my="$2">
|
|
{fullThemes.map((theme) => {
|
|
return (
|
|
<ListItem
|
|
key={theme?.name}
|
|
title={theme?.name}
|
|
iconAfter={
|
|
theme?.type === 'theme' ? (
|
|
<ColorThemeIndicator
|
|
primary={palettes[theme.palette]}
|
|
secondary={palettes[theme.accent?.palette as any]}
|
|
/>
|
|
) : (
|
|
<SizableText>Mask</SizableText>
|
|
)
|
|
}
|
|
/>
|
|
)
|
|
})}
|
|
</YGroup>
|
|
</YStack>
|
|
)
|
|
}
|