@node-core/ui-components 0.0.1 → 1.0.1-df203d0fe21ee5d80d258c60dc821e8ac0601ce4
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/Common/AlertBox/index.module.css +76 -0
- package/Common/AlertBox/index.stories.tsx +73 -0
- package/Common/AlertBox/index.tsx +24 -0
- package/Common/AvatarGroup/Avatar/index.module.css +40 -0
- package/Common/AvatarGroup/Avatar/index.stories.tsx +22 -0
- package/Common/AvatarGroup/Avatar/index.tsx +67 -0
- package/Common/AvatarGroup/Overlay/index.module.css +31 -0
- package/Common/AvatarGroup/Overlay/index.stories.tsx +33 -0
- package/Common/AvatarGroup/Overlay/index.tsx +37 -0
- package/Common/AvatarGroup/__tests__/index.test.jsx +55 -0
- package/Common/AvatarGroup/index.module.css +25 -0
- package/Common/AvatarGroup/index.stories.tsx +56 -0
- package/Common/AvatarGroup/index.tsx +87 -0
- package/Common/Badge/index.module.css +38 -0
- package/Common/Badge/index.stories.tsx +38 -0
- package/Common/Badge/index.tsx +35 -0
- package/Common/BadgeGroup/index.module.css +77 -0
- package/Common/BadgeGroup/index.stories.tsx +35 -0
- package/Common/BadgeGroup/index.tsx +35 -0
- package/Common/Banner/index.module.css +42 -0
- package/Common/Banner/index.stories.tsx +29 -0
- package/Common/Banner/index.tsx +18 -0
- package/Common/BaseActiveLink/__tests__/index.test.jsx +52 -0
- package/Common/BaseActiveLink/index.tsx +34 -0
- package/Common/BaseButton/index.module.css +142 -0
- package/Common/BaseButton/index.stories.tsx +67 -0
- package/Common/BaseButton/index.tsx +59 -0
- package/Common/BaseCodeBox/index.module.css +84 -0
- package/Common/BaseCodeBox/index.stories.tsx +39 -0
- package/Common/BaseCodeBox/index.tsx +133 -0
- package/Common/BaseCrossLink/index.module.css +51 -0
- package/Common/BaseCrossLink/index.stories.tsx +38 -0
- package/Common/BaseCrossLink/index.tsx +46 -0
- package/Common/BaseLinkTabs/index.module.css +43 -0
- package/Common/BaseLinkTabs/index.stories.tsx +34 -0
- package/Common/BaseLinkTabs/index.tsx +53 -0
- package/Common/BasePagination/Ellipsis/index.module.css +10 -0
- package/Common/BasePagination/Ellipsis/index.stories.tsx +10 -0
- package/Common/BasePagination/Ellipsis/index.tsx +11 -0
- package/Common/BasePagination/PaginationListItem/__tests__/index.test.jsx +58 -0
- package/Common/BasePagination/PaginationListItem/index.module.css +27 -0
- package/Common/BasePagination/PaginationListItem/index.stories.tsx +40 -0
- package/Common/BasePagination/PaginationListItem/index.tsx +39 -0
- package/Common/BasePagination/PrevNextArrow.tsx +15 -0
- package/Common/BasePagination/__tests__/index.test.jsx +180 -0
- package/Common/BasePagination/index.module.css +39 -0
- package/Common/BasePagination/index.stories.tsx +67 -0
- package/Common/BasePagination/index.tsx +77 -0
- package/Common/BasePagination/useGetPageElements.tsx +132 -0
- package/Common/Blockquote/index.module.css +29 -0
- package/Common/Blockquote/index.stories.tsx +45 -0
- package/Common/Blockquote/index.tsx +11 -0
- package/Common/Breadcrumbs/BreadcrumbHomeLink/index.module.css +5 -0
- package/Common/Breadcrumbs/BreadcrumbHomeLink/index.tsx +30 -0
- package/Common/Breadcrumbs/BreadcrumbItem/index.module.css +41 -0
- package/Common/Breadcrumbs/BreadcrumbItem/index.tsx +42 -0
- package/Common/Breadcrumbs/BreadcrumbLink/index.module.css +22 -0
- package/Common/Breadcrumbs/BreadcrumbLink/index.tsx +37 -0
- package/Common/Breadcrumbs/BreadcrumbRoot/index.module.css +9 -0
- package/Common/Breadcrumbs/BreadcrumbRoot/index.tsx +20 -0
- package/Common/Breadcrumbs/BreadcrumbTruncatedItem/index.tsx +9 -0
- package/Common/Breadcrumbs/index.stories.tsx +94 -0
- package/Common/Breadcrumbs/index.tsx +81 -0
- package/Common/CodeTabs/index.module.css +56 -0
- package/Common/CodeTabs/index.stories.tsx +74 -0
- package/Common/CodeTabs/index.tsx +16 -0
- package/Common/GlowingBackdrop/index.module.css +32 -0
- package/Common/GlowingBackdrop/index.stories.tsx +10 -0
- package/Common/GlowingBackdrop/index.tsx +13 -0
- package/Common/LanguageDropDown/index.module.css +53 -0
- package/Common/LanguageDropDown/index.stories.tsx +19 -0
- package/Common/LanguageDropDown/index.tsx +56 -0
- package/Common/Modal/index.module.css +79 -0
- package/Common/Modal/index.stories.tsx +32 -0
- package/Common/Modal/index.tsx +48 -0
- package/Common/NodejsLogo/index.module.css +6 -0
- package/Common/NodejsLogo/index.stories.tsx +14 -0
- package/Common/NodejsLogo/index.tsx +26 -0
- package/Common/Notification/index.module.css +20 -0
- package/Common/Notification/index.stories.tsx +36 -0
- package/Common/Notification/index.tsx +34 -0
- package/Common/Preview/index.module.css +79 -0
- package/Common/Preview/index.stories.tsx +44 -0
- package/Common/Preview/index.tsx +25 -0
- package/Common/ProgressionSidebar/ProgressionSidebarGroup/index.module.css +47 -0
- package/Common/ProgressionSidebar/ProgressionSidebarGroup/index.tsx +35 -0
- package/Common/ProgressionSidebar/ProgressionSidebarIcon/index.tsx +16 -0
- package/Common/ProgressionSidebar/ProgressionSidebarItem/index.module.css +39 -0
- package/Common/ProgressionSidebar/ProgressionSidebarItem/index.tsx +32 -0
- package/Common/ProgressionSidebar/index.module.css +30 -0
- package/Common/ProgressionSidebar/index.stories.tsx +79 -0
- package/Common/ProgressionSidebar/index.tsx +59 -0
- package/Common/Select/__tests__/index.test.jsx +67 -0
- package/Common/Select/index.module.css +161 -0
- package/Common/Select/index.stories.tsx +111 -0
- package/Common/Select/index.tsx +187 -0
- package/Common/Separator/index.module.css +16 -0
- package/Common/Separator/index.stories.tsx +32 -0
- package/Common/Separator/index.tsx +27 -0
- package/Common/Skeleton/index.module.css +30 -0
- package/Common/Skeleton/index.tsx +39 -0
- package/Common/Tabs/__tests__/index.test.jsx +52 -0
- package/Common/Tabs/index.module.css +54 -0
- package/Common/Tabs/index.stories.tsx +50 -0
- package/Common/Tabs/index.tsx +54 -0
- package/Common/ThemeToggle/__tests__/index.test.jsx +35 -0
- package/Common/ThemeToggle/index.module.css +15 -0
- package/Common/ThemeToggle/index.stories.tsx +10 -0
- package/Common/ThemeToggle/index.tsx +15 -0
- package/Common/Tooltip/index.module.css +43 -0
- package/Common/Tooltip/index.stories.tsx +73 -0
- package/Common/Tooltip/index.tsx +48 -0
- package/Containers/Article/index.module.css +70 -0
- package/Containers/Article/index.stories.tsx +39 -0
- package/Containers/Article/index.tsx +9 -0
- package/Containers/Footer/index.module.css +46 -0
- package/Containers/Footer/index.stories.tsx +27 -0
- package/Containers/Footer/index.tsx +95 -0
- package/Containers/MetaBar/__tests__/index.test.jsx +63 -0
- package/Containers/MetaBar/index.module.css +91 -0
- package/Containers/MetaBar/index.stories.tsx +80 -0
- package/Containers/MetaBar/index.tsx +72 -0
- package/Containers/NavBar/NavItem/index.module.css +60 -0
- package/Containers/NavBar/NavItem/index.stories.tsx +38 -0
- package/Containers/NavBar/NavItem/index.tsx +44 -0
- package/Containers/NavBar/index.module.css +125 -0
- package/Containers/NavBar/index.stories.tsx +45 -0
- package/Containers/NavBar/index.tsx +94 -0
- package/Containers/Sidebar/SidebarGroup/index.module.css +26 -0
- package/Containers/Sidebar/SidebarGroup/index.stories.tsx +36 -0
- package/Containers/Sidebar/SidebarGroup/index.tsx +30 -0
- package/Containers/Sidebar/SidebarItem/index.module.css +35 -0
- package/Containers/Sidebar/SidebarItem/index.stories.tsx +15 -0
- package/Containers/Sidebar/SidebarItem/index.tsx +26 -0
- package/Containers/Sidebar/index.module.css +31 -0
- package/Containers/Sidebar/index.stories.tsx +84 -0
- package/Containers/Sidebar/index.tsx +58 -0
- package/Icons/HexagonGrid.stories.tsx +10 -0
- package/Icons/HexagonGrid.tsx +1434 -0
- package/Icons/InstallationMethod/Choco.tsx +78 -0
- package/Icons/InstallationMethod/Devbox.tsx +21 -0
- package/Icons/InstallationMethod/Docker.tsx +20 -0
- package/Icons/InstallationMethod/FNM.tsx +132 -0
- package/Icons/InstallationMethod/Homebrew.tsx +69 -0
- package/Icons/InstallationMethod/N.tsx +32 -0
- package/Icons/InstallationMethod/NVM.tsx +63 -0
- package/Icons/InstallationMethod/Volta.tsx +34 -0
- package/Icons/InstallationMethod/index.ts +10 -0
- package/Icons/Logos/JsGreen.tsx +24 -0
- package/Icons/Logos/JsWhite.tsx +37 -0
- package/Icons/Logos/Nodejs.tsx +188 -0
- package/Icons/Logos/NodejsStackedBlack.tsx +98 -0
- package/Icons/Logos/NodejsStackedDark.tsx +124 -0
- package/Icons/Logos/NodejsStackedLight.tsx +123 -0
- package/Icons/Logos/NodejsStackedWhite.tsx +98 -0
- package/Icons/Logos/index.ts +17 -0
- package/Icons/OperatingSystem/AIX.tsx +46 -0
- package/Icons/OperatingSystem/Apple.tsx +23 -0
- package/Icons/OperatingSystem/Linux.tsx +969 -0
- package/Icons/OperatingSystem/Microsoft.tsx +19 -0
- package/Icons/OperatingSystem/index.ts +6 -0
- package/Icons/PackageManager/Npm.tsx +21 -0
- package/Icons/PackageManager/Pnpm.tsx +22 -0
- package/Icons/PackageManager/Yarn.tsx +22 -0
- package/Icons/PackageManager/index.ts +5 -0
- package/Icons/Social/Bluesky.tsx +19 -0
- package/Icons/Social/Discord.tsx +20 -0
- package/Icons/Social/GitHub.tsx +16 -0
- package/Icons/Social/LinkedIn.tsx +16 -0
- package/Icons/Social/Mastodon.tsx +36 -0
- package/Icons/Social/Slack.tsx +31 -0
- package/Icons/Social/X.tsx +16 -0
- package/Icons/Social/index.ts +9 -0
- package/LICENSE +21 -0
- package/package.json +89 -5
- package/stylelint/__tests__/index.test.mjs +80 -0
- package/stylelint/one-utility-class-per-line.mjs +64 -0
- package/stylelint/utils.mjs +53 -0
- package/styles/animations.css +47 -0
- package/styles/base.css +17 -0
- package/styles/effects.css +12 -0
- package/styles/index.css +38 -0
- package/styles/markdown.css +173 -0
- package/styles/theme.css +175 -0
- package/types.ts +25 -0
- package/README.md +0 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
@reference "../../styles/index.css";
|
|
2
|
+
|
|
3
|
+
.root {
|
|
4
|
+
@apply w-full
|
|
5
|
+
rounded-sm
|
|
6
|
+
border
|
|
7
|
+
border-neutral-900
|
|
8
|
+
bg-neutral-950;
|
|
9
|
+
|
|
10
|
+
.content {
|
|
11
|
+
@apply m-0
|
|
12
|
+
p-4;
|
|
13
|
+
|
|
14
|
+
& > code {
|
|
15
|
+
@apply font-ibm-plex-mono
|
|
16
|
+
font-regular
|
|
17
|
+
scrollbar-thin
|
|
18
|
+
grid
|
|
19
|
+
overflow-x-auto
|
|
20
|
+
bg-transparent
|
|
21
|
+
p-0
|
|
22
|
+
text-sm
|
|
23
|
+
leading-snug
|
|
24
|
+
text-neutral-400
|
|
25
|
+
[counter-reset:line];
|
|
26
|
+
|
|
27
|
+
& > [class='line'] {
|
|
28
|
+
@apply relative
|
|
29
|
+
min-w-0
|
|
30
|
+
pl-8;
|
|
31
|
+
|
|
32
|
+
&:not(:empty:last-child)::before {
|
|
33
|
+
@apply inline-block
|
|
34
|
+
content-[''];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
&:not(:empty:last-child)::after {
|
|
38
|
+
@apply font-ibm-plex-mono
|
|
39
|
+
w-4.5
|
|
40
|
+
absolute
|
|
41
|
+
left-0
|
|
42
|
+
top-0
|
|
43
|
+
mr-4
|
|
44
|
+
text-right
|
|
45
|
+
text-neutral-600
|
|
46
|
+
[content:counter(line)]
|
|
47
|
+
[counter-increment:line];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
& > .footer {
|
|
54
|
+
@apply flex
|
|
55
|
+
items-center
|
|
56
|
+
justify-between
|
|
57
|
+
border-t
|
|
58
|
+
border-t-neutral-900
|
|
59
|
+
px-4
|
|
60
|
+
py-3
|
|
61
|
+
text-sm
|
|
62
|
+
font-medium;
|
|
63
|
+
|
|
64
|
+
& > .language {
|
|
65
|
+
@apply text-neutral-400;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
& > .action {
|
|
69
|
+
@apply px-3
|
|
70
|
+
py-1.5
|
|
71
|
+
font-medium;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.notification {
|
|
77
|
+
@apply flex
|
|
78
|
+
items-center
|
|
79
|
+
gap-3;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.icon {
|
|
83
|
+
@apply size-4;
|
|
84
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import BaseCodeBox from '#ui/Common/BaseCodeBox';
|
|
4
|
+
|
|
5
|
+
type Story = StoryObj<typeof BaseCodeBox>;
|
|
6
|
+
type Meta = MetaObj<typeof BaseCodeBox>;
|
|
7
|
+
|
|
8
|
+
const content = `const http = require('http');
|
|
9
|
+
|
|
10
|
+
const hostname = '127.0.0.1';
|
|
11
|
+
const port = 3000;
|
|
12
|
+
|
|
13
|
+
const server = http.createServer((req, res) => {
|
|
14
|
+
res.statusCode = 200;
|
|
15
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
16
|
+
res.end('Hello World');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
server.listen(port, hostname, () => {
|
|
20
|
+
console.log(\`Server running at http://\${hostname}:\${port}/\`);
|
|
21
|
+
});`;
|
|
22
|
+
|
|
23
|
+
const args = {
|
|
24
|
+
language: 'JavaScript (CJS)',
|
|
25
|
+
children: <code>{content}</code>,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const Default: Story = {
|
|
29
|
+
args,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const WithCopyButton: Story = {
|
|
33
|
+
args: {
|
|
34
|
+
...args,
|
|
35
|
+
showCopyButton: true,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default { component: BaseCodeBox } as Meta;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DocumentDuplicateIcon,
|
|
5
|
+
CodeBracketIcon,
|
|
6
|
+
} from '@heroicons/react/24/outline';
|
|
7
|
+
import classNames from 'classnames';
|
|
8
|
+
import type { FC, PropsWithChildren, ReactElement, ReactNode } from 'react';
|
|
9
|
+
import { Fragment, isValidElement, useRef } from 'react';
|
|
10
|
+
|
|
11
|
+
import BaseButton from '#ui/Common/BaseButton';
|
|
12
|
+
import type { LinkLike } from '#ui/types';
|
|
13
|
+
|
|
14
|
+
import styles from './index.module.css';
|
|
15
|
+
|
|
16
|
+
// Transforms a code element with plain text content into a more structured
|
|
17
|
+
// format for rendering with line numbers
|
|
18
|
+
const transformCode = <T extends ReactElement<PropsWithChildren>>(
|
|
19
|
+
code: T,
|
|
20
|
+
language: string
|
|
21
|
+
): ReactElement<HTMLElement> | T => {
|
|
22
|
+
if (!isValidElement(code)) {
|
|
23
|
+
// Early return when the `CodeBox` child is not a valid element since the
|
|
24
|
+
// type is a ReactNode, and can assume any value
|
|
25
|
+
return code;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const content = code.props?.children;
|
|
29
|
+
|
|
30
|
+
if (code.type !== 'code' || typeof content !== 'string') {
|
|
31
|
+
// There is no need to transform an element that is not a code element or
|
|
32
|
+
// a content that is not a string
|
|
33
|
+
return code;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Note that since we use `.split` we will have an extra entry
|
|
37
|
+
// being an empty string, so we need to remove it
|
|
38
|
+
const lines = content.split('\n');
|
|
39
|
+
|
|
40
|
+
const extraStyle = language.length === 0 ? { fontFamily: 'monospace' } : {};
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<code style={extraStyle}>
|
|
44
|
+
{lines
|
|
45
|
+
.flatMap((line, lineIndex) => {
|
|
46
|
+
const columns = line.split(' ');
|
|
47
|
+
|
|
48
|
+
return [
|
|
49
|
+
<span key={lineIndex} className="line">
|
|
50
|
+
{columns.map((column, columnIndex) => (
|
|
51
|
+
<Fragment key={columnIndex}>
|
|
52
|
+
<span>{column}</span>
|
|
53
|
+
{columnIndex < columns.length - 1 && <span> </span>}
|
|
54
|
+
</Fragment>
|
|
55
|
+
))}
|
|
56
|
+
</span>,
|
|
57
|
+
// Add a break line so the text content is formatted correctly
|
|
58
|
+
// when copying to clipboard
|
|
59
|
+
'\n',
|
|
60
|
+
];
|
|
61
|
+
})
|
|
62
|
+
// Here we remove that empty line from before and
|
|
63
|
+
// the last flatMap entry which is an `\n`
|
|
64
|
+
.slice(0, -2)}
|
|
65
|
+
</code>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
interface CodeBoxProps {
|
|
70
|
+
language: string;
|
|
71
|
+
className?: string;
|
|
72
|
+
onCopy: (text: string, message: ReactNode) => void;
|
|
73
|
+
as?: LinkLike;
|
|
74
|
+
copyText: string;
|
|
75
|
+
copiedText: string;
|
|
76
|
+
showCopyButton?: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const BaseCodeBox: FC<PropsWithChildren<CodeBoxProps>> = ({
|
|
80
|
+
children,
|
|
81
|
+
language,
|
|
82
|
+
className,
|
|
83
|
+
onCopy,
|
|
84
|
+
copiedText,
|
|
85
|
+
copyText,
|
|
86
|
+
as = 'a',
|
|
87
|
+
showCopyButton = true,
|
|
88
|
+
}: PropsWithChildren<CodeBoxProps>) => {
|
|
89
|
+
const containerRef = useRef<HTMLPreElement>(null);
|
|
90
|
+
|
|
91
|
+
const handleCopy = (): void => {
|
|
92
|
+
const text = containerRef.current?.textContent;
|
|
93
|
+
if (text) {
|
|
94
|
+
onCopy(
|
|
95
|
+
text,
|
|
96
|
+
<div className={styles.notification}>
|
|
97
|
+
<CodeBracketIcon className={styles.icon} />
|
|
98
|
+
{copiedText}
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div className={styles.root}>
|
|
106
|
+
<pre
|
|
107
|
+
ref={containerRef}
|
|
108
|
+
className={classNames(styles.content, className)}
|
|
109
|
+
tabIndex={0}
|
|
110
|
+
>
|
|
111
|
+
{transformCode(children as ReactElement<PropsWithChildren>, language)}
|
|
112
|
+
</pre>
|
|
113
|
+
{language && (
|
|
114
|
+
<div className={styles.footer}>
|
|
115
|
+
<span className={styles.language}>{language}</span>
|
|
116
|
+
{showCopyButton && (
|
|
117
|
+
<BaseButton
|
|
118
|
+
as={as}
|
|
119
|
+
className={styles.action}
|
|
120
|
+
kind="neutral"
|
|
121
|
+
onClick={handleCopy}
|
|
122
|
+
>
|
|
123
|
+
<DocumentDuplicateIcon className={styles.icon} />
|
|
124
|
+
{copyText}
|
|
125
|
+
</BaseButton>
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export default BaseCodeBox;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
@reference "../../styles/index.css";
|
|
2
|
+
|
|
3
|
+
.crossLink {
|
|
4
|
+
@apply flex
|
|
5
|
+
flex-col
|
|
6
|
+
items-start
|
|
7
|
+
gap-2
|
|
8
|
+
truncate
|
|
9
|
+
rounded-sm
|
|
10
|
+
border
|
|
11
|
+
border-solid
|
|
12
|
+
border-neutral-300
|
|
13
|
+
bg-white
|
|
14
|
+
p-3
|
|
15
|
+
no-underline
|
|
16
|
+
dark:border-neutral-900
|
|
17
|
+
dark:bg-neutral-950;
|
|
18
|
+
|
|
19
|
+
.header {
|
|
20
|
+
@apply flex
|
|
21
|
+
items-center
|
|
22
|
+
gap-2
|
|
23
|
+
self-stretch
|
|
24
|
+
text-xs
|
|
25
|
+
text-neutral-800
|
|
26
|
+
dark:text-neutral-100;
|
|
27
|
+
|
|
28
|
+
&.reverse {
|
|
29
|
+
@apply flex-row-reverse
|
|
30
|
+
justify-start;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.icon {
|
|
34
|
+
@apply size-4
|
|
35
|
+
text-neutral-600
|
|
36
|
+
dark:text-neutral-400;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.content {
|
|
41
|
+
@apply self-stretch
|
|
42
|
+
truncate
|
|
43
|
+
text-sm
|
|
44
|
+
text-neutral-900
|
|
45
|
+
dark:text-white;
|
|
46
|
+
|
|
47
|
+
&.reverse {
|
|
48
|
+
@apply text-right;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import BaseCrossLink from '#ui/Common/BaseCrossLink';
|
|
4
|
+
|
|
5
|
+
type Story = StoryObj<typeof BaseCrossLink>;
|
|
6
|
+
type Meta = MetaObj<typeof BaseCrossLink>;
|
|
7
|
+
|
|
8
|
+
export const Prev: Story = {
|
|
9
|
+
args: {
|
|
10
|
+
type: 'previous',
|
|
11
|
+
text: 'How to install Node.js',
|
|
12
|
+
link: 'https://nodejs.dev/en/learn/how-to-install-nodejs/',
|
|
13
|
+
},
|
|
14
|
+
decorators: [
|
|
15
|
+
Story => (
|
|
16
|
+
<div className="w-[305px]">
|
|
17
|
+
<Story />
|
|
18
|
+
</div>
|
|
19
|
+
),
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const Next: Story = {
|
|
24
|
+
args: {
|
|
25
|
+
type: 'next',
|
|
26
|
+
text: 'How much JavaScript do you need to know to use Node.js?',
|
|
27
|
+
link: 'https://nodejs.dev/en/learn/how-much-javascript-do-you-need-to-know-to-use-nodejs/',
|
|
28
|
+
},
|
|
29
|
+
decorators: [
|
|
30
|
+
Story => (
|
|
31
|
+
<div className="w-[305px]">
|
|
32
|
+
<Story />
|
|
33
|
+
</div>
|
|
34
|
+
),
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default { component: BaseCrossLink } as Meta;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import type { FC } from 'react';
|
|
3
|
+
|
|
4
|
+
import PrevNextArrow from '#ui/Common/BasePagination/PrevNextArrow';
|
|
5
|
+
import type { LinkLike, FormattedMessage } from '#ui/types';
|
|
6
|
+
|
|
7
|
+
import styles from './index.module.css';
|
|
8
|
+
|
|
9
|
+
export type CrossLinkProps = {
|
|
10
|
+
type: 'previous' | 'next';
|
|
11
|
+
text: FormattedMessage;
|
|
12
|
+
label: string;
|
|
13
|
+
link: string;
|
|
14
|
+
as: LinkLike;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const BaseCrossLink: FC<CrossLinkProps> = ({
|
|
18
|
+
type,
|
|
19
|
+
label,
|
|
20
|
+
text,
|
|
21
|
+
link,
|
|
22
|
+
as: Component = 'a',
|
|
23
|
+
}) => {
|
|
24
|
+
return (
|
|
25
|
+
<Component className={styles.crossLink} href={link}>
|
|
26
|
+
<span
|
|
27
|
+
className={classNames(styles.header, {
|
|
28
|
+
[styles.reverse]: type === 'next',
|
|
29
|
+
})}
|
|
30
|
+
>
|
|
31
|
+
<PrevNextArrow className={styles.icon} type={type} />
|
|
32
|
+
{label}
|
|
33
|
+
</span>
|
|
34
|
+
|
|
35
|
+
<span
|
|
36
|
+
className={classNames(styles.content, {
|
|
37
|
+
[styles.reverse]: type === 'next',
|
|
38
|
+
})}
|
|
39
|
+
>
|
|
40
|
+
{text}
|
|
41
|
+
</span>
|
|
42
|
+
</Component>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default BaseCrossLink;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
@reference "../../styles/index.css";
|
|
2
|
+
|
|
3
|
+
.tabsList {
|
|
4
|
+
@apply font-open-sans
|
|
5
|
+
max-xs:hidden
|
|
6
|
+
mb-6
|
|
7
|
+
mt-10
|
|
8
|
+
flex
|
|
9
|
+
gap-2
|
|
10
|
+
border-b
|
|
11
|
+
border-b-neutral-200
|
|
12
|
+
dark:border-b-neutral-800;
|
|
13
|
+
|
|
14
|
+
.tabsTrigger {
|
|
15
|
+
@apply border-b-2
|
|
16
|
+
border-b-transparent
|
|
17
|
+
px-1
|
|
18
|
+
pb-[11px]
|
|
19
|
+
text-sm
|
|
20
|
+
font-semibold
|
|
21
|
+
text-neutral-800
|
|
22
|
+
dark:text-neutral-200;
|
|
23
|
+
|
|
24
|
+
&[data-state='active'] {
|
|
25
|
+
@apply border-b-green-600
|
|
26
|
+
text-green-600
|
|
27
|
+
dark:border-b-green-400
|
|
28
|
+
dark:text-green-400;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.tabsSelect {
|
|
34
|
+
@apply sm:visible
|
|
35
|
+
md:hidden;
|
|
36
|
+
|
|
37
|
+
> span {
|
|
38
|
+
@apply max-xs:flex
|
|
39
|
+
my-6
|
|
40
|
+
hidden
|
|
41
|
+
w-full;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import BaseLinkTabs from '#ui/Common/BaseLinkTabs';
|
|
4
|
+
|
|
5
|
+
type Story = StoryObj<typeof BaseLinkTabs>;
|
|
6
|
+
type Meta = MetaObj<typeof BaseLinkTabs>;
|
|
7
|
+
|
|
8
|
+
export const Default: Story = {
|
|
9
|
+
args: {
|
|
10
|
+
label: 'Select Tab',
|
|
11
|
+
tabs: [
|
|
12
|
+
{
|
|
13
|
+
key: 'package',
|
|
14
|
+
label: 'Package Manager',
|
|
15
|
+
link: '/package-manager',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
key: 'prebuilt',
|
|
19
|
+
label: 'Prebuilt Installer',
|
|
20
|
+
link: '/prebuilt-installer',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
key: 'source',
|
|
24
|
+
label: 'Source Code',
|
|
25
|
+
link: '/source-code',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
activeTab: 'prebuilt',
|
|
29
|
+
children: <p>Tab content goes here</p>,
|
|
30
|
+
onSelect: console.log,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default { component: BaseLinkTabs } as Meta;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { FC, PropsWithChildren } from 'react';
|
|
2
|
+
|
|
3
|
+
import Select from '#ui/Common/Select';
|
|
4
|
+
import type { LinkLike } from '#ui/types';
|
|
5
|
+
|
|
6
|
+
import styles from './index.module.css';
|
|
7
|
+
|
|
8
|
+
type LinkTab = { key: string; label: string; link: string };
|
|
9
|
+
|
|
10
|
+
export type LinkTabsProps = PropsWithChildren<{
|
|
11
|
+
label?: string;
|
|
12
|
+
tabs: Array<LinkTab>;
|
|
13
|
+
activeTab: string;
|
|
14
|
+
as?: LinkLike;
|
|
15
|
+
onSelect: (value: string) => void;
|
|
16
|
+
}>;
|
|
17
|
+
|
|
18
|
+
const BaseLinkTabs: FC<LinkTabsProps> = ({
|
|
19
|
+
tabs,
|
|
20
|
+
label,
|
|
21
|
+
activeTab,
|
|
22
|
+
children,
|
|
23
|
+
as: Component = 'a',
|
|
24
|
+
onSelect,
|
|
25
|
+
}) => (
|
|
26
|
+
<>
|
|
27
|
+
<div className={styles.tabsList}>
|
|
28
|
+
{tabs.map(tab => (
|
|
29
|
+
<Component
|
|
30
|
+
key={tab.key}
|
|
31
|
+
href={tab.link}
|
|
32
|
+
className={styles.tabsTrigger}
|
|
33
|
+
data-state={tab.key === activeTab ? 'active' : 'inactive'}
|
|
34
|
+
>
|
|
35
|
+
{tab.label}
|
|
36
|
+
</Component>
|
|
37
|
+
))}
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div className={styles.tabsSelect}>
|
|
41
|
+
<Select
|
|
42
|
+
label={label}
|
|
43
|
+
defaultValue={tabs.find(tab => tab.key === activeTab)?.link}
|
|
44
|
+
values={tabs.map(tab => ({ label: tab.label, value: tab.link }))}
|
|
45
|
+
onChange={onSelect}
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
{children}
|
|
50
|
+
</>
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
export default BaseLinkTabs;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import Ellipsis from '#ui/Common/BasePagination/Ellipsis';
|
|
4
|
+
|
|
5
|
+
type Story = StoryObj<typeof Ellipsis>;
|
|
6
|
+
type Meta = MetaObj<typeof Ellipsis>;
|
|
7
|
+
|
|
8
|
+
export const Default: Story = {};
|
|
9
|
+
|
|
10
|
+
export default { component: Ellipsis } as Meta;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
|
|
4
|
+
import { render, screen } from '@testing-library/react';
|
|
5
|
+
|
|
6
|
+
import { isVisible } from '../../../../../../tests/utilities.mjs';
|
|
7
|
+
|
|
8
|
+
import PaginationListItem from '#ui/Common/BasePagination/PaginationListItem';
|
|
9
|
+
|
|
10
|
+
function renderPaginationListItem({
|
|
11
|
+
url,
|
|
12
|
+
pageNumber,
|
|
13
|
+
currentPage,
|
|
14
|
+
totalPages,
|
|
15
|
+
}) {
|
|
16
|
+
render(
|
|
17
|
+
<PaginationListItem
|
|
18
|
+
url={url}
|
|
19
|
+
pageNumber={pageNumber}
|
|
20
|
+
currentPage={currentPage}
|
|
21
|
+
totalPages={totalPages}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('PaginationListItem', () => {
|
|
27
|
+
it('Renders the list item correctly, including the corresponding ARIA attributes', () => {
|
|
28
|
+
const pageNumber = 1;
|
|
29
|
+
const totalPages = 10;
|
|
30
|
+
const url = 'http://';
|
|
31
|
+
|
|
32
|
+
renderPaginationListItem({
|
|
33
|
+
url,
|
|
34
|
+
currentPage: 1,
|
|
35
|
+
pageNumber,
|
|
36
|
+
totalPages,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const listItem = screen.getByRole('listitem');
|
|
40
|
+
|
|
41
|
+
assert.ok(isVisible());
|
|
42
|
+
assert.equal(listItem.getAttribute('aria-posinset'), String(pageNumber));
|
|
43
|
+
assert.equal(listItem.getAttribute('aria-setsize'), String(totalPages));
|
|
44
|
+
|
|
45
|
+
assert.equal(screen.getByRole('link').getAttribute('href'), url);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('Assigns aria-current="page" attribute to the link when the current page is equal to the page number', () => {
|
|
49
|
+
renderPaginationListItem({
|
|
50
|
+
url: 'http://',
|
|
51
|
+
currentPage: 1,
|
|
52
|
+
pageNumber: 1,
|
|
53
|
+
totalPages: 10,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
assert.equal(screen.getByRole('link').getAttribute('aria-current'), 'page');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
@reference "../../../styles/index.css";
|
|
2
|
+
|
|
3
|
+
.listItem,
|
|
4
|
+
.listItem:link,
|
|
5
|
+
.listItem:active {
|
|
6
|
+
@apply aria-current:bg-green-600
|
|
7
|
+
aria-current:text-white
|
|
8
|
+
aria-current:cursor-default
|
|
9
|
+
flex
|
|
10
|
+
size-10
|
|
11
|
+
cursor-pointer
|
|
12
|
+
items-center
|
|
13
|
+
justify-center
|
|
14
|
+
rounded-sm
|
|
15
|
+
px-3
|
|
16
|
+
py-2.5
|
|
17
|
+
text-neutral-800
|
|
18
|
+
motion-safe:transition-colors
|
|
19
|
+
dark:text-neutral-200;
|
|
20
|
+
|
|
21
|
+
&:hover:not([aria-current='page']) {
|
|
22
|
+
@apply bg-neutral-100
|
|
23
|
+
text-neutral-800
|
|
24
|
+
dark:bg-neutral-900
|
|
25
|
+
dark:text-neutral-200;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import PaginationListItem from '#ui/Common/BasePagination/PaginationListItem';
|
|
4
|
+
|
|
5
|
+
type Story = StoryObj<typeof PaginationListItem>;
|
|
6
|
+
type Meta = MetaObj<typeof PaginationListItem>;
|
|
7
|
+
|
|
8
|
+
export const Default: Story = {
|
|
9
|
+
args: {
|
|
10
|
+
url: '#',
|
|
11
|
+
pageNumber: 1,
|
|
12
|
+
currentPage: 2,
|
|
13
|
+
totalPages: 2,
|
|
14
|
+
},
|
|
15
|
+
decorators: [
|
|
16
|
+
Story => (
|
|
17
|
+
<ul className="list-none">
|
|
18
|
+
<Story />
|
|
19
|
+
</ul>
|
|
20
|
+
),
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const CurrentPage: Story = {
|
|
25
|
+
args: {
|
|
26
|
+
url: '#',
|
|
27
|
+
pageNumber: 1,
|
|
28
|
+
currentPage: 1,
|
|
29
|
+
totalPages: 1,
|
|
30
|
+
},
|
|
31
|
+
decorators: [
|
|
32
|
+
Story => (
|
|
33
|
+
<ul className="list-none">
|
|
34
|
+
<Story />
|
|
35
|
+
</ul>
|
|
36
|
+
),
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default { component: PaginationListItem } as Meta;
|