@umijs/plugin-docs 4.0.0-beta.18 → 4.0.0-canary.20220317.2

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.
Files changed (40) hide show
  1. package/client/theme-blog/index.ts +1 -0
  2. package/client/theme-doc/Github.tsx +18 -0
  3. package/client/theme-doc/Head.tsx +72 -0
  4. package/client/theme-doc/LangSwitch.tsx +62 -0
  5. package/client/theme-doc/Layout.tsx +120 -0
  6. package/client/theme-doc/Logo.tsx +22 -0
  7. package/client/theme-doc/NavBar.tsx +27 -0
  8. package/client/theme-doc/Search.tsx +178 -0
  9. package/client/theme-doc/Sidebar.tsx +84 -0
  10. package/client/theme-doc/ThemeSwitch.tsx +60 -0
  11. package/client/theme-doc/Tip.tsx +5 -0
  12. package/client/theme-doc/Toc.tsx +63 -0
  13. package/client/theme-doc/VersionSwitch.tsx +5 -0
  14. package/client/theme-doc/components/Announcement.tsx +62 -0
  15. package/client/theme-doc/components/FeatureItem.tsx +48 -0
  16. package/client/theme-doc/components/Features.tsx +44 -0
  17. package/client/theme-doc/components/Hero.tsx +152 -0
  18. package/client/theme-doc/components/Message.tsx +56 -0
  19. package/client/theme-doc/context.ts +40 -0
  20. package/client/theme-doc/firefox-polyfill.css +32 -0
  21. package/client/theme-doc/icons/github.svg +1 -0
  22. package/client/theme-doc/icons/hero-bg.svg +1 -0
  23. package/client/theme-doc/icons/moon.png +0 -0
  24. package/client/theme-doc/icons/star.png +0 -0
  25. package/client/theme-doc/icons/sun.png +0 -0
  26. package/client/theme-doc/icons/umi.png +0 -0
  27. package/client/theme-doc/index.ts +8 -0
  28. package/client/theme-doc/tailwind.css +199 -0
  29. package/client/theme-doc/tailwind.out.css +2093 -0
  30. package/client/theme-doc/useLanguage.ts +74 -0
  31. package/compiled/@mdx-js/mdx/LICENSE +21 -0
  32. package/compiled/@mdx-js/mdx/index.js +8 -0
  33. package/compiled/@mdx-js/mdx/package.json +1 -0
  34. package/compiled/rehype-slug/LICENSE +22 -0
  35. package/compiled/rehype-slug/index.js +1 -0
  36. package/compiled/rehype-slug/package.json +1 -0
  37. package/dist/compiler.d.ts +1 -1
  38. package/dist/compiler.js +22 -11
  39. package/dist/index.js +54 -18
  40. package/package.json +26 -9
