Files
zeekay 464e1eece0 State a version once
A version written into a manifest is written into every manifest that wants it,
which is how eslint came to exist in a hundred and four forms across this estate
and how a package came to resolve twice at run time. The catalog moves the number
out of the manifest: a manifest names the package, the workspace names the
version.

Four are held below their latest because the newer release cannot be used, and
the reason is recorded rather than the version alone: @noble/hashes 2 renamed
every subpath the web3 stack imports, @splinetool/runtime 2 depends on a package
that was never published, @dynamic-labs 5.4.1 the same, and react-resizable-panels
3 points its types condition at the bundle.
2026-09-15 11:45:32 -07:00

364 lines
9.8 KiB
TypeScript

import { colorTokens, semanticTokens } from '@signozhq/design-tokens';
import { Button } from '@signozhq/ui';
import { useMemo, useState } from 'react';
import type { TokenData } from './TokenRow.js';
import { TokenSearch } from './TokenSearch.js';
import { TokenTable } from './TokenTable.js';
type ThemeMode = 'light' | 'dark';
export type SemanticTokenGroup =
| 'surface-levels'
| 'actions'
| 'callouts'
| 'accents'
| 'sidebar'
| 'charts'
| 'fonts'
| 'base'
| 'alerts'
| 'legacy';
export interface TokenReferenceProps {
groups?: SemanticTokenGroup[];
mode?: 'semantic' | 'primitive' | 'all';
showThemeToggle?: boolean;
showSearch?: boolean;
showCategoryFilter?: boolean;
showModeTabs?: boolean;
showHeader?: boolean;
title?: string;
description?: string;
}
interface TokenWithMetadata {
value: string;
description?: string;
usage?: string;
dontUse?: string;
category?: string;
group?: string;
}
type TokenValue = string | TokenWithMetadata;
function getTokenValue(token: TokenValue): string {
return typeof token === 'string' ? token : token.value;
}
function getTokenMetadata(token: TokenValue): Omit<TokenWithMetadata, 'value'> {
if (typeof token === 'string') {
return {};
}
const { value: _, ...metadata } = token;
return metadata;
}
function parseSemanticTokens(mode: ThemeMode): TokenData[] {
const themeTokens = semanticTokens.default?.[mode]?.tokens;
if (!themeTokens) return [];
return Object.entries(themeTokens).map(([name, token]) => ({
name,
value: getTokenValue(token as TokenValue),
...getTokenMetadata(token as TokenValue),
}));
}
interface ColorShade {
value: string;
type: string;
description?: string;
}
function parsePrimitiveColors(): TokenData[] {
const tokens: TokenData[] = [];
const bg = colorTokens.bg as Record<string, Record<string, ColorShade>>;
for (const [colorName, shades] of Object.entries(bg)) {
for (const [shadeName, shadeData] of Object.entries(shades)) {
tokens.push({
name: `bg-${colorName.toLowerCase()}-${shadeName}`,
value: shadeData.value,
description: shadeData.description || `${colorName} ${shadeName}`,
category: 'background',
group: colorName.toLowerCase(),
});
}
}
return tokens;
}
const GROUPS_ORDER: SemanticTokenGroup[] = [
'surface-levels',
'actions',
'callouts',
'accents',
'sidebar',
'charts',
'fonts',
'base',
'alerts',
'legacy',
];
function groupTokensByGroup(tokens: TokenData[]): Map<string, TokenData[]> {
const grouped = new Map<string, TokenData[]>();
for (const token of tokens) {
const group = token.group || 'other';
if (!grouped.has(group)) {
grouped.set(group, []);
}
grouped.get(group)!.push(token);
}
const sortedGroups = new Map<string, TokenData[]>();
for (const group of GROUPS_ORDER) {
if (grouped.has(group)) {
sortedGroups.set(group, grouped.get(group)!);
}
}
for (const [group, tokens] of grouped) {
if (!sortedGroups.has(group)) {
sortedGroups.set(group, tokens);
}
}
return sortedGroups;
}
export const GROUP_LABELS: Record<string, string> = {
'surface-levels': 'Surface Levels (L1, L2, L3)',
actions: 'Action Colors (Primary, Secondary, Success, Warning, Danger)',
callouts: 'Callout Components',
accents: 'Accent Colors',
sidebar: 'Sidebar',
charts: 'Chart Colors',
fonts: 'Typography',
base: 'Base Colors',
alerts: 'Alerts',
legacy: 'Legacy Tokens',
};
export const GROUP_DESCRIPTIONS: Record<string, string> = {
'surface-levels':
'Hierarchical background, foreground, and border colors for layered UI surfaces.',
actions: 'Colors for interactive elements like buttons, links, and form controls.',
callouts: 'Styled containers for informational, success, warning, and error messages.',
accents: 'Brand and semantic accent colors for highlights and data visualization.',
sidebar: 'Tokens specific to sidebar navigation components.',
charts: 'Color palette optimized for data visualization and charts.',
fonts: 'Typography font family definitions.',
base: 'Fundamental black and white base colors.',
alerts: 'Alert strip and notification styling tokens.',
legacy: 'Deprecated tokens maintained for backward compatibility. Migrate to semantic tokens.',
};
export function TokenReference({
groups,
mode = 'all',
showThemeToggle = true,
showSearch = true,
showCategoryFilter = true,
showModeTabs = true,
showHeader = false,
title = 'Token Reference',
description = 'Design tokens for building consistent UI',
}: TokenReferenceProps) {
const [themeMode, setThemeMode] = useState<ThemeMode>('dark');
const [searchQuery, setSearchQuery] = useState('');
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const [activeSection, setActiveSection] = useState<'semantic' | 'primitive'>(
mode === 'primitive' ? 'primitive' : 'semantic'
);
const semanticTokensData = useMemo(() => parseSemanticTokens(themeMode), [themeMode]);
const primitiveTokensData = useMemo(() => parsePrimitiveColors(), []);
const filteredByGroups = useMemo(() => {
if (!groups || groups.length === 0) {
return semanticTokensData;
}
return semanticTokensData.filter(
(token) => token.group && groups.includes(token.group as SemanticTokenGroup)
);
}, [semanticTokensData, groups]);
const categories = useMemo(() => {
const tokensToCheck = groups ? filteredByGroups : semanticTokensData;
const cats = new Set<string>();
for (const token of tokensToCheck) {
if (token.category) cats.add(token.category);
}
return Array.from(cats).sort();
}, [filteredByGroups, semanticTokensData, groups]);
const shouldShowSemantic = mode === 'all' || mode === 'semantic';
const shouldShowPrimitive = mode === 'all' || mode === 'primitive';
const effectiveActiveSection =
mode === 'semantic' ? 'semantic' : mode === 'primitive' ? 'primitive' : activeSection;
const filteredTokens = useMemo(() => {
const baseTokens =
effectiveActiveSection === 'semantic' ? filteredByGroups : primitiveTokensData;
return baseTokens.filter((token) => {
const matchesSearch =
!searchQuery ||
token.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
token.description?.toLowerCase().includes(searchQuery.toLowerCase()) ||
token.value.toLowerCase().includes(searchQuery.toLowerCase());
const matchesCategory = !selectedCategory || token.category === selectedCategory;
return matchesSearch && matchesCategory;
});
}, [
effectiveActiveSection,
filteredByGroups,
primitiveTokensData,
searchQuery,
selectedCategory,
]);
const groupedTokens = useMemo(() => groupTokensByGroup(filteredTokens), [filteredTokens]);
const showTabs = showModeTabs && mode === 'all';
return (
<div
style={{
minHeight: '100vh',
backgroundColor: 'var(--l1-background)',
padding: 24,
paddingTop: 0,
}}
>
<div className="stack-24" style={{ maxWidth: 1280, marginInline: 'auto' }}>
{showHeader && (
<div className="stack-to-row" style={{ gap: 16 }}>
<div>
<h1
style={{
fontSize: 24,
lineHeight: 1.33333,
fontWeight: 700,
color: 'var(--l1-foreground)',
}}
>
{title}
</h1>
<p
style={{
fontSize: 14,
lineHeight: 1.42857,
color: 'var(--l2-foreground)',
marginTop: 4,
}}
>
{description}
</p>
</div>
</div>
)}
{showTabs && (
<div
style={{
display: 'flex',
gap: 8,
borderBottomStyle: 'solid',
borderBottomWidth: 1,
borderColor: 'var(--l2-border)',
}}
>
{shouldShowSemantic && (
<Button
onClick={() => {
setActiveSection('semantic');
setSelectedCategory(null);
}}
variant={activeSection === 'semantic' ? 'solid' : 'link'}
color={activeSection === 'semantic' ? 'primary' : 'secondary'}
>
Semantic Tokens
</Button>
)}
{shouldShowPrimitive && (
<Button
type="button"
onClick={() => {
setActiveSection('primitive');
setSelectedCategory(null);
}}
variant={activeSection === 'primitive' ? 'solid' : 'ghost'}
color={activeSection === 'primitive' ? 'primary' : 'secondary'}
>
Primitive Colors
</Button>
)}
</div>
)}
{showSearch && (
<TokenSearch
onSearch={setSearchQuery}
onCategoryFilter={showCategoryFilter ? setSelectedCategory : undefined}
categories={showCategoryFilter ? categories : []}
selectedCategory={selectedCategory}
/>
)}
<div className="stack-to-row" style={{ gap: 12 }}>
<div style={{ fontSize: 14, lineHeight: 1.42857, color: 'var(--l3-foreground)' }}>
Showing {filteredTokens.length} tokens
{searchQuery && ` matching "${searchQuery}"`}
{selectedCategory && ` in ${selectedCategory}`}
</div>
{showThemeToggle && (
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
<div
style={{
display: 'flex',
borderRadius: 4,
borderStyle: 'solid',
borderWidth: 1,
borderColor: 'var(--l2-border)',
overflow: 'hidden',
}}
>
<Button
type="button"
onClick={() => setThemeMode('light')}
variant={themeMode === 'light' ? 'solid' : 'ghost'}
color={themeMode === 'light' ? 'primary' : 'secondary'}
>
Light
</Button>
<Button
type="button"
onClick={() => setThemeMode('dark')}
variant={themeMode === 'dark' ? 'solid' : 'ghost'}
color={themeMode === 'dark' ? 'primary' : 'secondary'}
>
Dark
</Button>
</div>
</div>
)}
</div>
<div className="stack-24">
{Array.from(groupedTokens.entries()).map(([group, tokens]) => (
<TokenTable key={group} tokens={tokens} title={GROUP_LABELS[group] || group} />
))}
</div>
</div>
</div>
);
}