Files
react-test/docs/pages/experiments/joy/style-guide.tsx
how2ice 005cf56baf
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
init project
2025-12-12 14:26:25 +09:00

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>
);
}