@dbosoft/nextjs-uicore 1.5.0 → 1.6.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 CHANGED
@@ -1,5 +1,24 @@
1
1
  # @dbosoft/nextjs-uicore
2
2
 
3
+ ## 1.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 42189cd: Rework design system
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [42189cd]
12
+ - @dbosoft/react-uicore@1.4.0
13
+
14
+ ## 1.5.1
15
+
16
+ ### Patch Changes
17
+
18
+ - react 19.x bug fixes
19
+ - Updated dependencies
20
+ - @dbosoft/react-uicore@1.3.1
21
+
3
22
  ## 1.5.0
4
23
 
5
24
  ### Minor Changes
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@dbosoft/nextjs-uicore",
3
- "description": "Core UI components for Next.js",
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
+ "deprecated": true,
4
5
  "author": "dbosoft",
5
- "version": "1.5.0",
6
+ "version": "1.6.0",
6
7
  "sideEffects": false,
7
8
  "license": "MIT",
8
9
  "exports": {
@@ -34,10 +35,11 @@
34
35
  "@heroicons/react": ">=2.1.0",
35
36
  "clsx": ">=2.1.0",
36
37
  "next-themes": ">=0.4.3",
37
- "@dbosoft/react-uicore": "1.3.0"
38
+ "@dbosoft/react-uicore": "1.4.0"
38
39
  },
39
40
  "scripts": {
40
- "lint": "eslint . --max-warnings 0",
41
+ "check-types": "tsc --noEmit",
42
+ "lint": "echo 'DEPRECATED: skipping lint for nextjs-uicore'",
41
43
  "clean": "rimraf .turbo && rimraf node_modules && rimraf dist"
42
44
  }
43
45
  }