@@ -0,0 +1 @@
1
+ export function $Layout() {}
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { useThemeContext } from './context';
3
+ // @ts-ignore
4
+ import GithubIcon from './icons/github.svg';
5
+
6
+ export default () => {
7
+ const ctx = useThemeContext();
8
+
9
+ if (!ctx?.themeConfig.github) {
10
+ return null;
11
+ }
12
+
13
+ return (
14
+ <a href={ctx.themeConfig.github}>
15
+ <img className="dark:invert" src={GithubIcon} alt="Github" />
16
+ </a>
17
+ );
18
+ };
@@ -0,0 +1,72 @@
1
+ import cx from 'classnames';
2
+ import React from 'react';
3
+ import Github from './Github';
4
+ import LangSwitch from './LangSwitch';
5
+ import Logo from './Logo';
6
+ import NavBar from './NavBar';
7
+ import Search from './Search';
8
+ import ThemeSwitch from './ThemeSwitch';
9
+
10
+ interface HeadProps {
11
+ isMenuOpened: boolean;
12
+ setMenuOpened: React.Dispatch<React.SetStateAction<boolean>>;
13
+ }
14
+
15
+ export default (props: HeadProps) => {
16
+ return (
17
+ <div
18
+ className="w-full flex flex-row items-center justify-between
19
+ border-b-gray-100 border-b-2 pt-4 pb-4 px-8 dark:border-b-gray-800"
20
+ >
21
+ <Logo />
22
+ <div className="flex flex-row items-center">
23
+ <Search />
24
+ <HamburgerButton {...props} />
25
+ <div className="hidden lg:block">
26
+ <NavBar />
27
+ </div>
28
+ <div className="ml-4 hidden lg:block">
29
+ <LangSwitch />
30
+ </div>
31
+ <div className="ml-4 hidden lg:block">
32
+ <ThemeSwitch />
33
+ </div>
34
+ <div className="ml-4 hidden lg:block">
35
+ <Github />
36
+ </div>
37
+ </div>
38
+ </div>
39
+ );
40
+ };
41
+
42
+ interface HamburgerButtonProps {
43
+ isMenuOpened: boolean;
44
+ setMenuOpened: React.Dispatch<React.SetStateAction<boolean>>;
45
+ }
46
+
47
+ function HamburgerButton(props: HamburgerButtonProps) {
48
+ const barClass =
49
+ 'block absolute h-0.5 w-5 bg-current transform dark:bg-white' +
50
+ ' transition duration-500 ease-in-out';
51
+
52
+ return (
53
+ <div
54
+ className="relative py-3 sm:max-w-xl mx-auto mx-5 lg:hidden"
55
+ onClick={() => props.setMenuOpened((o) => !o)}
56
+ >
57
+ <span
58
+ className={cx(
59
+ barClass,
60
+ props.isMenuOpened ? 'rotate-45 ' : '-translate-y-1.5',
61
+ )}
62
+ />
63
+ <span className={cx(barClass, props.isMenuOpened && 'opacity-0')} />
64
+ <span
65
+ className={cx(
66
+ barClass,
67
+ props.isMenuOpened ? '-rotate-45' : 'translate-y-1.5',
68
+ )}
69
+ />
70
+ </div>
71
+ );
72
+ }
@@ -0,0 +1,62 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import useLanguage from './useLanguage';
3
+
4
+ export default () => {
5
+ const { currentLanguage, languages, switchLanguage, isFromPath } =
6
+ useLanguage();
7
+ const [isExpanded, setExpanded] = useState(false);
8
+
9
+ // 首次加载时,根据 localstorage 记录的上次语言自动切换
10
+ useEffect(() => {
11
+ const locale = window.localStorage.getItem('umi_locale');
12
+ if (locale && !isFromPath) switchLanguage(locale);
13
+ }, []);
14
+
15
+ if (!currentLanguage) {
16
+ return null;
17
+ }
18
+
19
+ function handleClick() {
20
+ if (!currentLanguage) return;
21
+ if (languages.length === 2) {
22
+ switchLanguage(
23
+ languages[0].locale === currentLanguage.locale
24
+ ? languages[1].locale
25
+ : languages[0].locale,
26
+ );
27
+ return;
28
+ }
29
+ setExpanded((e) => !e);
30
+ }
31
+
32
+ return (
33
+ <div>
34
+ <div
35
+ className="w-24 rounded-lg overflow-hidden cursor-pointer border text-center
36
+ border-white hover:border-gray-100 dark:border-gray-800"
37
+ onClick={handleClick}
38
+ >
39
+ <p className="px-2 py-1 dark:text-white">{currentLanguage.text}</p>
40
+ </div>
41
+ <div
42
+ className={
43
+ 'absolute transition-all duration-300 bottom-[-12] w-24 rounded-lg' +
44
+ ' cursor-pointer shadow overflow-hidden ' +
45
+ (isExpanded ? ` max-h-${(languages.length - 1) * 12}` : ' max-h-0 ')
46
+ }
47
+ >
48
+ {languages
49
+ .filter((l) => l.locale !== currentLanguage.locale)
50
+ .map((lang) => (
51
+ <p
52
+ onClick={() => switchLanguage(lang.locale)}
53
+ key={lang.locale}
54
+ className="p-2 bg-white dark:bg-gray-700 dark:text-white hover:bg-gray-50 transition duration-300"
55
+ >
56
+ {lang.text}
57
+ </p>
58
+ ))}
59
+ </div>
60
+ </div>
61
+ );
62
+ };
@@ -0,0 +1,120 @@
1
+ import cx from 'classnames';
2
+ import React, { Fragment, useEffect, useState } from 'react';
3
+ import { Helmet } from 'react-helmet';
4
+ import Announcement from './components/Announcement';
5
+ import { ThemeContext } from './context';
6
+ import Head from './Head';
7
+ import Sidebar from './Sidebar';
8
+ import Toc from './Toc';
9
+
10
+ export default (props: any) => {
11
+ const [isMenuOpened, setIsMenuOpened] = useState(false);
12
+
13
+ /**
14
+ FireFox CSS backdrop-filter polyfill
15
+ https://www.cnblogs.com/coco1s/p/14953143.html
16
+ */
17
+ useEffect(() => {
18
+ let blur = document.getElementById('firefox-head-bg')?.style;
19
+ let offset = document.getElementById('head-container');
20
+
21
+ function updateBlur() {
22
+ if (!offset || !blur) return;
23
+ blur.backgroundPosition = `0px ` + `${-window.scrollY + 100}px`;
24
+ }
25
+
26
+ document.addEventListener('scroll', updateBlur, false), updateBlur();
27
+ return () => {
28
+ document.removeEventListener('scroll', updateBlur, false);
29
+ };
30
+ }, []);
31
+
32
+ const { title, description } = props.themeConfig;
33
+
34
+ const isHomePage =
35
+ window.location.pathname === '/' ||
36
+ window.location.pathname.replace(/[a-z]{2}-[A-Z]{2}\/?/, '') === '/';
37
+
38
+ return (
39
+ <ThemeContext.Provider
40
+ value={{
41
+ appData: props.appData,
42
+ components: props.components,
43
+ themeConfig: props.themeConfig,
44
+ location: props.location,
45
+ }}
46
+ >
47
+ <div className="flex flex-col dark:bg-gray-900 min-h-screen transition-all">
48
+ <div
49
+ id="head-container"
50
+ className="z-30 sticky top-0 dark:before:bg-gray-800 before:bg-white before:bg-opacity-[.85]
51
+ before:backdrop-blur-md before:absolute before:block dark:before:bg-opacity-[.85]
52
+ before:w-full before:h-full before:z-[-1]"
53
+ >
54
+ <Announcement />
55
+ <Head setMenuOpened={setIsMenuOpened} isMenuOpened={isMenuOpened} />
56
+ </div>
57
+
58
+ <div className="g-glossy-firefox-cover" />
59
+ <div className="g-glossy-firefox" id="firefox-head-bg" />
60
+
61
+ {isHomePage ? (
62
+ <div id="article-body">
63
+ <Helmet>
64
+ <title>
65
+ {title}
66
+ {description ? ` - ${description}` : ''}
67
+ </title>
68
+ </Helmet>
69
+ {props.children}
70
+ </div>
71
+ ) : (
72
+ <Fragment>
73
+ <div
74
+ id="article-body"
75
+ className="w-full flex flex-row justify-center overflow-x-hidden"
76
+ >
77
+ <div className="container flex flex-row justify-center">
78
+ <div className="w-full lg:w-1/2 px-4 lg:px-2 m-8 z-20 lg:py-12">
79
+ <article className="flex-1">{props.children}</article>
80
+ </div>
81
+ </div>
82
+ </div>
83
+
84
+ <div
85
+ className="fixed left-0 top-0 w-1/4 flex flex-row
86
+ justify-center h-screen z-10 pt-20"
87
+ >
88
+ <div className="container flex flex-row justify-end">
89
+ <div className="hidden lg:block">
90
+ <Sidebar />
91
+ </div>
92
+ </div>
93
+ </div>
94
+
95
+ <div
96
+ className="fixed right-0 top-0 w-1/4 flex flex-row
97
+ justify-center h-screen z-10 pt-20 hidden lg:block"
98
+ >
99
+ <div className="container flex flex-row justify-start">
100
+ <div className="w-2/3 top-32">
101
+ <Toc />
102
+ </div>
103
+ </div>
104
+ </div>
105
+ </Fragment>
106
+ )}
107
+ </div>
108
+
109
+ <div
110
+ className={cx(
111
+ 'fixed top-12 w-screen bg-white z-20 dark:bg-gray-800',
112
+ 'overflow-hidden transition-all duration-500',
113
+ isMenuOpened ? 'max-h-screen' : 'max-h-0',
114
+ )}
115
+ >
116
+ <Sidebar setMenuOpened={setIsMenuOpened} />
117
+ </div>
118
+ </ThemeContext.Provider>
119
+ );
120
+ };
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import { useThemeContext } from './context';
3
+ import useLanguage from './useLanguage';
4
+
5
+ export default () => {
6
+ const { themeConfig, components } = useThemeContext()!;
7
+ const { isFromPath, currentLanguage } = useLanguage();
8
+
9
+ // @ts-ignore
10
+ const { logo } = themeConfig;
11
+
12
+ return (
13
+ <components.Link to={isFromPath ? '/' + currentLanguage?.locale : '/'}>
14
+ <div className="flex flex-row items-center">
15
+ <img src={logo} className="w-8 h-8" alt="logo" />
16
+ <div className="text-xl font-extrabold ml-2 dark:text-white">
17
+ {themeConfig.title}
18
+ </div>
19
+ </div>
20
+ </components.Link>
21
+ );
22
+ };
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { useThemeContext } from './context';
3
+ import useLanguage from './useLanguage';
4
+
5
+ export default () => {
6
+ const { components, themeConfig } = useThemeContext()!;
7
+ const lang = useLanguage();
8
+ return (
9
+ <ul className="flex">
10
+ {themeConfig.navs.map((nav: any) => {
11
+ return (
12
+ <li key={nav.path} className="ml-4 dark:text-white">
13
+ <components.Link
14
+ to={
15
+ lang.isFromPath
16
+ ? lang.currentLanguage?.locale + nav.path
17
+ : nav.path
18
+ }
19
+ >
20
+ {lang.render(nav.title)}
21
+ </components.Link>
22
+ </li>
23
+ );
24
+ })}
25
+ </ul>
26
+ );
27
+ };
@@ -0,0 +1,178 @@
1
+ import cx from 'classnames';
2
+ import key from 'keymaster';
3
+ import React, { Fragment, useEffect, useState } from 'react';
4
+ import { useThemeContext } from './context';
5
+ import useLanguage from './useLanguage';
6
+
7
+ export default () => {
8
+ const { render } = useLanguage();
9
+ const [isFocused, setIsFocused] = useState(false);
10
+ const [keyword, setKeyword] = useState('');
11
+
12
+ const { appData, themeConfig } = useThemeContext()!;
13
+
14
+ const isMac = /(Mac|iPad)/i.test(navigator.userAgent);
15
+
16
+ let searchHotKey = '⌘+k, ctrl+k';
17
+ let macSearchKey = '⌘+k';
18
+ let windowsSearchKey = 'ctrl+k';
19
+
20
+ if (themeConfig.searchHotKey) {
21
+ if (typeof themeConfig.searchHotKey === 'string') {
22
+ searchHotKey = themeConfig.searchHotKey;
23
+ macSearchKey = themeConfig.searchHotKey;
24
+ windowsSearchKey = themeConfig.searchHotKey;
25
+ }
26
+ if (typeof themeConfig.searchHotKey === 'object') {
27
+ searchHotKey =
28
+ themeConfig.searchHotKey.macos +
29
+ ', ' +
30
+ themeConfig.searchHotKey.windows;
31
+ macSearchKey = themeConfig.searchHotKey.macos;
32
+ windowsSearchKey = themeConfig.searchHotKey.windows;
33
+ }
34
+ }
35
+
36
+ useEffect(() => {
37
+ key.filter = () => true;
38
+
39
+ // 在页面中按下 ⌘+k 可以打开搜索框
40
+ key(searchHotKey, (e) => {
41
+ e.preventDefault();
42
+ document.getElementById('search-input')?.focus();
43
+ });
44
+
45
+ // 在搜索框中按下 'Escape' 键可以关闭搜索框
46
+ key('escape', () => {
47
+ (document.activeElement as HTMLElement).blur();
48
+ });
49
+
50
+ key('up', handleKeyUp);
51
+ key('down', handleKeyDown);
52
+
53
+ return () => {
54
+ key.unbind(searchHotKey);
55
+ key.unbind('escape');
56
+ key.unbind('up');
57
+ key.unbind('down');
58
+ };
59
+ }, []);
60
+
61
+ const result = search(appData.routes, keyword);
62
+
63
+ return (
64
+ <Fragment>
65
+ <div
66
+ className="rounded-lg w-40 lg:w-64 flex items-center pr-2 flex-row hover:bg-gray-50
67
+ transition duration-300 bg-gray-100 border border-white focus-within:border-gray-100
68
+ focus-within:bg-white dark:bg-gray-700 dark:border-gray-700 relative
69
+ dark:focus-within:border-gray-700 dark:focus-within:bg-gray-800 dark:text-gray-100"
70
+ >
71
+ <input
72
+ onFocus={() => setIsFocused(true)}
73
+ onBlur={() => setIsFocused(false)}
74
+ value={keyword}
75
+ onChange={(e) => setKeyword(e.target.value)}
76
+ id="search-input"
77
+ className="w-full bg-transparent outline-none text-sm px-4 py-2 "
78
+ placeholder={render('Search anything ...')}
79
+ />
80
+ <div
81
+ className="bg-gray-200 rounded px-2 h-6 flex flex-row text-gray-400
82
+ items-center justify-center border border-gray-300 text-xs"
83
+ >
84
+ {isMac ? macSearchKey : windowsSearchKey}
85
+ </div>
86
+ <div
87
+ className={cx(
88
+ 'absolute transition-all duration-500 top-12 w-96 rounded-lg',
89
+ 'cursor-pointer shadow overflow-hidden',
90
+ result.length > 0 && isFocused ? 'max-h-80' : 'max-h-0',
91
+ )}
92
+ >
93
+ {result.map((r, i) => (
94
+ <a
95
+ href={r.href}
96
+ key={i}
97
+ className="group outline-none search-result"
98
+ onFocus={() => setIsFocused(true)}
99
+ onBlur={() => setIsFocused(false)}
100
+ >
101
+ <p
102
+ className="p-2 bg-white hover:bg-gray-50 transition
103
+ duration-300 group-focus:bg-blue-200 dark:bg-gray-700"
104
+ >
105
+ {r.path}
106
+ </p>
107
+ </a>
108
+ ))}
109
+ </div>
110
+ </div>
111
+ </Fragment>
112
+ );
113
+ };
114
+
115
+ interface SearchResultItem {
116
+ path: string;
117
+ href: string;
118
+ }
119
+
120
+ function search(routes: any, keyword: string): SearchResultItem[] {
121
+ if (!keyword) return [];
122
+
123
+ const result: SearchResultItem[] = [];
124
+
125
+ Object.keys(routes).map((path) => {
126
+ if (path.toLowerCase().includes(keyword.toLowerCase())) {
127
+ result.push({
128
+ path: path.split('/').slice(1).join(' > '),
129
+ href: '/' + path,
130
+ });
131
+ }
132
+
133
+ const route = routes[path];
134
+ if (!route.titles) return;
135
+ route.titles
136
+ .filter((t: any) => t.level <= 2)
137
+ .map((title: any) => {
138
+ if (title.title.toLowerCase().includes(keyword.toLowerCase())) {
139
+ result.push({
140
+ path: path.split('/').slice(1).join(' > ') + ' > ' + title.title,
141
+ href: '/' + path + '#' + title.title,
142
+ });
143
+ }
144
+ });
145
+ });
146
+
147
+ if (result.length > 8) return result.slice(0, 8);
148
+
149
+ return result;
150
+ }
151
+
152
+ function handleKeyDown(e: KeyboardEvent) {
153
+ if (!document.activeElement) return;
154
+
155
+ if (document.activeElement.id === 'search-input') {
156
+ e.preventDefault();
157
+ (
158
+ document.getElementsByClassName('search-result')[0] as
159
+ | HTMLDivElement
160
+ | undefined
161
+ )?.focus();
162
+ return;
163
+ }
164
+
165
+ if (document.activeElement.className.indexOf('search-result') === -1) return;
166
+
167
+ e.preventDefault();
168
+ (document.activeElement?.nextSibling as HTMLDivElement | undefined)?.focus();
169
+ }
170
+
171
+ function handleKeyUp(e: KeyboardEvent) {
172
+ if (!document.activeElement) return;
173
+ if (document.activeElement.className.indexOf('search-result') === -1) return;
174
+ e.preventDefault();
175
+ (
176
+ document.activeElement?.previousSibling as HTMLDivElement | undefined
177
+ )?.focus();
178
+ }
@@ -0,0 +1,84 @@
1
+ import cx from 'classnames';
2
+ import React from 'react';
3
+ import { useThemeContext } from './context';
4
+ import useLanguage from './useLanguage';
5
+
6
+ interface SidebarProps {
7
+ setMenuOpened?: React.Dispatch<React.SetStateAction<boolean>>;
8
+ }
9
+
10
+ export default (props: SidebarProps) => {
11
+ const { currentLanguage, isFromPath } = useLanguage();
12
+ const { appData, components, themeConfig, location } = useThemeContext()!;
13
+ const matchedNav = themeConfig.navs.filter((nav) =>
14
+ location.pathname.startsWith(
15
+ (isFromPath && currentLanguage ? '/' + currentLanguage.locale : '') +
16
+ nav.path,
17
+ ),
18
+ )[0];
19
+
20
+ if (!matchedNav) {
21
+ return null;
22
+ }
23
+
24
+ let locale = currentLanguage?.locale;
25
+ if (!isFromPath) locale = '';
26
+
27
+ return (
28
+ <ul
29
+ className={cx(
30
+ 'h-screen lg:h-[calc(100vh-8rem)] overflow-y-scroll',
31
+ 'lg:w-64 p-8 pb-12 fadeout w-full',
32
+ )}
33
+ >
34
+ {(matchedNav.children || []).map((item) => {
35
+ return (
36
+ <li key={item.title}>
37
+ <div>
38
+ <p className="text-xl font-extrabold my-6 dark:text-white">
39
+ {item.title}
40
+ </p>
41
+ {item.children.map((child: any) => {
42
+ const to =
43
+ (locale ? `/${locale}` : '') + `${matchedNav.path}/${child}`;
44
+ const id = `${matchedNav.path}/${child}`.slice(1);
45
+ const route =
46
+ appData.routes[id + '.' + locale] || appData.routes[id];
47
+ const title = route.titles[0]?.title || null;
48
+
49
+ if (to === window.location.pathname) {
50
+ return (
51
+ <div
52
+ key={child}
53
+ className="my-2 hover:text-blue-400 transition-all
54
+ bg-blue-50 text-blue-400 px-4 py-1
55
+ rounded-lg cursor-default dark:bg-blue-900 dark:text-blue-200"
56
+ >
57
+ {title}
58
+ </div>
59
+ );
60
+ }
61
+
62
+ return (
63
+ <components.Link
64
+ to={route.path}
65
+ onClick={() =>
66
+ props.setMenuOpened && props.setMenuOpened((o) => !o)
67
+ }
68
+ >
69
+ <div
70
+ key={child}
71
+ className="text-gray-700 my-2 hover:text-blue-400 transition-all px-4 py-1 dark:text-blue-200 dark:hover:text-blue-400"
72
+ >
73
+ {title}
74
+ </div>
75
+ </components.Link>
76
+ );
77
+ })}
78
+ </div>
79
+ </li>
80
+ );
81
+ })}
82
+ </ul>
83
+ );
84
+ };
@@ -0,0 +1,60 @@
1
+ import cx from 'classnames';
2
+ import React, { useEffect, useState } from 'react';
3
+ import MoonIcon from './icons/moon.png';
4
+ import SunIcon from './icons/sun.png';
5
+
6
+ export default () => {
7
+ const [toggle, setToggle] = useState<Boolean>();
8
+
9
+ useEffect(() => {
10
+ // 初始化,获取过去曾经设定过的主题,或是系统当前的主题
11
+ if (toggle === undefined) {
12
+ if (localStorage.getItem('theme') === 'dark') {
13
+ setToggle(false);
14
+ return;
15
+ }
16
+ if (localStorage.getItem('theme') === 'light') {
17
+ setToggle(true);
18
+ return;
19
+ }
20
+ if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
21
+ setToggle(false);
22
+ return;
23
+ }
24
+ setToggle(true);
25
+ }
26
+
27
+ if (toggle) {
28
+ document.body.classList.remove('dark');
29
+ localStorage.setItem('theme', 'light');
30
+ } else {
31
+ document.body.classList.add('dark');
32
+ localStorage.setItem('theme', 'dark');
33
+ }
34
+ }, [toggle]);
35
+
36
+ return (
37
+ <div
38
+ className={cx(
39
+ 'md:w-12 md:h-6 w-12 h-4 flex items-center rounded-full ',
40
+ 'py-1 px-1.5 cursor-pointer',
41
+ toggle ? 'bg-gray-100' : 'bg-gray-700',
42
+ )}
43
+ onClick={() => setToggle(!toggle)}
44
+ >
45
+ <div
46
+ className={cx(
47
+ 'md:w-4 md:h-4 h-3 w-3 rounded-full shadow-md ',
48
+ 'transition transform',
49
+ toggle && 'translate-x-5',
50
+ )}
51
+ >
52
+ <img
53
+ src={toggle ? SunIcon : MoonIcon}
54
+ alt="toggle"
55
+ className="w-full h-full"
56
+ />
57
+ </div>
58
+ </div>
59
+ );
60
+ };
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+
3
+ export default () => {
4
+ return <div>Tip</div>;
5
+ };