@dbosoft/nextjs-uicore 1.6.0 → 1.6.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @dbosoft/nextjs-uicore
2
2
 
3
+ ## 1.6.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [e1e8cbf]
8
+ - @dbosoft/react-uicore@1.4.2
9
+
10
+ ## 1.6.1
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies [909ea3a]
15
+ - @dbosoft/react-uicore@1.4.1
16
+
3
17
  ## 1.6.0
4
18
 
5
19
  ### Minor Changes
package/eslint.config.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import react from '@dbosoft/eslint-config/react'
2
-
3
- export default [...react]
1
+ import react from '@dbosoft/eslint-config/react'
2
+
3
+ export default [...react]
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "DEPRECATED: Use @dbosoft/react-uicore/tabs for pure React tabs and @dbosoft/nextjs-site-core/ui/tabs for Next.js integration. All other components (head, subnav, themeselector) are replaced by @dbosoft/nextjs-site-core.",
4
4
  "deprecated": true,
5
5
  "author": "dbosoft",
6
- "version": "1.6.0",
6
+ "version": "1.6.2",
7
7
  "sideEffects": false,
8
8
  "license": "MIT",
9
9
  "exports": {
@@ -30,12 +30,16 @@
30
30
  "publishConfig": {
31
31
  "access": "public"
32
32
  },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/dbosoft/react-components.git"
36
+ },
33
37
  "dependencies": {
34
38
  "@headlessui/react": ">=2.2.0",
35
39
  "@heroicons/react": ">=2.1.0",
36
40
  "clsx": ">=2.1.0",
37
41
  "next-themes": ">=0.4.3",
38
- "@dbosoft/react-uicore": "1.4.0"
42
+ "@dbosoft/react-uicore": "1.4.2"
39
43
  },
