Skip to content

Commit a35fb28

Browse files
committed
refactor(Callout): replace SCSS with tailwind-variants, adjust theming
1 parent ca816b2 commit a35fb28

File tree

8 files changed

+161
-290
lines changed

8 files changed

+161
-290
lines changed

lib/Callout/Callout.module.scss

Lines changed: 0 additions & 91 deletions
This file was deleted.

lib/Callout/Callout.tsx

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import clsx from 'clsx';
1+
import { tv } from 'tailwind-variants';
22
import {
33
CheckIcon,
44
CircleXIcon,
@@ -7,10 +7,56 @@ import {
77
LightbulbIcon,
88
TriangleAlertIcon,
99
} from 'lucide-react';
10-
import styles from './Callout.module.scss';
10+
import type { VariantProps } from 'tailwind-variants';
1111

12-
interface CalloutProps {
13-
variant?: 'info' | 'tip' | 'warning' | 'success' | 'error' | 'note';
12+
const callout = tv({
13+
slots: {
14+
base: 'mt-6 overflow-hidden rounded-xl border px-5 py-4 flex items-start space-x-3',
15+
icon: 'mt-0.5 h-5 w-5',
16+
content: 'flex-1 overflow-x-auto first:mt-0 last:mb-0',
17+
},
18+
variants: {
19+
variant: {
20+
info: {
21+
base: 'border-zinc-500/20 bg-zinc-50/50 dark:border-zinc-500/30 dark:bg-zinc-500/10',
22+
icon: 'text-zinc-500',
23+
content: 'text-zinc-900 dark:text-zinc-200',
24+
},
25+
tip: {
26+
base: 'border-emerald-500/20 bg-emerald-50/50 dark:border-emerald-500/30 dark:bg-emerald-500/10',
27+
icon: 'text-emerald-600',
28+
content: 'text-emerald-900 dark:text-emerald-200',
29+
},
30+
warning: {
31+
base: 'border-amber-500/20 bg-amber-50/50 dark:border-amber-500/30 dark:bg-amber-500/10',
32+
icon: 'text-amber-600',
33+
content: 'text-amber-900 dark:text-amber-200',
34+
},
35+
success: {
36+
base: 'border-green-500/20 bg-green-50/50 dark:border-green-500/30 dark:bg-green-500/10',
37+
icon: 'text-green-600',
38+
content: 'text-green-900 dark:text-green-200',
39+
},
40+
error: {
41+
base: 'border-red-500/20 bg-red-50/50 dark:border-red-500/30 dark:bg-red-500/10',
42+
icon: 'text-red-600',
43+
content: 'text-red-900 dark:text-red-200',
44+
},
45+
note: {
46+
base: 'border-sky-500/20 bg-sky-50/50 dark:border-sky-500/30 dark:bg-sky-500/10',
47+
icon: 'text-sky-600',
48+
content: 'text-sky-900 dark:text-sky-200',
49+
},
50+
},
51+
},
52+
defaultVariants: {
53+
variant: 'info',
54+
},
55+
});
56+
57+
type CalloutVariant = VariantProps<typeof callout>;
58+
59+
interface CalloutProps extends CalloutVariant {
1460
className?: string;
1561
children: React.ReactNode | React.ReactNode[];
1662
}
@@ -36,16 +82,20 @@ const variants = {
3682
},
3783
} as const;
3884

39-
const Callout = ({ variant = 'info', className, children }: CalloutProps) => {
85+
const Callout = ({ variant = 'info', children }: CalloutProps) => {
4086
const Icon = variants[variant].icon;
4187

88+
const { base, icon, content } = callout({
89+
variant: variant,
90+
});
91+
4292
return (
43-
<div className={clsx(styles['callout'], styles[`${variant}`], className)}>
93+
<div className={base()}>
4494
<Icon
4595
size={20}
46-
className={styles['calloutIcon']}
96+
className={icon()}
4797
/>
48-
<div className={styles['calloutContent']}>{children}</div>
98+
<div className={content()}>{children}</div>
4999
</div>
50100
);
51101
};

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@
8686
"react-medium-image-zoom": "^5.2.12",
8787
"react-router": "^7.0.2",
8888
"remark-gfm": "^4.0.0",
89-
"slugify": "^1.6.6"
89+
"slugify": "^1.6.6",
90+
"tailwind-variants": "^0.3.0"
9091
},
9192
"publishConfig": {
9293
"access": "public"

src/components/SideNav.tsx

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { NavLink } from 'react-router';
2+
import { useTheme } from './Theme';
23

34
export const components = [
45
{
@@ -46,6 +47,11 @@ export const components = [
4647
}[];
4748

4849
export const SideNav = () => {
50+
const theme = useTheme();
51+
const setTheme = (themeString: 'dark' | 'light') => {
52+
theme.toggleTheme(themeString);
53+
};
54+
4955
return (
5056
<div className="min-w-[220px]">
5157
<div className="sticky top-10">
@@ -57,14 +63,32 @@ export const SideNav = () => {
5763
key={component.name}
5864
className={({ isActive }) => {
5965
const classNames =
60-
'text-gray-700 hover:text-gray-900 px-4 py-1.5 hover:bg-slate-100 -ml-4 rounded-full';
61-
return isActive ? `${classNames} bg-slate-100 font-medium` : classNames;
66+
'text-gray-700 dark:text-gray-200 dark:hover:text-gray-800 hover:text-gray-900 px-4 py-1.5 hover:bg-slate-100 -ml-4 rounded-full';
67+
return isActive
68+
? `${classNames} bg-slate-100 dark:text-gray-700 font-medium`
69+
: classNames;
6270
}}
6371
>
6472
{component.name}
6573
</NavLink>
6674
))}
6775
</div>
76+
77+
<div className="mt-10">
78+
<button
79+
className="text-xs uppercase"
80+
onClick={() => setTheme('dark')}
81+
>
82+
Dark
83+
</button>{' '}
84+
|{' '}
85+
<button
86+
className="text-xs uppercase"
87+
onClick={() => setTheme('light')}
88+
>
89+
Light
90+
</button>
91+
</div>
6892
</div>
6993
</div>
7094
);

src/components/Theme.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { createContext, useContext, ReactNode, useState, useEffect } from 'react';
2+
3+
type Theme = 'light' | 'dark';
4+
5+
interface ThemeContextProps {
6+
theme: Theme;
7+
toggleTheme: (_theme: Theme) => void;
8+
}
9+
10+
const ThemeContext = createContext<ThemeContextProps | undefined>(undefined);
11+
12+
export const ThemeProvider = ({ children }: { children: ReactNode }) => {
13+
const localTheme = localStorage.getItem('theme') as Theme;
14+
const [theme, setTheme] = useState<Theme>(localTheme ?? 'light');
15+
16+
useEffect(() => {
17+
if (localTheme) {
18+
setTheme(localTheme);
19+
toggleTheme(localTheme);
20+
}
21+
}, []);
22+
23+
const toggleTheme = (_theme: 'dark' | 'light') => {
24+
localStorage.setItem('theme', _theme);
25+
setTheme(_theme);
26+
27+
document.documentElement.classList.add(_theme);
28+
document.documentElement.classList.remove(_theme === 'dark' ? 'light' : 'dark');
29+
document.documentElement.style.colorScheme = _theme;
30+
};
31+
32+
return <ThemeContext.Provider value={{ theme, toggleTheme }}>{children}</ThemeContext.Provider>;
33+
};
34+
35+
export const useTheme = () => {
36+
const context = useContext(ThemeContext);
37+
if (!context) {
38+
throw new Error('useTheme must be used within a ThemeProvider');
39+
}
40+
return context;
41+
};

src/main.tsx

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { StepSamples } from './pages/StepSamples.tsx';
1010
import { TypographySamples } from './pages/TypographySamples.tsx';
1111
import { ImageSamples } from './pages/ImageSamples.tsx';
1212
import { CodeSamples } from './pages/CodeSamples.tsx';
13+
import { ThemeProvider } from './components/Theme.tsx';
1314

1415
const components: { [key: string]: React.ReactElement } = {
1516
accordion: <AccordionSamples />,
@@ -23,26 +24,28 @@ const components: { [key: string]: React.ReactElement } = {
2324

2425
createRoot(document.getElementById('root')!).render(
2526
<BrowserRouter>
26-
<Routes>
27-
<Route
28-
path="/"
29-
element={<App />}
30-
/>
27+
<ThemeProvider>
28+
<Routes>
29+
<Route
30+
path="/"
31+
element={<App />}
32+
/>
3133

32-
<Route
33-
path="components"
34-
element={<ComponentsLayout />}
35-
>
36-
{Object.keys(components).map((key) => {
37-
return (
38-
<Route
39-
key={key}
40-
path={key}
41-
element={components[key]}
42-
/>
43-
);
44-
})}
45-
</Route>
46-
</Routes>
34+
<Route
35+
path="components"
36+
element={<ComponentsLayout />}
37+
>
38+
{Object.keys(components).map((key) => {
39+
return (
40+
<Route
41+
key={key}
42+
path={key}
43+
element={components[key]}
44+
/>
45+
);
46+
})}
47+
</Route>
48+
</Routes>
49+
</ThemeProvider>
4750
</BrowserRouter>,
4851
);

tailwind.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import tailwindTypography from '@tailwindcss/typography';
33
/** @type {import('tailwindcss').Config} */
44
export default {
55
content: ['./lib/**/*.tsx', './src/**/*.tsx'],
6+
darkMode: 'class',
67
theme: {
78
extend: {},
89
},

0 commit comments

Comments
 (0)