@@ -1,119 +1,119 @@
1
- import Head from 'next/head'
2
-
3
- export default function DBOHead(props: DBOHeadProps): React.ReactElement {
4
- return (
5
- <Head>
6
- {whenString(props.title, <title>{props.title}</title>)}
7
- <meta httpEquiv="x-ua-compatible" content="ie=edge" />
8
- <meta property="og:locale" content="en_US" key="og:locale" />
9
- <meta property="og:type" content="website" key="og:type" />
10
- <meta
11
- property="article:publisher"
12
- content="https://www.facebook.com/dbosoft/"
13
- key="article:publisher"
14
- />
15
- <meta name="twitter:site" content="@dbosoft" key="twitter:site" />
16
- <meta
17
- name="twitter:card"
18
- content={props.twitterCard || 'summary_large_image'}
19
- key="twitter:card"
20
- />
21
- <meta name="theme-color" content="#000" key="themeColor" />
22
- {whenString(
23
- props.description,
24
- <meta
25
- name="description"
26
- property="og:description"
27
- content={props.description}
28
- key="description"
29
- />
30
- )}
31
- {whenString(
32
- props.siteName,
33
- <meta
34
- property="og:site_name"
35
- content={props.siteName}
36
- key="og:site_name"
37
- />
38
- )}
39
- {whenString(
40
- props.pageName,
41
- <meta property="og:title" content={props.pageName} key="og:title" />
42
- )}
43
- {whenString(
44
- props.image,
45
- <meta property="og:image" content={props.image} key="og:image" />
46
- )}
47
- {whenString(
48
- props.canonicalUrl,
49
- <link rel="canonical" key="canonical" href={props.canonicalUrl} />
50
- )}
51
- {whenArray(props.preload, ({ href, ...linkProps }: {href:string}) => (
52
- <link href={href} {...linkProps} rel="preload" key={href} />
53
- ))}
54
- {whenArray(props.icon, ({ href, ...linkProps }: {href:string}) => (
55
- <link href={href} {...linkProps} rel="icon" key={href} />
56
- ))}
57
- {whenArray(props.stylesheet, ({ href, ...linkProps }: {href:string}) => (
58
- <link href={href} {...linkProps} rel="stylesheet" key={href} />
59
- ))}
60
- {props.children}
61
- </Head>
62
- )
63
- }
64
-
65
- /** Return a map of the value using the callback if it is an Array. */
66
- const whenArray = (value : any, mapFn: $TSFixMe) =>
67
- Array.isArray(value) ? value.map(mapFn) : null
68
-
69
- /** Return the value if it is a String */
70
- const whenString = (value : any, returnValue: any) =>
71
- typeof value === 'string' ? returnValue : null
72
-
73
- // -----
74
- // Types
75
- // -----
76
-
77
- interface DBOHeadProps {
78
- canonicalUrl?: string
79
- children?: React.ReactNode
80
- description?: string
81
- icon?: {
82
- [key: string]: unknown
83
- href: string
84
- sizes?: string
85
- type?: string
86
- }[]
87
- image?: string
88
- pageName?: string
89
- preload?: {
90
- [key: string]: unknown
91
- as: asProp
92
- href: string
93
- type?: string
94
- }[]
95
- siteName?: string
96
- stylesheet?: {
97
- [key: string]: unknown
98
- href: string
99
- media?: string
100
- }[]
101
- title?: string
102
- twitterCard?: TwitterCardProp
103
- }
104
-
105
- type TwitterCardProp = 'summary' | 'summary_large_image'
106
-
107
- type asProp =
108
- | 'audio'
109
- | 'document'
110
- | 'embed'
111
- | 'fetch'
112
- | 'font'
113
- | 'image'
114
- | 'object'
115
- | 'script'
116
- | 'style'
117
- | 'track'
118
- | 'video'
119
- | 'worker'
1
+ import Head from 'next/head'
2
+
3
+ export default function DBOHead(props: DBOHeadProps): React.ReactElement {
4
+ return (
5
+ <Head>
6
+ {whenString(props.title, <title>{props.title}</title>)}
7
+ <meta httpEquiv="x-ua-compatible" content="ie=edge" />
8
+ <meta property="og:locale" content="en_US" key="og:locale" />
9
+ <meta property="og:type" content="website" key="og:type" />
10
+ <meta
11
+ property="article:publisher"
12
+ content="https://www.facebook.com/dbosoft/"
13
+ key="article:publisher"
14
+ />
15
+ <meta name="twitter:site" content="@dbosoft" key="twitter:site" />
16
+ <meta
17
+ name="twitter:card"
18
+ content={props.twitterCard || 'summary_large_image'}
19
+ key="twitter:card"
20
+ />
21
+ <meta name="theme-color" content="#000" key="themeColor" />
22
+ {whenString(
23
+ props.description,
24
+ <meta
25
+ name="description"
26
+ property="og:description"
27
+ content={props.description}
28
+ key="description"
29
+ />
30
+ )}
31
+ {whenString(
32
+ props.siteName,
33
+ <meta
34
+ property="og:site_name"
35
+ content={props.siteName}
36
+ key="og:site_name"
37
+ />
38
+ )}
39
+ {whenString(
40
+ props.pageName,
41
+ <meta property="og:title" content={props.pageName} key="og:title" />
42
+ )}
43
+ {whenString(
44
+ props.image,
45
+ <meta property="og:image" content={props.image} key="og:image" />
46
+ )}
47
+ {whenString(
48
+ props.canonicalUrl,
49
+ <link rel="canonical" key="canonical" href={props.canonicalUrl} />
50
+ )}
51
+ {whenArray(props.preload, ({ href, ...linkProps }: {href:string}) => (
52
+ <link href={href} {...linkProps} rel="preload" key={href} />
53
+ ))}
54
+ {whenArray(props.icon, ({ href, ...linkProps }: {href:string}) => (
55
+ <link href={href} {...linkProps} rel="icon" key={href} />
56
+ ))}
57
+ {whenArray(props.stylesheet, ({ href, ...linkProps }: {href:string}) => (
58
+ <link href={href} {...linkProps} rel="stylesheet" key={href} />
59
+ ))}
60
+ {props.children}
61
+ </Head>
62
+ )
63
+ }
64
+
65
+ /** Return a map of the value using the callback if it is an Array. */
66
+ const whenArray = (value : any, mapFn: $TSFixMe) =>
67
+ Array.isArray(value) ? value.map(mapFn) : null
68
+
69
+ /** Return the value if it is a String */
70
+ const whenString = (value : any, returnValue: any) =>
71
+ typeof value === 'string' ? returnValue : null
72
+
73
+ // -----
74
+ // Types
75
+ // -----
76
+
77
+ interface DBOHeadProps {
78
+ canonicalUrl?: string
79
+ children?: React.ReactNode
80
+ description?: string
81
+ icon?: {
82
+ [key: string]: unknown
83
+ href: string
84
+ sizes?: string
85
+ type?: string
86
+ }[]
87
+ image?: string
88
+ pageName?: string
89
+ preload?: {
90
+ [key: string]: unknown
91
+ as: asProp
92
+ href: string
93
+ type?: string
94
+ }[]
95
+ siteName?: string
96
+ stylesheet?: {
97
+ [key: string]: unknown
98
+ href: string
99
+ media?: string
100
+ }[]
101
+ title?: string
102
+ twitterCard?: TwitterCardProp
103
+ }
104
+
105
+ type TwitterCardProp = 'summary' | 'summary_large_image'
106
+
107
+ type asProp =
108
+ | 'audio'
109
+ | 'document'
110
+ | 'embed'
111
+ | 'fetch'
112
+ | 'font'
113
+ | 'image'
114
+ | 'object'
115
+ | 'script'
116
+ | 'style'
117
+ | 'track'
118
+ | 'video'
119
+ | 'worker'
@@ -42,7 +42,7 @@ export default function useStuckRef(deps: DependencyList) {
42
42
  return intersectionObserver.disconnect.bind(intersectionObserver)
43
43
  }