40
44
  "scripts": {
41
45
  "check-types": "tsc --noEmit",
@@ -1,50 +1,50 @@
1
- "use client"
2
-
3
- import type { DependencyList} from 'react';
4
- import { useCallback, useState } from 'react'
5
-
6
- const IntersectionObserver =
7
- (typeof window !== 'undefined' && window.IntersectionObserver) || null
8
- const intersectionOpts = { threshold: [1] }
9
-
10
- /*
11
-
12
- Stuck-ness is determined by whether a sticky target element has intersected the
13
- top boundary of its viewport.
14
-
15
- Stuck-ness is checked on intersection observation.
16
-
17
- IntersectionObserver Compatibility (https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver#Browser_compatibility):
18
-
19
- - Chrome 51+
20
- - Edge 15+
21
- - Firefox 55+
22
- - Safari 12.1+
23
-
24
- Internet Explorer 11 gracefully skips over this functionality, as it does not
25
- support `IntersectionObserver` or CSS `position: sticky`.
26
-
27
- */
28
-
29
- export default function useStuckRef(deps: DependencyList) {
30
- const [isStuck, setStuck] = useState(false)
31
-
32
- const stuckRef = useCallback((target: Element | null) => {
33
- if (target && IntersectionObserver) {
34
- const intersectionObserver = new IntersectionObserver(([entry]) => {
35
- const nowIsStuck = entry.intersectionRatio < 1
36
- setStuck(nowIsStuck);
37
-
38
- }, intersectionOpts)
39
-
40
- intersectionObserver.observe(target)
41
-
42
- return intersectionObserver.disconnect.bind(intersectionObserver)
43
- }
44
- return () => { };
45
- }, [...deps]);
46
-
47
-
48
-
49
- return { isStuck, stuckRef }
50
- }
1
+ "use client"
2
+
3
+ import type { DependencyList} from 'react';
4
+ import { useCallback, useState } from 'react'
5
+
6
+ const IntersectionObserver =
7
+ (typeof window !== 'undefined' && window.IntersectionObserver) || null
8
+ const intersectionOpts = { threshold: [1] }
9
+
10
+ /*
11
+
12
+ Stuck-ness is determined by whether a sticky target element has intersected the
13
+ top boundary of its viewport.
14
+
15
+ Stuck-ness is checked on intersection observation.
16
+
17
+ IntersectionObserver Compatibility (https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver#Browser_compatibility):
18
+
19
+ - Chrome 51+
20
+ - Edge 15+
21
+ - Firefox 55+
22
+ - Safari 12.1+
23
+
24
+ Internet Explorer 11 gracefully skips over this functionality, as it does not
25
+ support `IntersectionObserver` or CSS `position: sticky`.
26
+
27
+ */
28
+
29
+ export default function useStuckRef(deps: DependencyList) {
30
+ const [isStuck, setStuck] = useState(false)
31
+
32
+ const stuckRef = useCallback((target: Element | null) => {
33
+ if (target && IntersectionObserver) {
34
+ const intersectionObserver = new IntersectionObserver(([entry]) => {
35
+ const nowIsStuck = entry.intersectionRatio < 1
36
+ setStuck(nowIsStuck);
37
+
38
+ }, intersectionOpts)
39
+
40
+ intersectionObserver.observe(target)
41
+
42
+ return intersectionObserver.disconnect.bind(intersectionObserver)
43
+ }
44
+ return () => { };
45
+ }, [...deps]);
46
+
47
+
48
+
49
+ return { isStuck, stuckRef }
50
+ }
@@ -1,134 +1,134 @@
1
- import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react'
2
- import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
3
- import type { LinkType, Variant } from '@dbosoft/react-uicore/linkbutton'
4
- import MenuItemsOverflow from './partials/MenuItemsOverflow'
5
- import MenuItemsDefault from './partials/MenuItemsDefault'
6
- import CtaLinks from './partials/CtaLinks'
7
- import useStuckRef from './helpers/useStuckRef'
8
- import TitleLink from './partials/TitleLink'
9
- import style from './style.module.scss'
10
- import clsx from 'clsx'
11
-
12
- export type MenuItem = 'divider' | ILinkMenuItem;
13
-
14
- export interface IDropDownMenuItem {
15
- text: string,
16
- submenu: ILinkMenuItem[]
17
- }
18
-
19
- export interface ILinkMenuItem {
20
- text: string,
21
- url: string,
22
- type: 'inbound' | 'outbound' | 'anchor'
23
- active?: boolean
24
- }
25
-
26
- export interface ITitleLink {
27
- url: string,
28
- text?: string,
29
- }
30
-
31
-
32
- export interface ICtaItem {
33
- text: string,
34
- url: string,
35
- variant?: Variant
36
- linktype?: LinkType
37
- className?: string,
38
- }
39
-
40
- export interface SubNavLabels {
41
- openMainMenu: string
42
- }
43
-
44
- const defaultSubNavLabels: SubNavLabels = {
45
- openMainMenu: 'Open main menu',
46
- }
47
-
48
- interface ISubNavProps {
49
- titleLink?: ITitleLink,
50
- titleContent?: React.JSX.Element,
51
- ctaLinks: ICtaItem[],
52
- hideGithubStars: boolean
53
- menuItems: MenuItem[],
54
- menuItemsAlign: 'left' | 'right',
55
- className?: string,
56
- labels?: Partial<SubNavLabels>
57
- }
58
-
59
- function Subnav({
60
- className,
61
- titleLink,
62
- ctaLinks = [],
63
- hideGithubStars,
64
- menuItems,
65
- menuItemsAlign = `right`,
66
- titleContent,
67
- labels: labelsProp
68
- }: ISubNavProps) {
69
- const labels = { ...defaultSubNavLabels, ...labelsProp }
70
-
71
- const { isStuck, stuckRef } = useStuckRef([])
72
-
73
- return (
74
- <div className={clsx(style.root, `bg-white dark:bg-slate-900`, className, {
75
- [style.isSticky]: isStuck,
76
- })} ref={stuckRef}>
77
- <Disclosure as="nav" >
78
- {({ open }) => (
79
- <>
80
- <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
81
- <div className="flex justify-between h-16">
82
- <div className={clsx(`flex grow`, menuItemsAlign == `right` ? ` justify-between` : ``)}>
83
-
84
- <div className="flex-shrink-0 flex items-center ">
85
- {titleLink &&<TitleLink
86
- text={titleLink.text}
87
-
88
- url={titleLink.url} >{titleContent}</TitleLink>}
89
- {!titleLink && titleContent}
90
- </div>
91
- <div className="hidden sm:-my-px sm:ml-6 sm:flex">
92
- <MenuItemsDefault
93
- menuItems={menuItems}
94
- />
95
- <CtaLinks
96
- hideGithubStars={hideGithubStars}
97
- isInDropdown={false}
98
- links={ctaLinks}
99
- />
100
- </div>
101
- </div>
102
-
103
- <div className="-mr-2 flex items-center sm:hidden ">
104
- {/* Mobile menu button */}
105
- <DisclosureButton className="bg-white inline-flex items-center justify-center p-2 rounded-control text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-secondary">
106
- <span className="sr-only">{labels.openMainMenu}</span>
107
- {open ? (
108
- <XMarkIcon aria-hidden="true" className="block h-6 w-6" />
109
- ) : (
110
- <Bars3Icon aria-hidden="true" className="block h-6 w-6" />
111
- )}
112
- </DisclosureButton>
113
- </div>
114
- </div>
115
-
116
- </div>
117
-
118
- <DisclosurePanel className="sm:hidden">
119
- <div className="pt-2 pb-3 space-y-1">
120
- <MenuItemsOverflow
121
- ctaLinks={ctaLinks}
122
- hideGithubStars={hideGithubStars}
123
- menuItems={menuItems}
124
- /> </div>
125
-
126
- </DisclosurePanel>
127
- </>
128
- )}
129
- </Disclosure>
130
- </div>
131
- )
132
- }
133
-
134
- export default Subnav
1
+ import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react'
2
+ import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
3
+ import type { LinkType, Variant } from '@dbosoft/react-uicore/linkbutton'
4
+ import MenuItemsOverflow from './partials/MenuItemsOverflow'
5
+ import MenuItemsDefault from './partials/MenuItemsDefault'
6
+ import CtaLinks from './partials/CtaLinks'
7
+ import useStuckRef from './helpers/useStuckRef'
8
+ import TitleLink from './partials/TitleLink'
9
+ import style from './style.module.scss'
10
+ import clsx from 'clsx'
11
+
12
+ export type MenuItem = 'divider' | ILinkMenuItem;
13
+
14
+ export interface IDropDownMenuItem {
15
+ text: string,
16
+ submenu: ILinkMenuItem[]
17
+ }
18
+
19
+ export interface ILinkMenuItem {
20
+ text: string,
21
+ url: string,
22
+ type: 'inbound' | 'outbound' | 'anchor'
23
+ active?: boolean
24
+ }
25
+
26
+ export interface ITitleLink {
27
+ url: string,
28
+ text?: string,
29
+ }
30
+
31
+
32
+ export interface ICtaItem {
33
+ text: string,
34
+ url: string,
35
+ variant?: Variant
36
+ linktype?: LinkType
37
+ className?: string,
38
+ }
39
+
40
+ export interface SubNavLabels {
41
+ openMainMenu: string
42
+ }
43
+
44
+ const defaultSubNavLabels: SubNavLabels = {
45
+ openMainMenu: 'Open main menu',
46
+ }
47
+
48
+ interface ISubNavProps {
49
+ titleLink?: ITitleLink,
50
+ titleContent?: React.JSX.Element,
51
+ ctaLinks: ICtaItem[],
52
+ hideGithubStars: boolean
53
+ menuItems: MenuItem[],
54
+ menuItemsAlign: 'left' | 'right',
55
+ className?: string,
56
+ labels?: Partial<SubNavLabels>
57
+ }
58
+
59
+ function Subnav({
60
+ className,
61
+ titleLink,
62
+ ctaLinks = [],
63
+ hideGithubStars,
64
+ menuItems,
65
+ menuItemsAlign = `right`,
66
+ titleContent,
67
+ labels: labelsProp
68
+ }: ISubNavProps) {
69
+ const labels = { ...defaultSubNavLabels, ...labelsProp }
70
+
71
+ const { isStuck, stuckRef } = useStuckRef([])
72
+
73
+ return (
74
+ <div className={clsx(style.root, `bg-white dark:bg-slate-900`, className, {
75
+ [style.isSticky]: isStuck,
76
+ })} ref={stuckRef}>
77
+ <Disclosure as="nav" >
78
+ {({ open }) => (
79
+ <>
80
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
81
+ <div className="flex justify-between h-16">
82
+ <div className={clsx(`flex grow`, menuItemsAlign == `right` ? ` justify-between` : ``)}>
83
+
84
+ <div className="flex-shrink-0 flex items-center ">
85
+ {titleLink &&<TitleLink
86
+ text={titleLink.text}
87
+
88
+ url={titleLink.url} >{titleContent}</TitleLink>}
89
+ {!titleLink && titleContent}
90
+ </div>
91
+ <div className="hidden sm:-my-px sm:ml-6 sm:flex">
92
+ <MenuItemsDefault
93
+ menuItems={menuItems}
94
+ />
95
+ <CtaLinks
96
+ hideGithubStars={hideGithubStars}
97
+ isInDropdown={false}
98
+ links={ctaLinks}
99
+ />
100
+ </div>
101
+ </div>
102
+
103
+ <div className="-mr-2 flex items-center sm:hidden ">
104
+ {/* Mobile menu button */}
105
+ <DisclosureButton className="bg-white inline-flex items-center justify-center p-2 rounded-control text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-secondary">
106
+ <span className="sr-only">{labels.openMainMenu}</span>
107
+ {open ? (
108
+ <XMarkIcon aria-hidden="true" className="block h-6 w-6" />
109
+ ) : (
110
+ <Bars3Icon aria-hidden="true" className="block h-6 w-6" />
111
+ )}
112
+ </DisclosureButton>
113
+ </div>
114
+ </div>
115
+
116
+ </div>
117
+
118
+ <DisclosurePanel className="sm:hidden">
119
+ <div className="pt-2 pb-3 space-y-1">
120
+ <MenuItemsOverflow
121
+ ctaLinks={ctaLinks}
122
+ hideGithubStars={hideGithubStars}
123
+ menuItems={menuItems}
124
+ /> </div>
125
+
126
+ </DisclosurePanel>
127
+ </>
128
+ )}
129
+ </Disclosure>
130
+ </div>
131
+ )
132
+ }
133
+
134
+ export default Subnav
@@ -1,25 +1,25 @@
1
- import formatStarCount from './index.js'
2
-
3
- describe('<Subnav /> - formatStarCount', () => {
4
- it('should return zero for an undefined, null, or zero count', () => {
5
- expect(formatStarCount(undefined)).toBe(false)
6
- expect(formatStarCount(null)).toBe(false)
7
- expect(formatStarCount(0)).toBe(false)
8
- })
9
-
10
- it('should return numbers 1 to 999 without formatting', () => {
11
- expect(formatStarCount(1)).toBe('1')
12
- expect(formatStarCount(999)).toBe('999')
13
- })
14
-
15
- it('should abbreviate numbers in the tens of thousands with "k" and a single decimal point', () => {
16
- expect(formatStarCount(1003)).toBe('1.0k')
17
- expect(formatStarCount(1280)).toBe('1.2k')
18
- expect(formatStarCount(15630)).toBe('15.6k')
19
- })
20
-
21
- it('should abbreviate numbers in the hundreds of thousands with "k" and no decimal point', () => {
22
- expect(formatStarCount(100000)).toBe('100k')
23
- expect(formatStarCount(156783)).toBe('156k')
24
- })
25
- })
1
+ import formatStarCount from './index.js'
2
+
3
+ describe('<Subnav /> - formatStarCount', () => {
4
+ it('should return zero for an undefined, null, or zero count', () => {
5
+ expect(formatStarCount(undefined)).toBe(false)
6
+ expect(formatStarCount(null)).toBe(false)
7
+ expect(formatStarCount(0)).toBe(false)
8
+ })
9
+
10
+ it('should return numbers 1 to 999 without formatting', () => {
11
+ expect(formatStarCount(1)).toBe('1')
12
+ expect(formatStarCount(999)).toBe('999')
13
+ })
14
+
15
+ it('should abbreviate numbers in the tens of thousands with "k" and a single decimal point', () => {
16
+ expect(formatStarCount(1003)).toBe('1.0k')
17
+ expect(formatStarCount(1280)).toBe('1.2k')
18
+ expect(formatStarCount(15630)).toBe('15.6k')
19
+ })
20
+
21
+ it('should abbreviate numbers in the hundreds of thousands with "k" and no decimal point', () => {
22
+ expect(formatStarCount(100000)).toBe('100k')
23
+ expect(formatStarCount(156783)).toBe('156k')
24
+ })
25
+ })
@@ -1,25 +1,25 @@
1
- import parseGithubUrl from './index.js'
2
-
3
- describe('<Subnav /> - parseGithubUrl', () => {
4
- it('should gracefully handle an invalid URL', () => {
5
- // Suppress console.warn for this test, we expect warnings
6
- jest.spyOn(console, 'warn')
7
- global.console.warn.mockImplementation(() => {})
8
- expect(parseGithubUrl(undefined)).toBe(false)
9
- expect(parseGithubUrl('blah-blah')).toBe(false)
10
- // Restore console.warn for further tests
11
- global.console.warn.mockRestore()
12
- })
13
-
14
- it('should handle a non-GitHub URL', () => {
15
- expect(parseGithubUrl('https://www.example.com/dbosoft/maxback')).toBe(
16
- false
17
- )
18
- })
19
-
20
- it('should parse the org and repo from a GitHub URL', () => {
21
- const testInput = 'https://www.github.com/dbosoft/maxback'
22
- expect(parseGithubUrl(testInput).repo).toBe('maxback')
23
- expect(parseGithubUrl(testInput).org).toBe('dbosoft')
24
- })
25
- })
1
+ import parseGithubUrl from './index.js'
2
+
3
+ describe('<Subnav /> - parseGithubUrl', () => {
4
+ it('should gracefully handle an invalid URL', () => {
5
+ // Suppress console.warn for this test, we expect warnings
6
+ jest.spyOn(console, 'warn')
7
+ global.console.warn.mockImplementation(() => {})
8
+ expect(parseGithubUrl(undefined)).toBe(false)
9
+ expect(parseGithubUrl('blah-blah')).toBe(false)
10
+ // Restore console.warn for further tests
11
+ global.console.warn.mockRestore()
12
+ })
13
+
14
+ it('should handle a non-GitHub URL', () => {
15
+ expect(parseGithubUrl('https://www.example.com/dbosoft/maxback')).toBe(
16
+ false
17
+ )
18
+ })
19
+
20
+ it('should parse the org and repo from a GitHub URL', () => {
21
+ const testInput = 'https://www.github.com/dbosoft/maxback'
22
+ expect(parseGithubUrl(testInput).repo).toBe('maxback')
23
+ expect(parseGithubUrl(testInput).org).toBe('dbosoft')
24
+ })
25
+ })
@@ -1,4 +1,4 @@
1
- <svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
2
- <title>GitHub</title>
3
- <path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
1
+ <svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
2
+ <title>GitHub</title>
3
+ <path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
4
4
  </svg>
@@ -1,71 +1,71 @@
1
- .root {
2
- display: flex;
3
- padding: 0 32px;
4
- margin: 0 auto;
5
- align-items: center;
6
- list-style: none;
7
-
8
- &.alignRight {
9
- margin-right: 0;
10
- padding-right: 16px;
11
- }
12
- }
13
-
14
- .listItem {
15
- position: relative;
16
- white-space: nowrap;
17
- margin: 0;
18
- padding: 0;
19
- }
20
-
21
- .navLink {
22
- composes: g-type-body-small-strong from global;
23
- position: relative;
24
- padding: 0 16px;
25
- line-height: 2.5rem;
26
- display: flex;
27
- align-items: center;
28
- white-space: nowrap;
29
- }
30
-
31
- .submenuItem {
32
- composes: g-type-body-small-strong from global;
33
- color: var(--black);
34
- }
35
-
36
- .submenuModal {
37
- border-radius: 4px;
38
- box-shadow: 0 8px 12px rgba(37, 38, 45, 0.08);
39
- background: var(--white);
40
- z-index: 1;
41
- display: block;
42
- position: absolute;
43
- top: 100%;
44
- margin: 8px 0 0 0;
45
- padding: 24px;
46
- left: 50%;
47
- transform: translateX(-50%);
48
- list-style: none;
49
-
50
- & li {
51
- & a:hover .text {
52
- text-decoration: underline;
53
- }
54
-
55
- & + li {
56
- margin-top: 6px;
57
- }
58
- }
59
-
60
- &.isCollapsed {
61
- display: none;
62
- }
63
- }
64
-
65
- .verticalDivider {
66
- background: var(--gray-5);
67
- height: 1.75rem;
68
- width: 1px;
69
- margin: 0 8px;
70
- display: block;
71
- }
1
+ .root {
2
+ display: flex;
3
+ padding: 0 32px;
4
+ margin: 0 auto;
5
+ align-items: center;
6
+ list-style: none;
7
+
8
+ &.alignRight {
9
+ margin-right: 0;
10
+ padding-right: 16px;
11
+ }
12
+ }
13
+
14
+ .listItem {
15
+ position: relative;
16
+ white-space: nowrap;
17
+ margin: 0;
18
+ padding: 0;
19
+ }
20
+
21
+ .navLink {
22
+ composes: g-type-body-small-strong from global;
23
+ position: relative;
24
+ padding: 0 16px;
25
+ line-height: 2.5rem;
26
+ display: flex;
27
+ align-items: center;
28
+ white-space: nowrap;
29
+ }
30
+
31
+ .submenuItem {
32
+ composes: g-type-body-small-strong from global;
33
+ color: var(--black);
34
+ }
35
+
36
+ .submenuModal {
37
+ border-radius: 4px;
38
+ box-shadow: 0 8px 12px rgba(37, 38, 45, 0.08);
39
+ background: var(--white);
40
+ z-index: 1;
41
+ display: block;
42
+ position: absolute;
43
+ top: 100%;
44
+ margin: 8px 0 0 0;
45
+ padding: 24px;
46
+ left: 50%;
47
+ transform: translateX(-50%);
48
+ list-style: none;
49
+
50
+ & li {
51
+ & a:hover .text {
52
+ text-decoration: underline;
53
+ }
54
+
55
+ & + li {
56
+ margin-top: 6px;
57
+ }
58
+ }
59
+
60
+ &.isCollapsed {
61
+ display: none;
62
+ }
63
+ }
64
+
65
+ .verticalDivider {
66
+ background: var(--gray-5);
67
+ height: 1.75rem;
68
+ width: 1px;
69
+ margin: 0 8px;
70
+ display: block;
71
+ }
@@ -1,51 +1,51 @@
1
- .root {
2
- position: relative;
3
- margin-left: auto;
4
- }
5
-
6
- .dropdown {
7
- background: var(--white);
8
- border-radius: 4px;
9
- box-shadow: 0 8px 12px rgba(37, 38, 45, 0.08);
10
- margin-top: 8px;
11
- max-height: calc(100vh - 300%);
12
- overflow-y: auto;
13
- padding: 12px 24px 0 24px;
14
- position: absolute;
15
- right: 0;
16
- top: 100%;
17
- width: 256px;
18
- z-index: 1;
19
-
20
- &.isCollapsed {
21
- display: none;
22
- }
23
- }
24
-
25
- .ulElem {
26
- padding: 0;
27
- margin: 0;
28
- list-style: none;
29
- }
30
-
31
- .submenuTitle {
32
- composes: g-type-label from global;
33
- margin: 12px 0 8px 0;
34
- color: var(--gray-3);
35
- }
36
-
37
- .divider {
38
- margin: 12px 0;
39
- border: 0;
40
- padding: 0;
41
- height: 1px;
42
- background: var(--gray-5);
43
- }
44
-
45
- .submenuItem {
46
- composes: g-type-body-small-strong from global;
47
- display: block;
48
- padding: 4px 0;
49
- line-height: 1.6em;
50
- color: var(--black);
51
- }
1
+ .root {
2
+ position: relative;
3
+ margin-left: auto;
4
+ }
5
+
6
+ .dropdown {
7
+ background: var(--white);
8
+ border-radius: 4px;
9
+ box-shadow: 0 8px 12px rgba(37, 38, 45, 0.08);
10
+ margin-top: 8px;
11
+ max-height: calc(100vh - 300%);
12
+ overflow-y: auto;
13
+ padding: 12px 24px 0 24px;
14
+ position: absolute;
15
+ right: 0;
16
+ top: 100%;
17
+ width: 256px;
18
+ z-index: 1;
19
+
20
+ &.isCollapsed {
21
+ display: none;
22
+ }
23
+ }
24
+
25
+ .ulElem {
26
+ padding: 0;
27
+ margin: 0;
28
+ list-style: none;
29
+ }
30
+
31
+ .submenuTitle {
32
+ composes: g-type-label from global;
33
+ margin: 12px 0 8px 0;
34
+ color: var(--gray-3);
35
+ }
36
+
37
+ .divider {
38
+ margin: 12px 0;
39
+ border: 0;
40
+ padding: 0;
41
+ height: 1px;
42
+ background: var(--gray-5);
43
+ }
44
+
45
+ .submenuItem {
46
+ composes: g-type-body-small-strong from global;
47
+ display: block;
48
+ padding: 4px 0;
49
+ line-height: 1.6em;
50
+ color: var(--black);
51
+ }
@@ -1,25 +1,25 @@
1
- import type { FC, ReactElement, ReactNode } from 'react'
2
- import LinkWrap from '@dbosoft/react-uicore/link-wrap'
3
- import Link from 'next/link'
4
-
5
-
6
- const TitleLink: FC<{
7
- text?: string,
8
- url: string,
9
- children?: ReactNode
10
- }> = ({
11
- text,
12
- url, children }) => {
13
- return (
14
- <LinkWrap
15
- Link={Link}
16
- className="font-semibold text-gray-900 tracking-widest text-lg inline"
17
- href={url}
18
- title={text}
19
- >{text}
20
- {children}
21
- </LinkWrap>
22
- )
23
- }
24
-
25
- export default TitleLink
1
+ import type { FC, ReactElement, ReactNode } from 'react'
2
+ import LinkWrap from '@dbosoft/react-uicore/link-wrap'
3
+ import Link from 'next/link'
4
+
5
+
6
+ const TitleLink: FC<{
7
+ text?: string,
8
+ url: string,
9
+ children?: ReactNode
10
+ }> = ({
11
+ text,
12
+ url, children }) => {
13
+ return (
14
+ <LinkWrap
15
+ Link={Link}
16
+ className="font-semibold text-gray-900 tracking-widest text-lg inline"
17
+ href={url}
18
+ title={text}
19
+ >{text}
20
+ {children}
21
+ </LinkWrap>
22
+ )
23
+ }
24
+
25
+ export default TitleLink
@@ -1,19 +1,19 @@
1
- .root {
2
- color: var(--black);
3
- position: relative;
4
-
5
- &::after {
6
- content: '';
7
- position: absolute;
8
- left: 0;
9
- right: 0;
10
- bottom: 0;
11
- height: 2px;
12
- background: var(--black);
13
- opacity: 0;
14
- }
15
-
16
- &.isActive::after {
17
- opacity: 1;
18
- }
19
- }
1
+ .root {
2
+ color: var(--black);
3
+ position: relative;
4
+
5
+ &::after {
6
+ content: '';
7
+ position: absolute;
8
+ left: 0;
9
+ right: 0;
10
+ bottom: 0;
11
+ height: 2px;
12
+ background: var(--black);
13
+ opacity: 0;
14
+ }
15
+
16
+ &.isActive::after {
17
+ opacity: 1;
18
+ }
19
+ }
@@ -1,13 +1,13 @@
1
- .root {
2
- --gray-6-transparent: rgba(174, 176, 183, 0.45);
3
- position: sticky;
4
- top: -1px;
5
- z-index: 800;
6
- border-top: 1px solid var(--gray-6-transparent);
7
- border-bottom: 1px solid transparent;
8
- transition: border-bottom-color 0.8s;
9
-
10
- &.isSticky {
11
- border-bottom-color: var(--gray-6-transparent);
12
- }
13
- }
1
+ .root {
2
+ --gray-6-transparent: rgba(174, 176, 183, 0.45);
3
+ position: sticky;
4
+ top: -1px;
5
+ z-index: 800;
6
+ border-top: 1px solid var(--gray-6-transparent);
7
+ border-bottom: 1px solid transparent;
8
+ transition: border-bottom-color 0.8s;
9
+
10
+ &.isSticky {
11
+ border-bottom-color: var(--gray-6-transparent);
12
+ }
13
+ }
@@ -1,139 +1,139 @@
1
- import { useEffect, useState } from 'react'
2
- import { useTheme } from 'next-themes'
3
- import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } from '@headlessui/react'
4
- import clsx from 'clsx'
5
-
6
- export interface ThemeSelectorLabels {
7
- light: string
8
- dark: string
9
- system: string
10
- theme: string
11
- themeAriaLabel: string
12
- }
13
-
14
- const defaultThemeSelectorLabels: ThemeSelectorLabels = {
15
- light: 'Light',
16
- dark: 'Dark',
17
- system: 'System',
18
- theme: 'Theme',
19
- themeAriaLabel: 'Theme',
20
- }
21
-
22
- function LightIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
23
- return (
24
- <svg aria-hidden="true" viewBox="0 0 16 16" {...props}>
25
- <path
26
- fillRule="evenodd"
27
- clipRule="evenodd"
28
- 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"
29
- />
30
- </svg>
31
- )
32
- }
33
-
34
- function DarkIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
35
- return (
36
- <svg aria-hidden="true" viewBox="0 0 16 16" {...props}>
37
- <path
38
- fillRule="evenodd"
39
- clipRule="evenodd"
40
- 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"
41
- />
42
- </svg>
43
- )
44
- }
45
-
46
- function SystemIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
47
- return (
48
- <svg aria-hidden="true" viewBox="0 0 16 16" {...props}>
49
- <path
50
- fillRule="evenodd"
51
- clipRule="evenodd"
52
- 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"
53
- />
54
- </svg>
55
- )
56
- }
57
-
58
- export function ThemeSelector({
59
- labels: labelsProp,
60
- ...props
61
- }: React.ComponentPropsWithoutRef<typeof Listbox<'div'>> & {
62
- labels?: Partial<ThemeSelectorLabels>
63
- }) {
64
- const labels = { ...defaultThemeSelectorLabels, ...labelsProp }
65
- const themes = [
66
- { name: labels.light, value: 'light', icon: LightIcon },
67
- { name: labels.dark, value: 'dark', icon: DarkIcon },
68
- { name: labels.system, value: 'system', icon: SystemIcon },
69
- ]
70
-
71
- let { theme, setTheme } = useTheme()
72
- let [mounted, setMounted] = useState(false)
73
-
74
- useEffect(() => {
75
- setMounted(true)
76
- }, [])
77
-
78
- if (!mounted) {
79
- return <div className="h-6 w-6" />
80
- }
81
-
82
- return (
83
- <Listbox as="div" value={theme} onChange={setTheme} {...props}>
84
- <ListboxLabel className="sr-only">{labels.theme}</ListboxLabel>
85
- <ListboxButton
86
- className="flex h-9 w-12 mt-3 items-center justify-center rounded-container-sm shadow-popover shadow-black/5 ring-1 ring-black/5 dark:bg-slate-700 dark:ring-inset dark:ring-white/5"
87
- aria-label={labels.themeAriaLabel}
88
- >
89
- <LightIcon
90
- className={clsx(
91
- 'h-4 w-4 dark:hidden',
92
- theme === 'system' ? 'fill-slate-400' : 'fill-sky-400',
93
- )}
94
- />
95
- <DarkIcon
96
- className={clsx(
97
- 'hidden h-4 w-4 dark:block',
98
- theme === 'system' ? 'fill-slate-400' : 'fill-sky-400',
99
- )}
100
- />
101
- </ListboxButton>
102
- <ListboxOptions className="absolute left-1/2 top-full mt-3 w-36 -translate-x-1/2 space-y-1 rounded-container bg-white p-3 text-sm font-medium shadow-popover shadow-black/5 ring-1 ring-black/5 dark:bg-slate-800 dark:ring-white/5">
103
- {themes.map((theme) => (
104
- <ListboxOption
105
- key={theme.value}
106
- value={theme.value}
107
- className={({ focus, selected }) =>
108
- clsx(
109
- 'flex cursor-pointer select-none items-center rounded-[0.625rem] p-1',
110
- {
111
- 'text-sky-500': selected,
112
- 'text-slate-900 dark:text-white': focus && !selected,
113
- 'text-slate-700 dark:text-slate-400': !focus && !selected,
114
- 'bg-slate-100 dark:bg-slate-900/40': focus,
115
- },
116
- )
117
- }
118
- >
119
- {({ selected }) => (
120
- <>
121
- <div className="rounded-control bg-white p-1 shadow-control ring-1 ring-slate-900/5 dark:bg-slate-700 dark:ring-inset dark:ring-white/5">
122
- <theme.icon
123
- className={clsx(
124
- 'h-4 w-4',
125
- selected
126
- ? 'fill-sky-400 dark:fill-sky-400'
127
- : 'fill-slate-400',
128
- )}
129
- />
130
- </div>
131
- <div className="ml-3">{theme.name}</div>
132
- </>
133
- )}
134
- </ListboxOption>
135
- ))}
136
- </ListboxOptions>
137
- </Listbox>
138
- )
139
- }
1
+ import { useEffect, useState } from 'react'
2
+ import { useTheme } from 'next-themes'
3
+ import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } from '@headlessui/react'
4
+ import clsx from 'clsx'
5
+
6
+ export interface ThemeSelectorLabels {
7
+ light: string
8
+ dark: string
9
+ system: string
10
+ theme: string
11
+ themeAriaLabel: string
12
+ }
13
+
14
+ const defaultThemeSelectorLabels: ThemeSelectorLabels = {
15
+ light: 'Light',
16
+ dark: 'Dark',
17
+ system: 'System',
18
+ theme: 'Theme',
19
+ themeAriaLabel: 'Theme',
20
+ }
21
+
22
+ function LightIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
23
+ return (
24
+ <svg aria-hidden="true" viewBox="0 0 16 16" {...props}>
25
+ <path
26
+ fillRule="evenodd"
27
+ clipRule="evenodd"
28
+ 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"
29
+ />
30
+ </svg>
31
+ )
32
+ }
33
+
34
+ function DarkIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
35
+ return (
36
+ <svg aria-hidden="true" viewBox="0 0 16 16" {...props}>
37
+ <path
38
+ fillRule="evenodd"
39
+ clipRule="evenodd"
40
+ 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"
41
+ />
42
+ </svg>
43
+ )
44
+ }
45
+
46
+ function SystemIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
47
+ return (
48
+ <svg aria-hidden="true" viewBox="0 0 16 16" {...props}>
49
+ <path
50
+ fillRule="evenodd"
51
+ clipRule="evenodd"
52
+ 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"
53
+ />
54
+ </svg>
55
+ )
56
+ }
57
+
58
+ export function ThemeSelector({
59
+ labels: labelsProp,
60
+ ...props
61
+ }: React.ComponentPropsWithoutRef<typeof Listbox<'div'>> & {
62
+ labels?: Partial<ThemeSelectorLabels>
63
+ }) {
64
+ const labels = { ...defaultThemeSelectorLabels, ...labelsProp }
65
+ const themes = [
66
+ { name: labels.light, value: 'light', icon: LightIcon },
67
+ { name: labels.dark, value: 'dark', icon: DarkIcon },
68
+ { name: labels.system, value: 'system', icon: SystemIcon },
69
+ ]
70
+
71
+ let { theme, setTheme } = useTheme()
72
+ let [mounted, setMounted] = useState(false)
73
+
74
+ useEffect(() => {
75
+ setMounted(true)
76
+ }, [])
77
+
78
+ if (!mounted) {
79
+ return <div className="h-6 w-6" />
80
+ }
81
+
82
+ return (
83
+ <Listbox as="div" value={theme} onChange={setTheme} {...props}>
84
+ <ListboxLabel className="sr-only">{labels.theme}</ListboxLabel>
85
+ <ListboxButton
86
+ className="flex h-9 w-12 mt-3 items-center justify-center rounded-container-sm shadow-popover shadow-black/5 ring-1 ring-black/5 dark:bg-slate-700 dark:ring-inset dark:ring-white/5"
87
+ aria-label={labels.themeAriaLabel}
88
+ >
89
+ <LightIcon
90
+ className={clsx(
91
+ 'h-4 w-4 dark:hidden',
92
+ theme === 'system' ? 'fill-slate-400' : 'fill-sky-400',
93
+ )}
94
+ />
95
+ <DarkIcon
96
+ className={clsx(
97
+ 'hidden h-4 w-4 dark:block',
98
+ theme === 'system' ? 'fill-slate-400' : 'fill-sky-400',
99
+ )}
100
+ />
101
+ </ListboxButton>
102
+ <ListboxOptions className="absolute left-1/2 top-full mt-3 w-36 -translate-x-1/2 space-y-1 rounded-container bg-white p-3 text-sm font-medium shadow-popover shadow-black/5 ring-1 ring-black/5 dark:bg-slate-800 dark:ring-white/5">
103
+ {themes.map((theme) => (
104
+ <ListboxOption
105
+ key={theme.value}
106
+ value={theme.value}
107
+ className={({ focus, selected }) =>
108
+ clsx(
109
+ 'flex cursor-pointer select-none items-center rounded-[0.625rem] p-1',
110
+ {
111
+ 'text-sky-500': selected,
112
+ 'text-slate-900 dark:text-white': focus && !selected,
113
+ 'text-slate-700 dark:text-slate-400': !focus && !selected,
114
+ 'bg-slate-100 dark:bg-slate-900/40': focus,
115
+ },
116
+ )
117
+ }
118
+ >
119
+ {({ selected }) => (
120
+ <>
121
+ <div className="rounded-control bg-white p-1 shadow-control ring-1 ring-slate-900/5 dark:bg-slate-700 dark:ring-inset dark:ring-white/5">
122
+ <theme.icon
123
+ className={clsx(
124
+ 'h-4 w-4',
125
+ selected
126
+ ? 'fill-sky-400 dark:fill-sky-400'
127
+ : 'fill-slate-400',
128
+ )}
129
+ />
130
+ </div>
131
+ <div className="ml-3">{theme.name}</div>
132
+ </>
133
+ )}
134
+ </ListboxOption>
135
+ ))}
136
+ </ListboxOptions>
137
+ </Listbox>
138
+ )
139
+ }
package/tsconfig.json CHANGED
@@ -1,8 +1,8 @@
1
- {
2
- "extends": "@dbosoft/typescript-config/nextjs.json",
3
- "compilerOptions": {
4
- "outDir": "dist"
5
- },
6
- "include": ["src"],
7
- "exclude": ["node_modules", "dist"]
1
+ {
2
+ "extends": "@dbosoft/typescript-config/nextjs.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist"
5
+ },
6
+ "include": ["src"],
7
+ "exclude": ["node_modules", "dist"]
8
8
  }