Some checks failed
No response / noResponse (push) Has been cancelled
CI / Continuous releases (push) Has been cancelled
CI / test-dev (macos-latest) (push) Has been cancelled
CI / test-dev (ubuntu-latest) (push) Has been cancelled
CI / test-dev (windows-latest) (push) Has been cancelled
Maintenance / main (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
216 lines
6.1 KiB
TypeScript
216 lines
6.1 KiB
TypeScript
import * as React from 'react';
|
|
import Container from '@mui/joy/Container';
|
|
import Box from '@mui/joy/Box';
|
|
import Button from '@mui/joy/Button';
|
|
import Typography from '@mui/joy/Typography';
|
|
import {
|
|
CssVarsProvider,
|
|
useColorScheme,
|
|
useTheme,
|
|
TypographySystem,
|
|
createGetCssVar,
|
|
} from '@mui/joy/styles';
|
|
|
|
const getCssVar = createGetCssVar();
|
|
|
|
const rgb2hex = (rgb: string) =>
|
|
`#${(rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/) || [])
|
|
.slice(1)
|
|
.map((n) => parseInt(n, 10).toString(16).padStart(2, '0'))
|
|
.join('')}`;
|
|
|
|
function ColorSchemePicker() {
|
|
const { mode, setMode } = useColorScheme();
|
|
const [mounted, setMounted] = React.useState(false);
|
|
React.useEffect(() => {
|
|
setMounted(true);
|
|
}, []);
|
|
if (!mounted) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<Box
|
|
sx={(theme) => ({
|
|
position: 'relative',
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
border: '1px solid',
|
|
borderRadius: theme.vars.radius.md,
|
|
...theme.variants.outlined.neutral,
|
|
})}
|
|
>
|
|
<Box sx={{ display: 'flex', gap: '8px', p: '4px' }}>
|
|
{(['light', 'dark'] as const).map((modeId) => {
|
|
return (
|
|
<Button
|
|
key={modeId}
|
|
size="sm"
|
|
variant={mode === modeId ? 'solid' : 'plain'}
|
|
onClick={() => {
|
|
setMode(modeId);
|
|
}}
|
|
>
|
|
{modeId}
|
|
</Button>
|
|
);
|
|
})}
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
function ColorToken({ name, value }: { name: string; value: string }) {
|
|
const [color, setColor] = React.useState('');
|
|
const ref = React.useRef<HTMLDivElement>(null);
|
|
React.useEffect(() => {
|
|
if (ref.current && typeof window !== 'undefined') {
|
|
const style = window.getComputedStyle(ref.current);
|
|
setColor(rgb2hex(style.backgroundColor));
|
|
}
|
|
}, []);
|
|
return (
|
|
<div>
|
|
<Box
|
|
ref={ref}
|
|
sx={{ borderRadius: 'sm', bgcolor: value, width: 64, height: 64, mb: 1, boxShadow: 'sm' }}
|
|
/>
|
|
<Typography level="body-xs">{name}</Typography>
|
|
<Typography level="body-xs">{color}</Typography>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function PaletteTokens() {
|
|
const { colorScheme } = useColorScheme();
|
|
const { palette } = useTheme();
|
|
const [mounted, setMounted] = React.useState(false);
|
|
React.useEffect(() => {
|
|
setMounted(true);
|
|
}, []);
|
|
return (
|
|
<React.Fragment>
|
|
{mounted && (
|
|
<Typography level="title-md" sx={{ mb: 1 }}>
|
|
Palette ({colorScheme})
|
|
</Typography>
|
|
)}
|
|
<div>
|
|
{Object.entries(palette).map(([key, nestedObj]) => {
|
|
if (typeof nestedObj === 'string' && mounted) {
|
|
return <ColorToken key={key} name={key} value={nestedObj} />;
|
|
}
|
|
return (
|
|
<details key={key} style={{ padding: '0.5rem 0' }}>
|
|
<summary
|
|
style={{
|
|
marginBottom: '0.5rem',
|
|
fontFamily: getCssVar('fontFamily-body'),
|
|
cursor: 'pointer',
|
|
}}
|
|
>
|
|
{key}
|
|
</summary>
|
|
|
|
{key !== 'mode' && key !== 'colorScheme' && (
|
|
<Box
|
|
sx={{
|
|
display: 'grid',
|
|
gridTemplateColumns: 'repeat(auto-fill, minmax(120px, 1fr))',
|
|
gap: 2,
|
|
}}
|
|
>
|
|
{Object.entries(nestedObj).map(([nestedKey, value]) => (
|
|
<ColorToken key={nestedKey} name={nestedKey} value={value as string} />
|
|
))}
|
|
</Box>
|
|
)}
|
|
</details>
|
|
);
|
|
})}
|
|
</div>
|
|
</React.Fragment>
|
|
);
|
|
}
|
|
|
|
function TypographyScale() {
|
|
const { typography } = useTheme();
|
|
return (
|
|
<React.Fragment>
|
|
<Typography level="title-md" sx={{ mb: 1 }}>
|
|
Typography
|
|
</Typography>
|
|
|
|
{(Object.keys(typography) as Array<keyof TypographySystem>).map((level) => {
|
|
return (
|
|
<Typography key={level} level={level}>
|
|
{level}
|
|
</Typography>
|
|
);
|
|
})}
|
|
</React.Fragment>
|
|
);
|
|
}
|
|
|
|
export default function JoyStyleGuide() {
|
|
return (
|
|
<CssVarsProvider>
|
|
<Container>
|
|
<Box
|
|
sx={{
|
|
position: 'sticky',
|
|
top: 0,
|
|
minHeight: 56,
|
|
borderBottom: '1px solid',
|
|
borderColor: 'neutral.outlinedBorder',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
bgcolor: 'background.body',
|
|
}}
|
|
>
|
|
<ColorSchemePicker />
|
|
</Box>
|
|
<Box sx={{ p: 2 }}>
|
|
<PaletteTokens />
|
|
</Box>
|
|
<Box sx={{ p: 2, display: 'flex', gap: 3 }}>
|
|
<Box sx={{ minWidth: 300 }}>
|
|
<TypographyScale />
|
|
</Box>
|
|
<div>
|
|
<Typography level="title-md" sx={{ mb: 1 }}>
|
|
UI Patterns
|
|
</Typography>
|
|
<Box sx={{ display: 'flex', gap: 2 }}>
|
|
<Box sx={{ width: 48, height: 48, bgcolor: 'background.level2' }} />
|
|
<div>
|
|
<Typography>List item title</Typography>
|
|
<Typography level="body-sm">Secondary text.</Typography>
|
|
</div>
|
|
</Box>
|
|
<hr />
|
|
<Box sx={{ display: 'flex', gap: 2, minWidth: 300 }}>
|
|
<Box sx={{ width: 48, height: 48, bgcolor: 'background.level2' }} />
|
|
<Box sx={{ flexGrow: 1 }}>
|
|
<Typography>List item title</Typography>
|
|
<Typography level="body-sm">Secondary text.</Typography>
|
|
</Box>
|
|
<Typography level="body-xs">metadata</Typography>
|
|
</Box>
|
|
<hr />
|
|
<Box sx={{ display: 'flex', gap: 2 }}>
|
|
<Box sx={{ width: 64, height: 64, bgcolor: 'background.level2' }} />
|
|
<Box sx={{ flexGrow: 1 }}>
|
|
<Typography>List item title</Typography>
|
|
<Typography level="body-sm">Secondary text.</Typography>
|
|
<Typography level="body-xs">metadata</Typography>
|
|
</Box>
|
|
</Box>
|
|
</div>
|
|
</Box>
|
|
</Container>
|
|
</CssVarsProvider>
|
|
);
|
|
}
|