@dbosoft/nextjs-uicore 1.0.2 → 1.1.0
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.
- package/CHANGELOG.md +36 -24
- package/package.json +5 -2
- package/src/subnav/index.tsx +8 -22
- package/src/subnav/partials/CtaLinks/github-stars-link/index.tsx +5 -1
- package/src/subnav/partials/CtaLinks/index.tsx +2 -2
- package/src/subnav/partials/MenuItemsDefault/index.tsx +1 -12
- package/src/subnav/partials/MenuItemsOverflow/index.tsx +3 -2
- package/src/subnav/partials/nav-item-text/index.tsx +2 -2
- package/src/tabs/partials/tab-trigger/index.tsx +2 -2
- package/src/tabs/partials/tab-triggers/index.tsx +11 -11
- package/src/themeselector/index.tsx +119 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,24 +1,36 @@
|
|
|
1
|
-
# @dbosoft/nextjs-uicore
|
|
2
|
-
|
|
3
|
-
## 1.0
|
|
4
|
-
|
|
5
|
-
###
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
## 1.0.
|
|
10
|
-
|
|
11
|
-
### Patch Changes
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
## 1.0.
|
|
16
|
-
|
|
17
|
-
###
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
1
|
+
# @dbosoft/nextjs-uicore
|
|
2
|
+
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 7a843e1: darkmode support for subnav
|
|
8
|
+
|
|
9
|
+
## 1.0.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 5fc59bc: fixed missing navigation for github links in subnav
|
|
14
|
+
|
|
15
|
+
## 1.0.2
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- 4122817: fixed subnav titlelink
|
|
20
|
+
|
|
21
|
+
## 1.0.1
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- ec24215: update @heroicons/react to v2
|
|
26
|
+
|
|
27
|
+
## 1.0.0
|
|
28
|
+
|
|
29
|
+
### Major Changes
|
|
30
|
+
|
|
31
|
+
- restructured nextjs components
|
|
32
|
+
|
|
33
|
+
### Patch Changes
|
|
34
|
+
|
|
35
|
+
- Updated dependencies
|
|
36
|
+
- @dbosoft/react-uicore@1.0.0
|
package/package.json
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
"name": "@dbosoft/nextjs-uicore",
|
|
3
3
|
"description": "Core UI components for Next.js",
|
|
4
4
|
"author": "dbosoft",
|
|
5
|
-
"version": "1.0
|
|
5
|
+
"version": "1.1.0",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": "./src",
|
|
10
10
|
"./head": "./src/head/index.tsx",
|
|
11
11
|
"./tabs": "./src/tabs/index.tsx",
|
|
12
|
-
"./subnav": "./src/subnav/index.tsx"
|
|
12
|
+
"./subnav": "./src/subnav/index.tsx",
|
|
13
|
+
"./themeselector": "./src/themeselector/index.tsx"
|
|
13
14
|
},
|
|
14
15
|
"devDependencies": {
|
|
15
16
|
"@types/react": "^18.2.46",
|
|
@@ -29,7 +30,9 @@
|
|
|
29
30
|
"@headlessui/react": "^1.4.3",
|
|
30
31
|
"@heroicons/react": "^2.1.3",
|
|
31
32
|
"classnames": "^2.3.1",
|
|
33
|
+
"clsx": "^2.1.0",
|
|
32
34
|
"isomorphic-unfetch": "^4.0.2",
|
|
35
|
+
"next-themes": "^0.4.3",
|
|
33
36
|
"unfetch": "^4.2.0",
|
|
34
37
|
"@dbosoft/react-uicore": "1.0.0"
|
|
35
38
|
},
|
package/src/subnav/index.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Disclosure } from '@headlessui/react'
|
|
2
2
|
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
|
|
3
|
-
import classNames from 'classnames'
|
|
4
3
|
import type { LinkType, Variant } from '@dbosoft/react-uicore/linkbutton'
|
|
5
4
|
import MenuItemsOverflow from './partials/MenuItemsOverflow'
|
|
6
5
|
import MenuItemsDefault from './partials/MenuItemsDefault'
|
|
@@ -8,6 +7,8 @@ import CtaLinks from './partials/CtaLinks'
|
|
|
8
7
|
import useStuckRef from './helpers/useStuckRef'
|
|
9
8
|
import TitleLink from './partials/TitleLink'
|
|
10
9
|
import style from './style.module.scss'
|
|
10
|
+
import clsx from 'clsx'
|
|
11
|
+
import { ThemeSelector } from '../themeselector'
|
|
11
12
|
|
|
12
13
|
export type MenuItem = 'divider' | ILinkMenuItem;
|
|
13
14
|
|
|
@@ -41,7 +42,7 @@ interface ISubNavProps {
|
|
|
41
42
|
titleLink?: ITitleLink,
|
|
42
43
|
titleContent?: JSX.Element,
|
|
43
44
|
ctaLinks: ICtaItem[],
|
|
44
|
-
hideGithubStars: boolean
|
|
45
|
+
hideGithubStars: boolean
|
|
45
46
|
menuItems: MenuItem[],
|
|
46
47
|
menuItemsAlign: 'left' | 'right',
|
|
47
48
|
className?: string
|
|
@@ -60,7 +61,7 @@ function Subnav({
|
|
|
60
61
|
const { isStuck, stuckRef } = useStuckRef([])
|
|
61
62
|
|
|
62
63
|
return (
|
|
63
|
-
<div className={
|
|
64
|
+
<div className={clsx(style.root, `bg-white dark:bg-slate-900`, className, {
|
|
64
65
|
[style.isSticky]: isStuck,
|
|
65
66
|
})} ref={stuckRef}>
|
|
66
67
|
<Disclosure as="nav" >
|
|
@@ -68,7 +69,7 @@ function Subnav({
|
|
|
68
69
|
<>
|
|
69
70
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
70
71
|
<div className="flex justify-between h-16">
|
|
71
|
-
<div className={
|
|
72
|
+
<div className={clsx(`flex grow`, menuItemsAlign == `right` ? ` justify-between` : ``)}>
|
|
72
73
|
|
|
73
74
|
<div className="flex-shrink-0 flex items-center ">
|
|
74
75
|
{titleLink &&<TitleLink
|
|
@@ -88,6 +89,7 @@ function Subnav({
|
|
|
88
89
|
/>
|
|
89
90
|
</div>
|
|
90
91
|
</div>
|
|
92
|
+
|
|
91
93
|
<div className="-mr-2 flex items-center sm:hidden ">
|
|
92
94
|
{/* Mobile menu button */}
|
|
93
95
|
<Disclosure.Button className="bg-white inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-secondary">
|
|
@@ -100,6 +102,7 @@ function Subnav({
|
|
|
100
102
|
</Disclosure.Button>
|
|
101
103
|
</div>
|
|
102
104
|
</div>
|
|
105
|
+
|
|
103
106
|
</div>
|
|
104
107
|
|
|
105
108
|
<Disclosure.Panel className="sm:hidden">
|
|
@@ -108,24 +111,7 @@ function Subnav({
|
|
|
108
111
|
ctaLinks={ctaLinks}
|
|
109
112
|
hideGithubStars={hideGithubStars}
|
|
110
113
|
menuItems={menuItems}
|
|
111
|
-
/>
|
|
112
|
-
{/* {navigation.map((item) => (
|
|
113
|
-
<Disclosure.Button
|
|
114
|
-
key={item.name}
|
|
115
|
-
as="a"
|
|
116
|
-
href={item.href}
|
|
117
|
-
className={classNames(
|
|
118
|
-
item.current
|
|
119
|
-
? 'bg-indigo-50 border-indigo-500 text-indigo-700'
|
|
120
|
-
: 'border-transparent text-gray-600 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-800',
|
|
121
|
-
'block pl-3 pr-4 py-2 border-l-4 text-base font-medium'
|
|
122
|
-
)}
|
|
123
|
-
aria-current={item.current ? 'page' : undefined}
|
|
124
|
-
>
|
|
125
|
-
{item.name}
|
|
126
|
-
</Disclosure.Button>
|
|
127
|
-
))}
|
|
128
|
-
*/} </div>
|
|
114
|
+
/> </div>
|
|
129
115
|
|
|
130
116
|
</Disclosure.Panel>
|
|
131
117
|
</>
|
|
@@ -8,6 +8,7 @@ import { StarIcon } from '@heroicons/react/16/solid'
|
|
|
8
8
|
import GithubIcon from '../icons/github.svg'
|
|
9
9
|
import formatStarCount from './formatStarCount'
|
|
10
10
|
import parseGithubUrl from './parseGithubUrl'
|
|
11
|
+
import Link from 'next/link';
|
|
11
12
|
|
|
12
13
|
function GithubStarsButton({ url, hideGithubStars }: { url: string, hideGithubStars: boolean }) {
|
|
13
14
|
const [starCount, setStarCount] = useState(-1)
|
|
@@ -63,7 +64,10 @@ function GithubStarsButton({ url, hideGithubStars }: { url: string, hideGithubSt
|
|
|
63
64
|
|
|
64
65
|
return (
|
|
65
66
|
<LinkButton icon={!showStarCount ? icon : undefined} label='Github' text={!showStarCount ? "Github" : ""}
|
|
66
|
-
variant='neutral'
|
|
67
|
+
variant='neutral'
|
|
68
|
+
url={url} external
|
|
69
|
+
Link={Link}
|
|
70
|
+
>
|
|
67
71
|
{showStarCount ? <div className="inline-flex">
|
|
68
72
|
<GithubIcon className="w-5 mr-2" />
|
|
69
73
|
<div className='-my-2 mx-2 h-9 block bg-gray-300' style={{ width: "1px" }} />
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import clsx from 'clsx'
|
|
2
2
|
import LinkButton from '@dbosoft/react-uicore/linkbutton'
|
|
3
3
|
import Link from 'next/link'
|
|
4
4
|
import type { ICtaItem } from '../..'
|
|
@@ -11,7 +11,7 @@ function CtaLinks(props: {
|
|
|
11
11
|
}) {
|
|
12
12
|
const { links, hideGithubStars, isInDropdown } = props
|
|
13
13
|
return (
|
|
14
|
-
<div className={
|
|
14
|
+
<div className={clsx("flex gap-2", isInDropdown ? "pt-4 flex-col" : "items-center ml-4 space-x-1 ")}>
|
|
15
15
|
{links.map((link, stableIdx) => {
|
|
16
16
|
|
|
17
17
|
const textKey = link.text.toLowerCase()
|
|
@@ -13,17 +13,6 @@ function MenuItemsDefault(props: {
|
|
|
13
13
|
return (
|
|
14
14
|
<ul
|
|
15
15
|
className="inline-flex items-center">
|
|
16
|
-
{/* <a
|
|
17
|
-
key={item.name}
|
|
18
|
-
href={item.href}
|
|
19
|
-
className={classNames(
|
|
20
|
-
item.current
|
|
21
|
-
? 'border-indigo-500 text-gray-900'
|
|
22
|
-
: 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700',
|
|
23
|
-
'inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium'
|
|
24
|
-
)}
|
|
25
|
-
aria-current={item.current ? 'page' : undefined}
|
|
26
|
-
></a> */}
|
|
27
16
|
{menuItems.map((menuItem, stableIdx) => {
|
|
28
17
|
if (menuItem === 'divider') {
|
|
29
18
|
return <VerticalDivider key={stableIdx} />
|
|
@@ -42,7 +31,7 @@ function MenuItemsDefault(props: {
|
|
|
42
31
|
function VerticalDivider() {
|
|
43
32
|
return (
|
|
44
33
|
<li>
|
|
45
|
-
<span className="bg-gray-400 w-0.5 h-6 mx-1 block" />
|
|
34
|
+
<span className="bg-gray-400 dark:bg-gray-200 w-0.5 h-6 mx-1 block" />
|
|
46
35
|
</li>
|
|
47
36
|
)
|
|
48
37
|
}
|
|
@@ -5,7 +5,7 @@ import CtaLinks from '../CtaLinks'
|
|
|
5
5
|
import type { ICtaItem, MenuItem } from '../..'
|
|
6
6
|
import s from './style.module.scss'
|
|
7
7
|
import Link from 'next/link';
|
|
8
|
-
|
|
8
|
+
import clsx from 'clsx'
|
|
9
9
|
|
|
10
10
|
interface MenuItemsOverflowProps {
|
|
11
11
|
menuItems: MenuItem[],
|
|
@@ -39,9 +39,10 @@ export default function MenuItemsOverflow({ menuItems, ctaLinks, hideGithubStars
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function SubmenuItem({ url, text, active }: { url: string; text: string; active: boolean }) {
|
|
42
|
+
|
|
42
43
|
return (
|
|
43
44
|
<li>
|
|
44
|
-
<LinkWrap Link={Link} className={s.submenuItem} href={url} title={text}>
|
|
45
|
+
<LinkWrap Link={Link} className={clsx(s.submenuItem, "text-black dark:text-white" )} href={url} title={text}>
|
|
45
46
|
{text}
|
|
46
47
|
</LinkWrap>
|
|
47
48
|
</li>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import clsx from 'clsx'
|
|
2
2
|
import React from 'react'
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -19,7 +19,7 @@ function NavItemText({
|
|
|
19
19
|
}): React.ReactElement {
|
|
20
20
|
return (
|
|
21
21
|
<span
|
|
22
|
-
className={
|
|
22
|
+
className={clsx("text-black dark:text-white text-sm font-semibold", isActive ? "border-b-2 pb-2 border-black" : "")}
|
|
23
23
|
>
|
|
24
24
|
{text}
|
|
25
25
|
</span>
|
|
@@ -3,7 +3,7 @@ import TooltipIcon from '../../icons/tooltip.svg'
|
|
|
3
3
|
import Tooltip from '../tooltip'
|
|
4
4
|
import { useTabGroups } from '../../provider.js'
|
|
5
5
|
import s from './style.module.scss'
|
|
6
|
-
import
|
|
6
|
+
import clsx from 'clsx'
|
|
7
7
|
|
|
8
8
|
export interface TabTriggerType {
|
|
9
9
|
index: number
|
|
@@ -39,7 +39,7 @@ function TabTrigger({
|
|
|
39
39
|
|
|
40
40
|
return (
|
|
41
41
|
<button
|
|
42
|
-
className={
|
|
42
|
+
className={clsx(s.root, {
|
|
43
43
|
[s.isActiveTab]: isActiveTab,
|
|
44
44
|
[s.hasOverflow]: hasOverflow,
|
|
45
45
|
})}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { MouseEventHandler, useCallback, useEffect, useRef, useState } from 'react'
|
|
2
|
-
import
|
|
3
|
-
import InlineSvg from '@dbosoft/react-inline-svg'
|
|
2
|
+
import clsx from 'clsx'
|
|
3
|
+
import InlineSvg from '@dbosoft/react-uicore/inline-svg'
|
|
4
4
|
import TabTrigger, { TabTriggerType } from '../tab-trigger'
|
|
5
5
|
import SvgChevronRight from '../../icons/chevron-right.svg'
|
|
6
6
|
import smoothScroll from '../../utils/smooth-scroll.js'
|
|
@@ -104,7 +104,7 @@ function TabTriggers({
|
|
|
104
104
|
|
|
105
105
|
return (
|
|
106
106
|
<div
|
|
107
|
-
className={
|
|
107
|
+
className={clsx(s.root, { [s.fullWidthBorder]: fullWidthBorder })}
|
|
108
108
|
>
|
|
109
109
|
<div className="g-grid-container">
|
|
110
110
|
{/* Note: the overflowBaseRef element has zero height, but is still "visible".
|
|
@@ -114,14 +114,14 @@ function TabTriggers({
|
|
|
114
114
|
<div className={s.borderAdjuster}>
|
|
115
115
|
<NextPrevScrims hasOverflow={hasOverflow} hiddenArrows={hiddenArrows} />
|
|
116
116
|
<div
|
|
117
|
-
className={
|
|
117
|
+
className={clsx(s.scrollContainer, {
|
|
118
118
|
[s.centered]: centered,
|
|
119
119
|
[s.hasOverflow]: hasOverflow,
|
|
120
120
|
})}
|
|
121
121
|
ref={scrollRef}
|
|
122
122
|
>
|
|
123
123
|
<div
|
|
124
|
-
className={
|
|
124
|
+
className={clsx(s.tabsWidthContainer, {
|
|
125
125
|
[s.centered]: centered,
|
|
126
126
|
[s.hasOverflow]: hasOverflow,
|
|
127
127
|
})}
|
|
@@ -176,13 +176,13 @@ function NextPrevScrims({ hasOverflow, hiddenArrows }: { hasOverflow: $TSFixMe,
|
|
|
176
176
|
return (
|
|
177
177
|
<div className={s.scrimContainer}>
|
|
178
178
|
<div
|
|
179
|
-
className={
|
|
179
|
+
className={clsx(s.prevArrowScrim, {
|
|
180
180
|
[s.hasOverflow]: hasOverflow,
|
|
181
181
|
[s.hidden]: hiddenArrows.prev,
|
|
182
182
|
})}
|
|
183
183
|
/>
|
|
184
184
|
<div
|
|
185
|
-
className={
|
|
185
|
+
className={clsx(s.nextArrowScrim, {
|
|
186
186
|
[s.hasOverflow]: hasOverflow,
|
|
187
187
|
[s.hidden]: hiddenArrows.next,
|
|
188
188
|
})}
|
|
@@ -196,7 +196,7 @@ function NextPrevArrows({ hasOverflow, hiddenArrows, onPrev, onNext }
|
|
|
196
196
|
return (
|
|
197
197
|
<>
|
|
198
198
|
<div
|
|
199
|
-
className={
|
|
199
|
+
className={clsx(s.prevArrow, {
|
|
200
200
|
[s.hasOverflow]: hasOverflow,
|
|
201
201
|
[s.hidden]: hiddenArrows.prev,
|
|
202
202
|
})}
|
|
@@ -205,7 +205,7 @@ function NextPrevArrows({ hasOverflow, hiddenArrows, onPrev, onNext }
|
|
|
205
205
|
<InlineSvg src={SvgChevronRight} />
|
|
206
206
|
</div>
|
|
207
207
|
<div
|
|
208
|
-
className={
|
|
208
|
+
className={clsx(s.nextArrow, {
|
|
209
209
|
[s.hasOverflow]: hasOverflow,
|
|
210
210
|
[s.hidden]: hiddenArrows.next,
|
|
211
211
|
})}
|
|
@@ -222,14 +222,14 @@ function BottomBorder({ hasOverflow, fullWidthBorder }: { hasOverflow: $TSFixMe,
|
|
|
222
222
|
<>
|
|
223
223
|
<div className="g-grid-container">
|
|
224
224
|
<div
|
|
225
|
-
className={
|
|
225
|
+
className={clsx(s.bottomBorder, s.forDefault, {
|
|
226
226
|
[s.hasOverflow]: hasOverflow,
|
|
227
227
|
[s.fullWidthBorder]: fullWidthBorder,
|
|
228
228
|
})}
|
|
229
229
|
></div>
|
|
230
230
|
</div>
|
|
231
231
|
<div
|
|
232
|
-
className={
|
|
232
|
+
className={clsx(s.bottomBorder, s.forOverflow, {
|
|
233
233
|
[s.hasOverflow]: hasOverflow,
|
|
234
234
|
[s.fullWidthBorder]: fullWidthBorder,
|
|
235
235
|
})}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { useTheme } from 'next-themes'
|
|
3
|
+
import { Listbox } from '@headlessui/react'
|
|
4
|
+
import clsx from 'clsx'
|
|
5
|
+
|
|
6
|
+
const themes = [
|
|
7
|
+
{ name: 'Light', value: 'light', icon: LightIcon },
|
|
8
|
+
{ name: 'Dark', value: 'dark', icon: DarkIcon },
|
|
9
|
+
{ name: 'System', value: 'system', icon: SystemIcon },
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
function LightIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
|
13
|
+
return (
|
|
14
|
+
<svg aria-hidden="true" viewBox="0 0 16 16" {...props}>
|
|
15
|
+
<path
|
|
16
|
+
fillRule="evenodd"
|
|
17
|
+
clipRule="evenodd"
|
|
18
|
+
d="M7 1a1 1 0 0 1 2 0v1a1 1 0 1 1-2 0V1Zm4 7a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm2.657-5.657a1 1 0 0 0-1.414 0l-.707.707a1 1 0 0 0 1.414 1.414l.707-.707a1 1 0 0 0 0-1.414Zm-1.415 11.313-.707-.707a1 1 0 0 1 1.415-1.415l.707.708a1 1 0 0 1-1.415 1.414ZM16 7.999a1 1 0 0 0-1-1h-1a1 1 0 1 0 0 2h1a1 1 0 0 0 1-1ZM7 14a1 1 0 1 1 2 0v1a1 1 0 1 1-2 0v-1Zm-2.536-2.464a1 1 0 0 0-1.414 0l-.707.707a1 1 0 0 0 1.414 1.414l.707-.707a1 1 0 0 0 0-1.414Zm0-8.486A1 1 0 0 1 3.05 4.464l-.707-.707a1 1 0 0 1 1.414-1.414l.707.707ZM3 8a1 1 0 0 0-1-1H1a1 1 0 0 0 0 2h1a1 1 0 0 0 1-1Z"
|
|
19
|
+
/>
|
|
20
|
+
</svg>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function DarkIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
|
25
|
+
return (
|
|
26
|
+
<svg aria-hidden="true" viewBox="0 0 16 16" {...props}>
|
|
27
|
+
<path
|
|
28
|
+
fillRule="evenodd"
|
|
29
|
+
clipRule="evenodd"
|
|
30
|
+
d="M7.23 3.333C7.757 2.905 7.68 2 7 2a6 6 0 1 0 0 12c.68 0 .758-.905.23-1.332A5.989 5.989 0 0 1 5 8c0-1.885.87-3.568 2.23-4.668ZM12 5a1 1 0 0 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 0 2 1 1 0 0 0-1 1 1 1 0 1 1-2 0 1 1 0 0 0-1-1 1 1 0 1 1 0-2 1 1 0 0 0 1-1 1 1 0 0 1 1-1Z"
|
|
31
|
+
/>
|
|
32
|
+
</svg>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function SystemIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
|
37
|
+
return (
|
|
38
|
+
<svg aria-hidden="true" viewBox="0 0 16 16" {...props}>
|
|
39
|
+
<path
|
|
40
|
+
fillRule="evenodd"
|
|
41
|
+
clipRule="evenodd"
|
|
42
|
+
d="M1 4a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v4a3 3 0 0 1-3 3h-1.5l.31 1.242c.084.333.36.573.63.808.091.08.182.158.264.24A1 1 0 0 1 11 15H5a1 1 0 0 1-.704-1.71c.082-.082.173-.16.264-.24.27-.235.546-.475.63-.808L5.5 11H4a3 3 0 0 1-3-3V4Zm3-1a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H4Z"
|
|
43
|
+
/>
|
|
44
|
+
</svg>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function ThemeSelector(
|
|
49
|
+
props: React.ComponentPropsWithoutRef<typeof Listbox<'div'>>,
|
|
50
|
+
) {
|
|
51
|
+
let { theme, setTheme } = useTheme()
|
|
52
|
+
let [mounted, setMounted] = useState(false)
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
setMounted(true)
|
|
56
|
+
}, [])
|
|
57
|
+
|
|
58
|
+
if (!mounted) {
|
|
59
|
+
return <div className="h-6 w-6" />
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Listbox as="div" value={theme} onChange={setTheme} {...props}>
|
|
64
|
+
<Listbox.Label className="sr-only">Theme</Listbox.Label>
|
|
65
|
+
<Listbox.Button
|
|
66
|
+
className="flex h-9 w-12 mt-3 items-center justify-center rounded-lg shadow-md shadow-black/5 ring-1 ring-black/5 dark:bg-slate-700 dark:ring-inset dark:ring-white/5"
|
|
67
|
+
aria-label="Theme"
|
|
68
|
+
>
|
|
69
|
+
<LightIcon
|
|
70
|
+
className={clsx(
|
|
71
|
+
'h-4 w-4 dark:hidden',
|
|
72
|
+
theme === 'system' ? 'fill-slate-400' : 'fill-sky-400',
|
|
73
|
+
)}
|
|
74
|
+
/>
|
|
75
|
+
<DarkIcon
|
|
76
|
+
className={clsx(
|
|
77
|
+
'hidden h-4 w-4 dark:block',
|
|
78
|
+
theme === 'system' ? 'fill-slate-400' : 'fill-sky-400',
|
|
79
|
+
)}
|
|
80
|
+
/>
|
|
81
|
+
</Listbox.Button>
|
|
82
|
+
<Listbox.Options className="absolute left-1/2 top-full mt-3 w-36 -translate-x-1/2 space-y-1 rounded-xl bg-white p-3 text-sm font-medium shadow-md shadow-black/5 ring-1 ring-black/5 dark:bg-slate-800 dark:ring-white/5">
|
|
83
|
+
{themes.map((theme) => (
|
|
84
|
+
<Listbox.Option
|
|
85
|
+
key={theme.value}
|
|
86
|
+
value={theme.value}
|
|
87
|
+
className={({ active, selected }) =>
|
|
88
|
+
clsx(
|
|
89
|
+
'flex cursor-pointer select-none items-center rounded-[0.625rem] p-1',
|
|
90
|
+
{
|
|
91
|
+
'text-sky-500': selected,
|
|
92
|
+
'text-slate-900 dark:text-white': active && !selected,
|
|
93
|
+
'text-slate-700 dark:text-slate-400': !active && !selected,
|
|
94
|
+
'bg-slate-100 dark:bg-slate-900/40': active,
|
|
95
|
+
},
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
>
|
|
99
|
+
{({ selected }) => (
|
|
100
|
+
<>
|
|
101
|
+
<div className="rounded-md bg-white p-1 shadow ring-1 ring-slate-900/5 dark:bg-slate-700 dark:ring-inset dark:ring-white/5">
|
|
102
|
+
<theme.icon
|
|
103
|
+
className={clsx(
|
|
104
|
+
'h-4 w-4',
|
|
105
|
+
selected
|
|
106
|
+
? 'fill-sky-400 dark:fill-sky-400'
|
|
107
|
+
: 'fill-slate-400',
|
|
108
|
+
)}
|
|
109
|
+
/>
|
|
110
|
+
</div>
|
|
111
|
+
<div className="ml-3">{theme.name}</div>
|
|
112
|
+
</>
|
|
113
|
+
)}
|
|
114
|
+
</Listbox.Option>
|
|
115
|
+
))}
|
|
116
|
+
</Listbox.Options>
|
|
117
|
+
</Listbox>
|
|
118
|
+
)
|
|
119
|
+
}
|