@glyphui/ui 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +137 -2
- package/dist/data-table.d.ts +110 -0
- package/dist/data-table.js +225 -0
- package/dist/field-DXZD0l8w.js +107 -0
- package/dist/field-context-B8cYheXz.js +321 -0
- package/dist/form.d.ts +79 -0
- package/dist/form.js +84 -0
- package/dist/index-BWK4czIG.d.ts +233 -0
- package/dist/index.d.ts +2155 -0
- package/dist/index.js +1529 -0
- package/dist/registry-BGSJoiLq.d.ts +9 -0
- package/dist/states-BxPjCUxz.js +287 -0
- package/dist/tv-DuMh7UH2.d.ts +6 -0
- package/package.json +113 -3
- package/src/components/avatar/avatar.styles.ts +22 -0
- package/src/components/avatar/avatar.tsx +62 -0
- package/src/components/avatar/index.ts +2 -0
- package/src/components/badge/badge.styles.ts +69 -0
- package/src/components/badge/badge.tsx +30 -0
- package/src/components/badge/index.ts +2 -0
- package/src/components/button/button.styles.ts +43 -0
- package/src/components/button/button.tsx +77 -0
- package/src/components/button/index.ts +2 -0
- package/src/components/card/card.styles.ts +32 -0
- package/src/components/card/card.tsx +91 -0
- package/src/components/card/index.ts +11 -0
- package/src/components/checkbox/checkbox.tsx +39 -0
- package/src/components/checkbox/index.ts +1 -0
- package/src/components/container/container.tsx +35 -0
- package/src/components/container/index.ts +1 -0
- package/src/components/eyebrow/eyebrow.styles.ts +14 -0
- package/src/components/eyebrow/eyebrow.tsx +19 -0
- package/src/components/eyebrow/index.ts +2 -0
- package/src/components/field/field-context.ts +40 -0
- package/src/components/field/field.styles.ts +23 -0
- package/src/components/field/field.tsx +109 -0
- package/src/components/field/index.ts +3 -0
- package/src/components/heading/heading.styles.ts +16 -0
- package/src/components/heading/heading.tsx +26 -0
- package/src/components/heading/index.ts +2 -0
- package/src/components/input/index.ts +2 -0
- package/src/components/input/input.styles.ts +36 -0
- package/src/components/input/input.tsx +33 -0
- package/src/components/kbd/index.ts +1 -0
- package/src/components/kbd/kbd.tsx +21 -0
- package/src/components/label/index.ts +1 -0
- package/src/components/label/label.tsx +21 -0
- package/src/components/section/index.ts +1 -0
- package/src/components/section/section.tsx +39 -0
- package/src/components/segmented-control/index.ts +6 -0
- package/src/components/segmented-control/segmented-control.tsx +79 -0
- package/src/components/select/index.ts +1 -0
- package/src/components/select/select.tsx +133 -0
- package/src/components/separator/index.ts +1 -0
- package/src/components/separator/separator.tsx +29 -0
- package/src/components/skeleton/index.ts +1 -0
- package/src/components/skeleton/skeleton.tsx +20 -0
- package/src/components/spinner/index.ts +2 -0
- package/src/components/spinner/spinner.styles.ts +19 -0
- package/src/components/spinner/spinner.tsx +42 -0
- package/src/components/stack/index.ts +1 -0
- package/src/components/stack/stack.tsx +62 -0
- package/src/components/switch/index.ts +1 -0
- package/src/components/switch/switch.tsx +48 -0
- package/src/components/table/index.ts +10 -0
- package/src/components/table/table.tsx +90 -0
- package/src/components/tabs/index.ts +1 -0
- package/src/components/tabs/tabs.tsx +92 -0
- package/src/components/text/index.ts +2 -0
- package/src/components/text/text.styles.ts +32 -0
- package/src/components/text/text.tsx +17 -0
- package/src/components/textarea/index.ts +1 -0
- package/src/components/textarea/textarea.tsx +27 -0
- package/src/components/tooltip/index.ts +1 -0
- package/src/components/tooltip/tooltip.tsx +42 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/use-component-config.ts +42 -0
- package/src/hooks/use-controllable-state.ts +27 -0
- package/src/hooks/use-media-query.ts +14 -0
- package/src/hooks/use-scrolled.ts +14 -0
- package/src/index.ts +52 -0
- package/src/internal/icons.tsx +99 -0
- package/src/patterns/app-shell/app-shell-context.ts +27 -0
- package/src/patterns/app-shell/app-shell.styles.ts +127 -0
- package/src/patterns/app-shell/app-shell.tsx +81 -0
- package/src/patterns/app-shell/index.ts +16 -0
- package/src/patterns/app-shell/navbar.tsx +119 -0
- package/src/patterns/app-shell/page-header.tsx +30 -0
- package/src/patterns/app-shell/sidebar.tsx +163 -0
- package/src/patterns/async/async.tsx +83 -0
- package/src/patterns/async/index.ts +10 -0
- package/src/patterns/async/states.tsx +128 -0
- package/src/patterns/data-table/data-table.tsx +367 -0
- package/src/patterns/data-table/index.ts +2 -0
- package/src/patterns/form/form.tsx +150 -0
- package/src/patterns/form/index.ts +13 -0
- package/src/provider/color-mode.tsx +133 -0
- package/src/provider/glyph-context.ts +42 -0
- package/src/provider/glyph-provider.tsx +41 -0
- package/src/provider/index.ts +18 -0
- package/src/provider/theme-scope.tsx +36 -0
- package/src/provider/theme-style.tsx +19 -0
- package/src/register.ts +69 -0
- package/src/themes.ts +56 -0
- package/src/utils/cn.ts +12 -0
- package/src/utils/compose-refs.ts +11 -0
- package/src/utils/registry.ts +9 -0
- package/src/utils/styles.ts +13 -0
- package/src/utils/tv.ts +7 -0
- package/src/utils/tw-merge-config.ts +14 -0
- package/styles.css +178 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Theme } from '@glyphui/tokens'
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
type ElementType,
|
|
5
|
+
type MouseEventHandler,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
useContext,
|
|
8
|
+
} from 'react'
|
|
9
|
+
|
|
10
|
+
/** Props Glyph passes to your router's link component. */
|
|
11
|
+
export interface LinkComponentProps {
|
|
12
|
+
href: string
|
|
13
|
+
className?: string
|
|
14
|
+
children?: ReactNode
|
|
15
|
+
onClick?: MouseEventHandler<HTMLAnchorElement>
|
|
16
|
+
'aria-current'?: 'page' | undefined
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Any component that renders a link from these props. Plain `'a'` by default.
|
|
21
|
+
* React Router: `({ href, ...props }) => <Link to={href} {...props} />`
|
|
22
|
+
*/
|
|
23
|
+
export type LinkComponent = ElementType<LinkComponentProps>
|
|
24
|
+
|
|
25
|
+
export interface GlyphContextValue {
|
|
26
|
+
theme: Theme
|
|
27
|
+
linkComponent: LinkComponent
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const GlyphContext = createContext<GlyphContextValue | null>(null)
|
|
31
|
+
|
|
32
|
+
/** The active theme and app-level settings. Must be used under `GlyphProvider`. */
|
|
33
|
+
export function useGlyph(): GlyphContextValue {
|
|
34
|
+
const value = useContext(GlyphContext)
|
|
35
|
+
if (!value) throw new Error('useGlyph must be used inside <GlyphProvider>.')
|
|
36
|
+
return value
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The router-aware link component configured on `GlyphProvider`. */
|
|
40
|
+
export function useLinkComponent(): LinkComponent {
|
|
41
|
+
return useContext(GlyphContext)?.linkComponent ?? 'a'
|
|
42
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Theme } from '@glyphui/tokens'
|
|
2
|
+
import { Tooltip } from 'radix-ui'
|
|
3
|
+
import { type ReactNode, useMemo } from 'react'
|
|
4
|
+
import { glyph } from '../themes'
|
|
5
|
+
import { type ColorModePreference, ColorModeProvider } from './color-mode'
|
|
6
|
+
import { GlyphContext, type LinkComponent } from './glyph-context'
|
|
7
|
+
import { ThemeStyle } from './theme-style'
|
|
8
|
+
|
|
9
|
+
export interface GlyphProviderProps {
|
|
10
|
+
children: ReactNode
|
|
11
|
+
/** The theme for the whole app. Defaults to the `glyph` preset. */
|
|
12
|
+
theme?: Theme
|
|
13
|
+
/** Color mode used until the user picks one. */
|
|
14
|
+
defaultMode?: ColorModePreference
|
|
15
|
+
/** localStorage key for the user's color mode. `null` disables persistence. */
|
|
16
|
+
storageKey?: string | null
|
|
17
|
+
/** Your router's link, used by Navbar, Sidebar and other navigation components. */
|
|
18
|
+
linkComponent?: LinkComponent
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Provides the theme, color mode and app settings. Render once at the root. */
|
|
22
|
+
export function GlyphProvider({
|
|
23
|
+
children,
|
|
24
|
+
theme = glyph,
|
|
25
|
+
defaultMode,
|
|
26
|
+
storageKey,
|
|
27
|
+
linkComponent = 'a',
|
|
28
|
+
}: GlyphProviderProps) {
|
|
29
|
+
const value = useMemo(() => ({ theme, linkComponent }), [theme, linkComponent])
|
|
30
|
+
return (
|
|
31
|
+
<ColorModeProvider
|
|
32
|
+
{...(defaultMode !== undefined && { defaultMode })}
|
|
33
|
+
{...(storageKey !== undefined && { storageKey })}
|
|
34
|
+
>
|
|
35
|
+
<GlyphContext.Provider value={value}>
|
|
36
|
+
<ThemeStyle theme={theme} />
|
|
37
|
+
<Tooltip.Provider delayDuration={300}>{children}</Tooltip.Provider>
|
|
38
|
+
</GlyphContext.Provider>
|
|
39
|
+
</ColorModeProvider>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export {
|
|
2
|
+
type ColorModeContextValue,
|
|
3
|
+
type ColorModeOptions,
|
|
4
|
+
type ColorModePreference,
|
|
5
|
+
ColorModeProvider,
|
|
6
|
+
ColorModeScript,
|
|
7
|
+
getColorModeScript,
|
|
8
|
+
useColorMode,
|
|
9
|
+
} from './color-mode'
|
|
10
|
+
export {
|
|
11
|
+
type GlyphContextValue,
|
|
12
|
+
type LinkComponent,
|
|
13
|
+
type LinkComponentProps,
|
|
14
|
+
useGlyph,
|
|
15
|
+
useLinkComponent,
|
|
16
|
+
} from './glyph-context'
|
|
17
|
+
export { GlyphProvider, type GlyphProviderProps } from './glyph-provider'
|
|
18
|
+
export { ThemeScope, type ThemeScopeProps } from './theme-scope'
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type ColorMode, scopeSelector, type Theme } from '@glyphui/tokens'
|
|
2
|
+
import { Slot } from 'radix-ui'
|
|
3
|
+
import { type ComponentProps, useContext, useMemo } from 'react'
|
|
4
|
+
import { cn } from '../utils/cn'
|
|
5
|
+
import { GlyphContext } from './glyph-context'
|
|
6
|
+
import { ThemeStyle } from './theme-style'
|
|
7
|
+
|
|
8
|
+
export interface ThemeScopeProps extends ComponentProps<'div'> {
|
|
9
|
+
theme: Theme
|
|
10
|
+
/** Force a color mode for this subtree. Follows the page by default. */
|
|
11
|
+
mode?: ColorMode
|
|
12
|
+
asChild?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Renders a subtree with a different theme, e.g. to compare themes side by side. */
|
|
16
|
+
export function ThemeScope({ theme, mode, asChild, children, ...props }: ThemeScopeProps) {
|
|
17
|
+
const parent = useContext(GlyphContext)
|
|
18
|
+
const value = useMemo(
|
|
19
|
+
() => ({ theme, linkComponent: parent?.linkComponent ?? 'a' }),
|
|
20
|
+
[theme, parent?.linkComponent],
|
|
21
|
+
)
|
|
22
|
+
const Comp = asChild ? Slot.Root : 'div'
|
|
23
|
+
return (
|
|
24
|
+
<GlyphContext.Provider value={value}>
|
|
25
|
+
<ThemeStyle theme={theme} selector={scopeSelector(theme)} />
|
|
26
|
+
<Comp
|
|
27
|
+
data-glyph-theme={theme.name}
|
|
28
|
+
data-mode={mode}
|
|
29
|
+
{...props}
|
|
30
|
+
className={cn('bg-background font-sans text-foreground', props.className)}
|
|
31
|
+
>
|
|
32
|
+
{children}
|
|
33
|
+
</Comp>
|
|
34
|
+
</GlyphContext.Provider>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Theme, themeToCss } from '@glyphui/tokens'
|
|
2
|
+
import { useMemo } from 'react'
|
|
3
|
+
|
|
4
|
+
interface ThemeStyleProps {
|
|
5
|
+
theme: Theme
|
|
6
|
+
selector?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Renders a theme's CSS variables. Re-renders replace the previous rules in place. */
|
|
10
|
+
export function ThemeStyle({ theme, selector }: ThemeStyleProps) {
|
|
11
|
+
const css = useMemo(() => themeToCss(theme, selector ? { selector } : {}), [theme, selector])
|
|
12
|
+
return (
|
|
13
|
+
<style
|
|
14
|
+
data-glyph-theme-style={theme.name}
|
|
15
|
+
// biome-ignore lint/security/noDangerouslySetInnerHtml: generated from typed theme tokens
|
|
16
|
+
dangerouslySetInnerHTML={{ __html: css }}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
19
|
+
}
|
package/src/register.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registers every component with the theme engine so `theme.components` is typed:
|
|
3
|
+
* `components.Button.defaultVariants.size` only accepts 'sm' | 'md' | 'lg', and
|
|
4
|
+
* `components.Card.className` only accepts Card's slot names.
|
|
5
|
+
*
|
|
6
|
+
* Adding a component: add one line here.
|
|
7
|
+
*/
|
|
8
|
+
import type { avatarStyles } from './components/avatar/avatar.styles'
|
|
9
|
+
import type { badgeStyles } from './components/badge/badge.styles'
|
|
10
|
+
import type { buttonStyles } from './components/button/button.styles'
|
|
11
|
+
import type { cardStyles } from './components/card/card.styles'
|
|
12
|
+
import type { checkboxStyles } from './components/checkbox/checkbox'
|
|
13
|
+
import type { eyebrowStyles } from './components/eyebrow/eyebrow.styles'
|
|
14
|
+
import type { fieldStyles } from './components/field/field.styles'
|
|
15
|
+
import type { headingStyles } from './components/heading/heading.styles'
|
|
16
|
+
import type { inputStyles } from './components/input/input.styles'
|
|
17
|
+
import type { kbdStyles } from './components/kbd/kbd'
|
|
18
|
+
import type { labelStyles } from './components/label/label'
|
|
19
|
+
import type { segmentedControlStyles } from './components/segmented-control/segmented-control'
|
|
20
|
+
import type { selectStyles } from './components/select/select'
|
|
21
|
+
import type { separatorStyles } from './components/separator/separator'
|
|
22
|
+
import type { skeletonStyles } from './components/skeleton/skeleton'
|
|
23
|
+
import type { spinnerStyles } from './components/spinner/spinner.styles'
|
|
24
|
+
import type { switchStyles } from './components/switch/switch'
|
|
25
|
+
import type { tableStyles } from './components/table/table'
|
|
26
|
+
import type { tabsStyles } from './components/tabs/tabs'
|
|
27
|
+
import type { textStyles } from './components/text/text.styles'
|
|
28
|
+
import type { textareaStyles } from './components/textarea/textarea'
|
|
29
|
+
import type { tooltipStyles } from './components/tooltip/tooltip'
|
|
30
|
+
import type {
|
|
31
|
+
appShellStyles,
|
|
32
|
+
navbarStyles,
|
|
33
|
+
pageHeaderStyles,
|
|
34
|
+
sidebarStyles,
|
|
35
|
+
} from './patterns/app-shell/app-shell.styles'
|
|
36
|
+
import type { stateStyles } from './patterns/async/states'
|
|
37
|
+
import type { StylesEntry } from './utils/registry'
|
|
38
|
+
|
|
39
|
+
declare module '@glyphui/tokens' {
|
|
40
|
+
interface GlyphComponentRegistry {
|
|
41
|
+
AppShell: StylesEntry<typeof appShellStyles>
|
|
42
|
+
Avatar: StylesEntry<typeof avatarStyles>
|
|
43
|
+
Badge: StylesEntry<typeof badgeStyles>
|
|
44
|
+
Button: StylesEntry<typeof buttonStyles>
|
|
45
|
+
Card: StylesEntry<typeof cardStyles>
|
|
46
|
+
Checkbox: StylesEntry<typeof checkboxStyles>
|
|
47
|
+
EmptyState: StylesEntry<typeof stateStyles>
|
|
48
|
+
Eyebrow: StylesEntry<typeof eyebrowStyles>
|
|
49
|
+
Field: StylesEntry<typeof fieldStyles>
|
|
50
|
+
Heading: StylesEntry<typeof headingStyles>
|
|
51
|
+
Input: StylesEntry<typeof inputStyles>
|
|
52
|
+
Kbd: StylesEntry<typeof kbdStyles>
|
|
53
|
+
Label: StylesEntry<typeof labelStyles>
|
|
54
|
+
Navbar: StylesEntry<typeof navbarStyles>
|
|
55
|
+
PageHeader: StylesEntry<typeof pageHeaderStyles>
|
|
56
|
+
SegmentedControl: StylesEntry<typeof segmentedControlStyles>
|
|
57
|
+
Select: StylesEntry<typeof selectStyles>
|
|
58
|
+
Separator: StylesEntry<typeof separatorStyles>
|
|
59
|
+
Sidebar: StylesEntry<typeof sidebarStyles>
|
|
60
|
+
Skeleton: StylesEntry<typeof skeletonStyles>
|
|
61
|
+
Spinner: StylesEntry<typeof spinnerStyles>
|
|
62
|
+
Switch: StylesEntry<typeof switchStyles>
|
|
63
|
+
Table: StylesEntry<typeof tableStyles>
|
|
64
|
+
Tabs: StylesEntry<typeof tabsStyles>
|
|
65
|
+
Text: StylesEntry<typeof textStyles>
|
|
66
|
+
Textarea: StylesEntry<typeof textareaStyles>
|
|
67
|
+
Tooltip: StylesEntry<typeof tooltipStyles>
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/themes.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import './register'
|
|
2
|
+
import { defineTheme, presets as tokenPresets } from '@glyphui/tokens'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Built-in themes: the token presets from @glyphui/tokens plus per-component recipes.
|
|
6
|
+
* Extend any of them in your theme file: `defineTheme({ name: 'acme', extends: presets.glyph, ... })`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Precise, dark-first, one electric-blue accent, layered soft shadows, Geist. */
|
|
10
|
+
export const glyph = tokenPresets.glyph
|
|
11
|
+
|
|
12
|
+
/** Zero radius, heavy borders, hard offset shadows, loud color. */
|
|
13
|
+
export const brutalist = defineTheme({
|
|
14
|
+
name: 'brutalist',
|
|
15
|
+
extends: tokenPresets.brutalist,
|
|
16
|
+
components: {
|
|
17
|
+
Button: {
|
|
18
|
+
className: {
|
|
19
|
+
base: 'font-bold uppercase tracking-wide shadow-sm hover:-translate-x-px hover:-translate-y-px hover:shadow-md active:translate-x-[3px] active:translate-y-[3px] active:shadow-none',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
Badge: { className: { base: 'font-bold uppercase' } },
|
|
23
|
+
Card: { defaultVariants: { variant: 'elevated' } },
|
|
24
|
+
Heading: { className: { base: 'uppercase' } },
|
|
25
|
+
Input: { className: { root: 'shadow-sm focus-within:ring-0 focus-within:shadow-md' } },
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
/** Serif display type, warm paper neutrals, deep green ink, hairlines instead of shadows. */
|
|
30
|
+
export const editorial = defineTheme({
|
|
31
|
+
name: 'editorial',
|
|
32
|
+
extends: tokenPresets.editorial,
|
|
33
|
+
components: {
|
|
34
|
+
Card: { defaultVariants: { variant: 'outline' } },
|
|
35
|
+
Eyebrow: { className: { base: 'font-sans tracking-[0.18em]' } },
|
|
36
|
+
Button: { className: { base: 'tracking-wide' } },
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
/** Pill shapes, lavender and mint, chunky rounded type, springy motion. */
|
|
41
|
+
export const playful = defineTheme({
|
|
42
|
+
name: 'playful',
|
|
43
|
+
extends: tokenPresets.playful,
|
|
44
|
+
components: {
|
|
45
|
+
Button: {
|
|
46
|
+
className: {
|
|
47
|
+
base: 'font-bold hover:-translate-y-0.5 active:translate-y-0 active:scale-[0.97]',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
Card: { defaultVariants: { variant: 'elevated' } },
|
|
51
|
+
Badge: { className: { base: 'font-semibold' } },
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
export const presets = { glyph, brutalist, editorial, playful } as const
|
|
56
|
+
export type PresetName = keyof typeof presets
|
package/src/utils/cn.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ClassValue, clsx } from 'clsx'
|
|
2
|
+
import { extendTailwindMerge } from 'tailwind-merge'
|
|
3
|
+
import { twMergeConfig } from './tw-merge-config'
|
|
4
|
+
|
|
5
|
+
const twMerge = extendTailwindMerge(twMergeConfig)
|
|
6
|
+
|
|
7
|
+
/** Joins class names and resolves Tailwind conflicts (last one wins). */
|
|
8
|
+
export function cn(...inputs: ClassValue[]): string {
|
|
9
|
+
return twMerge(clsx(inputs))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type { ClassValue }
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Ref, RefCallback } from 'react'
|
|
2
|
+
|
|
3
|
+
/** Combines several refs into one callback ref. */
|
|
4
|
+
export function composeRefs<T>(...refs: (Ref<T> | undefined)[]): RefCallback<T> {
|
|
5
|
+
return (node) => {
|
|
6
|
+
for (const ref of refs) {
|
|
7
|
+
if (typeof ref === 'function') ref(node)
|
|
8
|
+
else if (ref) ref.current = node
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ComponentEntry } from '@glyphui/tokens'
|
|
2
|
+
import type { VariantProps } from 'tailwind-variants'
|
|
3
|
+
|
|
4
|
+
/** Derives a registry entry (variant props + slot names) from a component's `tv()` styles. */
|
|
5
|
+
// biome-ignore lint/suspicious/noExplicitAny: matches tailwind-variants' own constraint
|
|
6
|
+
export type StylesEntry<T extends ((...args: any) => any) & { slots: unknown }> = ComponentEntry<
|
|
7
|
+
VariantProps<T>,
|
|
8
|
+
Extract<keyof NonNullable<T['slots']>, string>
|
|
9
|
+
>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Shared class fragments. Kept as literals so Tailwind's scanner picks them up. */
|
|
2
|
+
|
|
3
|
+
export const focusRing =
|
|
4
|
+
'outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background'
|
|
5
|
+
|
|
6
|
+
export const themedBorder = 'border-(length:--glyph-border-width)'
|
|
7
|
+
|
|
8
|
+
export const transition =
|
|
9
|
+
'transition-[color,background-color,border-color,box-shadow,transform,opacity] duration-(--glyph-duration-base) ease-standard'
|
|
10
|
+
|
|
11
|
+
/** Popover surfaces: Select, Tooltip-like menus. */
|
|
12
|
+
export const popover =
|
|
13
|
+
'z-50 overflow-hidden rounded-[min(var(--radius-md),var(--radius-lg))] border-(length:--glyph-border-width) border-border bg-raised text-foreground shadow-lg animate-pop-in'
|
package/src/utils/tv.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createTV, type VariantProps } from 'tailwind-variants'
|
|
2
|
+
import { twMergeConfig } from './tw-merge-config'
|
|
3
|
+
|
|
4
|
+
/** `tailwind-variants` preconfigured with Glyph's merge rules. All component styles use this. */
|
|
5
|
+
export const tv = createTV({ twMergeConfig })
|
|
6
|
+
|
|
7
|
+
export type { VariantProps }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { textTokens } from '@glyphui/tokens'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Teaches tailwind-merge about Glyph's custom utilities so `cn('text-h1', 'text-small')`
|
|
5
|
+
* keeps only the last size instead of treating `text-h1` as a color.
|
|
6
|
+
*/
|
|
7
|
+
export const twMergeConfig = {
|
|
8
|
+
extend: {
|
|
9
|
+
theme: {
|
|
10
|
+
text: [...textTokens],
|
|
11
|
+
radius: ['pill'],
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
}
|
package/styles.css
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Glyph ↔ Tailwind CSS v4 bridge. Import once, after tailwindcss:
|
|
3
|
+
*
|
|
4
|
+
* @import "tailwindcss";
|
|
5
|
+
* @import "@glyphui/ui/styles.css";
|
|
6
|
+
*
|
|
7
|
+
* Values come from the --glyph-* variables that <GlyphProvider theme={...}> injects, so one
|
|
8
|
+
* stylesheet serves every theme. Your own markup can use the same utilities (bg-primary,
|
|
9
|
+
* rounded-lg, text-h2, shadow-md, font-display, ...).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/* Generate the utilities Glyph's components use. */
|
|
13
|
+
@source "./src";
|
|
14
|
+
|
|
15
|
+
@custom-variant dark (&:where([data-mode="dark"], [data-mode="dark"] *));
|
|
16
|
+
|
|
17
|
+
@theme inline {
|
|
18
|
+
--color-background: var(--glyph-background);
|
|
19
|
+
--color-foreground: var(--glyph-foreground);
|
|
20
|
+
--color-subtle: var(--glyph-subtle);
|
|
21
|
+
--color-surface: var(--glyph-surface);
|
|
22
|
+
--color-raised: var(--glyph-raised);
|
|
23
|
+
--color-muted: var(--glyph-muted);
|
|
24
|
+
--color-muted-foreground: var(--glyph-muted-foreground);
|
|
25
|
+
--color-faint-foreground: var(--glyph-faint-foreground);
|
|
26
|
+
--color-border: var(--glyph-border);
|
|
27
|
+
--color-border-strong: var(--glyph-border-strong);
|
|
28
|
+
--color-input: var(--glyph-input);
|
|
29
|
+
--color-ring: var(--glyph-ring);
|
|
30
|
+
--color-primary: var(--glyph-primary);
|
|
31
|
+
--color-primary-foreground: var(--glyph-primary-foreground);
|
|
32
|
+
--color-secondary: var(--glyph-secondary);
|
|
33
|
+
--color-secondary-foreground: var(--glyph-secondary-foreground);
|
|
34
|
+
--color-success: var(--glyph-success);
|
|
35
|
+
--color-success-foreground: var(--glyph-success-foreground);
|
|
36
|
+
--color-warning: var(--glyph-warning);
|
|
37
|
+
--color-warning-foreground: var(--glyph-warning-foreground);
|
|
38
|
+
--color-danger: var(--glyph-danger);
|
|
39
|
+
--color-danger-foreground: var(--glyph-danger-foreground);
|
|
40
|
+
--color-info: var(--glyph-info);
|
|
41
|
+
--color-info-foreground: var(--glyph-info-foreground);
|
|
42
|
+
--color-overlay: var(--glyph-overlay);
|
|
43
|
+
|
|
44
|
+
--radius-xs: var(--glyph-radius-xs);
|
|
45
|
+
--radius-sm: var(--glyph-radius-sm);
|
|
46
|
+
--radius-md: var(--glyph-radius-md);
|
|
47
|
+
--radius-lg: var(--glyph-radius-lg);
|
|
48
|
+
--radius-xl: var(--glyph-radius-xl);
|
|
49
|
+
--radius-pill: var(--glyph-radius-pill);
|
|
50
|
+
|
|
51
|
+
--shadow-xs: var(--glyph-shadow-xs);
|
|
52
|
+
--shadow-sm: var(--glyph-shadow-sm);
|
|
53
|
+
--shadow-md: var(--glyph-shadow-md);
|
|
54
|
+
--shadow-lg: var(--glyph-shadow-lg);
|
|
55
|
+
|
|
56
|
+
--font-sans: var(--glyph-font-sans);
|
|
57
|
+
--font-display: var(--glyph-font-display);
|
|
58
|
+
--font-serif: var(--glyph-font-serif);
|
|
59
|
+
--font-mono: var(--glyph-font-mono);
|
|
60
|
+
|
|
61
|
+
--text-display: var(--glyph-text-display);
|
|
62
|
+
--text-display--line-height: var(--glyph-text-display-leading);
|
|
63
|
+
--text-display--letter-spacing: var(--glyph-text-display-tracking);
|
|
64
|
+
--text-display--font-weight: var(--glyph-text-display-weight);
|
|
65
|
+
--text-h1: var(--glyph-text-h1);
|
|
66
|
+
--text-h1--line-height: var(--glyph-text-h1-leading);
|
|
67
|
+
--text-h1--letter-spacing: var(--glyph-text-h1-tracking);
|
|
68
|
+
--text-h1--font-weight: var(--glyph-text-h1-weight);
|
|
69
|
+
--text-h2: var(--glyph-text-h2);
|
|
70
|
+
--text-h2--line-height: var(--glyph-text-h2-leading);
|
|
71
|
+
--text-h2--letter-spacing: var(--glyph-text-h2-tracking);
|
|
72
|
+
--text-h2--font-weight: var(--glyph-text-h2-weight);
|
|
73
|
+
--text-h3: var(--glyph-text-h3);
|
|
74
|
+
--text-h3--line-height: var(--glyph-text-h3-leading);
|
|
75
|
+
--text-h3--letter-spacing: var(--glyph-text-h3-tracking);
|
|
76
|
+
--text-h3--font-weight: var(--glyph-text-h3-weight);
|
|
77
|
+
--text-h4: var(--glyph-text-h4);
|
|
78
|
+
--text-h4--line-height: var(--glyph-text-h4-leading);
|
|
79
|
+
--text-h4--letter-spacing: var(--glyph-text-h4-tracking);
|
|
80
|
+
--text-h4--font-weight: var(--glyph-text-h4-weight);
|
|
81
|
+
--text-lead: var(--glyph-text-lead);
|
|
82
|
+
--text-lead--line-height: var(--glyph-text-lead-leading);
|
|
83
|
+
--text-lead--letter-spacing: var(--glyph-text-lead-tracking);
|
|
84
|
+
--text-lead--font-weight: var(--glyph-text-lead-weight);
|
|
85
|
+
--text-body: var(--glyph-text-body);
|
|
86
|
+
--text-body--line-height: var(--glyph-text-body-leading);
|
|
87
|
+
--text-body--letter-spacing: var(--glyph-text-body-tracking);
|
|
88
|
+
--text-body--font-weight: var(--glyph-text-body-weight);
|
|
89
|
+
--text-small: var(--glyph-text-small);
|
|
90
|
+
--text-small--line-height: var(--glyph-text-small-leading);
|
|
91
|
+
--text-small--letter-spacing: var(--glyph-text-small-tracking);
|
|
92
|
+
--text-small--font-weight: var(--glyph-text-small-weight);
|
|
93
|
+
--text-caption: var(--glyph-text-caption);
|
|
94
|
+
--text-caption--line-height: var(--glyph-text-caption-leading);
|
|
95
|
+
--text-caption--letter-spacing: var(--glyph-text-caption-tracking);
|
|
96
|
+
--text-caption--font-weight: var(--glyph-text-caption-weight);
|
|
97
|
+
|
|
98
|
+
--ease-standard: var(--glyph-ease-standard);
|
|
99
|
+
--ease-emphasized: var(--glyph-ease-emphasized);
|
|
100
|
+
|
|
101
|
+
--animate-pop-in: glyph-pop-in var(--glyph-duration-fast) var(--glyph-ease-standard);
|
|
102
|
+
--animate-fade-in: glyph-fade-in var(--glyph-duration-base) var(--glyph-ease-standard);
|
|
103
|
+
--animate-slide-in-left: glyph-slide-in-left var(--glyph-duration-slow) var(--glyph-ease-standard);
|
|
104
|
+
--animate-slide-in-top: glyph-slide-in-top var(--glyph-duration-slow) var(--glyph-ease-standard);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@keyframes glyph-pop-in {
|
|
108
|
+
from {
|
|
109
|
+
opacity: 0;
|
|
110
|
+
transform: translateY(-2px) scale(0.98);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
@keyframes glyph-fade-in {
|
|
114
|
+
from {
|
|
115
|
+
opacity: 0;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
@keyframes glyph-slide-in-left {
|
|
119
|
+
from {
|
|
120
|
+
transform: translateX(-100%);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
@keyframes glyph-slide-in-top {
|
|
124
|
+
from {
|
|
125
|
+
opacity: 0;
|
|
126
|
+
transform: translateY(-8px);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@layer base {
|
|
131
|
+
*,
|
|
132
|
+
::before,
|
|
133
|
+
::after {
|
|
134
|
+
border-color: var(--color-border);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
html {
|
|
138
|
+
font-family: var(--font-sans);
|
|
139
|
+
font-feature-settings: var(--glyph-font-features);
|
|
140
|
+
background-color: var(--color-background);
|
|
141
|
+
color: var(--color-foreground);
|
|
142
|
+
-webkit-font-smoothing: antialiased;
|
|
143
|
+
-moz-osx-font-smoothing: grayscale;
|
|
144
|
+
text-rendering: optimizeLegibility;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
body {
|
|
148
|
+
font-size: var(--text-body);
|
|
149
|
+
line-height: var(--text-body--line-height);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
h1,
|
|
153
|
+
h2,
|
|
154
|
+
h3,
|
|
155
|
+
h4,
|
|
156
|
+
h5,
|
|
157
|
+
h6 {
|
|
158
|
+
text-wrap: balance;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
p {
|
|
162
|
+
text-wrap: pretty;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
::selection {
|
|
166
|
+
background-color: color-mix(in oklab, var(--color-primary) 30%, transparent);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@media (prefers-reduced-motion: reduce) {
|
|
170
|
+
*,
|
|
171
|
+
::before,
|
|
172
|
+
::after {
|
|
173
|
+
animation-duration: 0.01ms !important;
|
|
174
|
+
animation-iteration-count: 1 !important;
|
|
175
|
+
transition-duration: 0.01ms !important;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|