44
44
  return () => { };
45
- }, deps);
45
+ }, [...deps]);
46
46
 
47
47
 
48
48
 
@@ -8,7 +8,6 @@ import useStuckRef from './helpers/useStuckRef'
8
8
  import TitleLink from './partials/TitleLink'
9
9
  import style from './style.module.scss'
10
10
  import clsx from 'clsx'
11
- import { ThemeSelector } from '../themeselector'
12
11
 
13
12
  export type MenuItem = 'divider' | ILinkMenuItem;
14
13
 
@@ -48,7 +47,7 @@ const defaultSubNavLabels: SubNavLabels = {
48
47
 
49
48
  interface ISubNavProps {
50
49
  titleLink?: ITitleLink,
51
- titleContent?: JSX.Element,
50
+ titleContent?: React.JSX.Element,
52
51
  ctaLinks: ICtaItem[],
53
52
  hideGithubStars: boolean
54
53
  menuItems: MenuItem[],
@@ -103,7 +102,7 @@ function Subnav({
103
102
 
104
103
  <div className="-mr-2 flex items-center sm:hidden ">
105
104
  {/* Mobile menu button */}
106
- <DisclosureButton 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">
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">
107
106
  <span className="sr-only">{labels.openMainMenu}</span>
108
107
  {open ? (
109
108
  <XMarkIcon aria-hidden="true" className="block h-6 w-6" />
@@ -1,17 +1,17 @@
1
- /*
2
- * Given:
3
- * starCount (int)
4
- * Return:
5
- * Formatted string, to match GitHub's typical display of star counts,
6
- * that is, expressed as thousands of stars
7
- * Or returns false for falsy starCount values
8
- */
9
- function formatStarCount(starCount: number) {
10
- if (!starCount || starCount <= 0) return false
11
- if (starCount < 1000) return `${starCount}`
12
- const thousands = Math.floor(starCount / 100.0) / 10.0
13
- if (starCount < 100000) return `${thousands.toFixed(1)}k`
14
- return `${Math.floor(thousands)}k`
15
- }
16
-
17
- export default formatStarCount
1
+ /*
2
+ * Given:
3
+ * starCount (int)
4
+ * Return:
5
+ * Formatted string, to match GitHub's typical display of star counts,
6
+ * that is, expressed as thousands of stars
7
+ * Or returns false for falsy starCount values
8
+ */
9
+ function formatStarCount(starCount: number) {
10
+ if (!starCount || starCount <= 0) return false
11
+ if (starCount < 1000) return `${starCount}`
12
+ const thousands = Math.floor(starCount / 100.0) / 10.0
13
+ if (starCount < 100000) return `${thousands.toFixed(1)}k`
14
+ return `${Math.floor(thousands)}k`
15
+ }
16
+
17
+ export default formatStarCount
@@ -1,96 +1,96 @@
1
- "use client";
2
-
3
- import React, { useEffect, useState } from 'react'
4
- import type { IconObject } from '@dbosoft/react-uicore/linkbutton';
5
- import LinkButton from '@dbosoft/react-uicore/linkbutton';
6
- import { StarIcon } from '@heroicons/react/16/solid'
7
- import GithubIcon from '../icons/github.svg'
8
- import formatStarCount from './formatStarCount'
9
- import parseGithubUrl from './parseGithubUrl'
10
- import Link from 'next/link';
11
-
12
- export interface GithubStarsLinkLabels {
13
- github: string
14
- }
15
-
16
- const defaultGithubStarsLinkLabels: GithubStarsLinkLabels = {
17
- github: 'Github',
18
- }
19
-
20
- function GithubStarsButton({ url, hideGithubStars, labels: labelsProp }: { url: string, hideGithubStars: boolean, labels?: Partial<GithubStarsLinkLabels> }) {
21
- const labels = { ...defaultGithubStarsLinkLabels, ...labelsProp }
22
- const [starCount, setStarCount] = useState(-1)
23
-
24
- useEffect(() => {
25
- if (hideGithubStars) { setStarCount(0); return; }
26
- const parseResult = parseGithubUrl(url);
27
- const { org, repo } = parseResult !== false ? parseResult : { org: undefined, repo: undefined };
28
-
29
- if (!org || !repo) { setStarCount(0); return; }
30
- const githubApiUrl = `https://api.github.com/repos/${org}/${repo}`
31
- fetch(githubApiUrl)
32
- .then((response) => {
33
- response.json().then((data) => {
34
- // Github's rate limit for unauthenticated requests is 60 per hour
35
- // When the limit is hit, data.stargazers_count is undefined,
36
- // and setStarCount falls back to not showing the star count
37
- setStarCount(data.stargazers_count)
38
- // Warn if this limit is hit, to avoid otherwise confusing behavior
39
- // We're still using the response to provide a documentation link
40
- if (!data.stargazers_count) {
41
- const { headers } = response
42
- if (headers.get('x-ratelimit-remaining') === '0') {
43
- const resetAtSeconds = parseInt(headers.get('x-ratelimit-reset') || '')
44
- const resetDate = new Date(resetAtSeconds * 1000)
45
- const rateLimit = headers.get('x-ratelimit-limit')
46
- console.warn(
47
- `⭐ Stargazers count could not be fetched. Rate limit exceeded for unauthenticated GitHub API. Limit will be reset to ${rateLimit} at ${resetDate}. See ${data.documentation_url} for more details.`
48
- )
49
- } else {
50
- console.warn(
51
- `Request for stargazers was successful, but the returned value was undefined or falsy. This might be because the repo has no stars, or it might be a different issue.`
52
- )
53
- }
54
- }
55
- })
56
- })
57
- .catch((err) => {
58
- setStarCount(0)
59
- console.warn(JSON.stringify(err, null, 2))
60
- })
61
- }, [hideGithubStars, url])
62
-
63
- const isLoadingStarCount = starCount === -1
64
- const isFailedStarCount = formatStarCount(starCount) === false
65
-
66
- const showStarCount = !hideGithubStars && (isLoadingStarCount || !isFailedStarCount);
67
-
68
- const icon: IconObject = {
69
- position: 'right',
70
- svg: GithubIcon,
71
- }
72
-
73
- return (
74
- <LinkButton icon={!showStarCount ? icon : undefined} label={labels.github} text={!showStarCount ? labels.github : ""}
75
- variant='neutral'
76
- url={url} external
77
- Link={Link}
78
- >
79
- {showStarCount ? <div className="inline-flex">
80
- <GithubIcon className="w-5 mr-2" />
81
- <div className='-my-2 mx-2 h-9 block bg-gray-300' style={{ width: "1px" }} />
82
-
83
- <span className="inline-flex gap-1 items-center">
84
- <StarIcon className='w-5 fill-yellow-500' />
85
- <span className="g-type-body-small-strong" data-testid="github-stars">
86
- {formatStarCount(starCount) || <span>&mdash;</span>}
87
- </span>
88
- </span>
89
-
90
- </div> : null}
91
- </LinkButton>
92
-
93
- )
94
- }
95
-
96
- export default GithubStarsButton
1
+ "use client";
2
+
3
+ import React, { useEffect, useState } from 'react'
4
+ import type { IconObject } from '@dbosoft/react-uicore/linkbutton';
5
+ import LinkButton from '@dbosoft/react-uicore/linkbutton';
6
+ import { StarIcon } from '@heroicons/react/16/solid'
7
+ import GithubIcon from '../icons/github.svg'
8
+ import formatStarCount from './formatStarCount'
9
+ import parseGithubUrl from './parseGithubUrl'
10
+ import Link from 'next/link';
11
+
12
+ export interface GithubStarsLinkLabels {
13
+ github: string
14
+ }
15
+
16
+ const defaultGithubStarsLinkLabels: GithubStarsLinkLabels = {
17
+ github: 'Github',
18
+ }
19
+
20
+ function GithubStarsButton({ url, hideGithubStars, labels: labelsProp }: { url: string, hideGithubStars: boolean, labels?: Partial<GithubStarsLinkLabels> }) {
21
+ const labels = { ...defaultGithubStarsLinkLabels, ...labelsProp }
22
+ const [starCount, setStarCount] = useState(-1)
23
+
24
+ useEffect(() => {
25
+ if (hideGithubStars) { setStarCount(0); return; }
26
+ const parseResult = parseGithubUrl(url);
27
+ const { org, repo } = parseResult !== false ? parseResult : { org: undefined, repo: undefined };
28
+
29
+ if (!org || !repo) { setStarCount(0); return; }
30
+ const githubApiUrl = `https://api.github.com/repos/${org}/${repo}`
31
+ fetch(githubApiUrl)
32
+ .then((response) => {
33
+ response.json().then((data) => {
34
+ // Github's rate limit for unauthenticated requests is 60 per hour
35
+ // When the limit is hit, data.stargazers_count is undefined,
36
+ // and setStarCount falls back to not showing the star count
37
+ setStarCount(data.stargazers_count)
38
+ // Warn if this limit is hit, to avoid otherwise confusing behavior
39
+ // We're still using the response to provide a documentation link
40
+ if (!data.stargazers_count) {
41
+ const { headers } = response
42
+ if (headers.get('x-ratelimit-remaining') === '0') {
43
+ const resetAtSeconds = parseInt(headers.get('x-ratelimit-reset') || '')
44
+ const resetDate = new Date(resetAtSeconds * 1000)
45
+ const rateLimit = headers.get('x-ratelimit-limit')
46
+ console.warn(
47
+ `⭐ Stargazers count could not be fetched. Rate limit exceeded for unauthenticated GitHub API. Limit will be reset to ${rateLimit} at ${resetDate}. See ${data.documentation_url} for more details.`
48
+ )
49
+ } else {
50
+ console.warn(
51
+ `Request for stargazers was successful, but the returned value was undefined or falsy. This might be because the repo has no stars, or it might be a different issue.`
52
+ )
53
+ }
54
+ }
55
+ })
56
+ })
57
+ .catch((err) => {
58
+ setStarCount(0)
59
+ console.warn(JSON.stringify(err, null, 2))
60
+ })
61
+ }, [hideGithubStars, url])
62
+
63
+ const isLoadingStarCount = starCount === -1
64
+ const isFailedStarCount = formatStarCount(starCount) === false
65
+
66
+ const showStarCount = !hideGithubStars && (isLoadingStarCount || !isFailedStarCount);
67
+
68
+ const icon: IconObject = {
69
+ position: 'right',
70
+ svg: GithubIcon,
71
+ }
72
+
73
+ return (
74
+ <LinkButton icon={!showStarCount ? icon : undefined} label={labels.github} text={!showStarCount ? labels.github : ""}
75
+ variant='neutral'
76
+ url={url} external
77
+ Link={Link}
78
+ >
79
+ {showStarCount ? <div className="inline-flex">
80
+ <GithubIcon className="w-5 mr-2" />
81
+ <div className='-my-2 mx-2 h-9 block bg-gray-300' style={{ width: "1px" }} />
82
+
83
+ <span className="inline-flex gap-1 items-center">
84
+ <StarIcon className='w-5 fill-yellow-500' />
85
+ <span className="g-type-body-small-strong" data-testid="github-stars">
86
+ {formatStarCount(starCount) || <span>&mdash;</span>}
87
+ </span>
88
+ </span>
89
+
90
+ </div> : null}
91
+ </LinkButton>
92
+
93
+ )
94
+ }
95
+
96
+ export default GithubStarsButton
@@ -1,25 +1,25 @@
1
- /*
2
- * Given:
3
- * urlString (string) valid URL string
4
- * Return:
5
- * { org, repo }, where the url is ~= www.github.com/{org}/{repo}
6
- * or false otherwise (eg if not a valid URL, or if not a GitHub url)
7
- */
8
- function parseGithubUrl(urlString: string) {
9
- try {
10
- const urlObj = new URL(urlString)
11
- if (urlObj.hostname !== 'www.github.com') return false
12
- const parts = urlObj.pathname.split('/').filter(Boolean)
13
- const org = parts[0]
14
- const repo = parts[1]
15
- return { org, repo }
16
- } catch (err) {
17
- console.warn(
18
- 'Warning! An invalid URL has probably been supplied to the GitHub ctaLink in <Subnav />. The corresponding error:'
19
- )
20
- console.warn(err)
21
- return false
22
- }
23
- }
24
-
25
- export default parseGithubUrl
1
+ /*
2
+ * Given:
3
+ * urlString (string) valid URL string
4
+ * Return:
5
+ * { org, repo }, where the url is ~= www.github.com/{org}/{repo}
6
+ * or false otherwise (eg if not a valid URL, or if not a GitHub url)
7
+ */
8
+ function parseGithubUrl(urlString: string) {
9
+ try {
10
+ const urlObj = new URL(urlString)
11
+ if (urlObj.hostname !== 'www.github.com') return false
12
+ const parts = urlObj.pathname.split('/').filter(Boolean)
13
+ const org = parts[0]
14
+ const repo = parts[1]
15
+ return { org, repo }
16
+ } catch (err) {
17
+ console.warn(
18
+ 'Warning! An invalid URL has probably been supplied to the GitHub ctaLink in <Subnav />. The corresponding error:'
19
+ )
20
+ console.warn(err)
21
+ return false
22
+ }
23
+ }
24
+
25
+ export default parseGithubUrl