@fpkit/acss 0.4.4
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/LICENSE +21 -0
- package/README.md +52 -0
- package/dist/chunk-77CZU5XZ.cjs +9 -0
- package/dist/chunk-77CZU5XZ.cjs.map +1 -0
- package/dist/chunk-D43FJIRQ.cjs +31 -0
- package/dist/chunk-D43FJIRQ.cjs.map +1 -0
- package/dist/chunk-GJWMCDFS.js +9 -0
- package/dist/chunk-GJWMCDFS.js.map +1 -0
- package/dist/chunk-PCDUGD3C.js +5 -0
- package/dist/chunk-PCDUGD3C.js.map +1 -0
- package/dist/hooks.cjs +10 -0
- package/dist/hooks.cjs.map +1 -0
- package/dist/hooks.d.cts +32 -0
- package/dist/hooks.d.ts +32 -0
- package/dist/hooks.js +8 -0
- package/dist/hooks.js.map +1 -0
- package/dist/icon-e6044c73.d.ts +227 -0
- package/dist/icons.cjs +73 -0
- package/dist/icons.cjs.map +1 -0
- package/dist/icons.d.cts +252 -0
- package/dist/icons.d.ts +252 -0
- package/dist/icons.js +4 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.cjs +59 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +566 -0
- package/dist/index.d.ts +566 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/libs/chunk-GCGKYLDG.js +7 -0
- package/libs/chunk-GCGKYLDG.js.map +1 -0
- package/libs/chunk-PDD4N5P5.cjs +10 -0
- package/libs/chunk-PDD4N5P5.cjs.map +1 -0
- package/libs/chunk-QHIABQNQ.js +8 -0
- package/libs/chunk-QHIABQNQ.js.map +1 -0
- package/libs/chunk-ZOHIKF6I.cjs +31 -0
- package/libs/chunk-ZOHIKF6I.cjs.map +1 -0
- package/libs/components/badge/badge.css +1 -0
- package/libs/components/badge/badge.css.map +1 -0
- package/libs/components/badge/badge.min.css +3 -0
- package/libs/components/breadcrumbs/breadcrumb.css +1 -0
- package/libs/components/breadcrumbs/breadcrumb.css.map +1 -0
- package/libs/components/breadcrumbs/breadcrumb.min.css +3 -0
- package/libs/components/buttons/button.css +1 -0
- package/libs/components/buttons/button.css.map +1 -0
- package/libs/components/buttons/button.min.css +3 -0
- package/libs/components/cards/card-style.css +1 -0
- package/libs/components/cards/card-style.css.map +1 -0
- package/libs/components/cards/card-style.min.css +3 -0
- package/libs/components/cards/card.css +1 -0
- package/libs/components/cards/card.css.map +1 -0
- package/libs/components/cards/card.min.css +3 -0
- package/libs/components/details/details.css +1 -0
- package/libs/components/details/details.css.map +1 -0
- package/libs/components/details/details.min.css +3 -0
- package/libs/components/form/form.css +1 -0
- package/libs/components/form/form.css.map +1 -0
- package/libs/components/form/form.min.css +3 -0
- package/libs/components/icons/icon.css +1 -0
- package/libs/components/icons/icon.css.map +1 -0
- package/libs/components/icons/icon.min.css +3 -0
- package/libs/components/images/img.css +1 -0
- package/libs/components/images/img.css.map +1 -0
- package/libs/components/images/img.min.css +3 -0
- package/libs/components/layout/landmarks.css +1 -0
- package/libs/components/layout/landmarks.css.map +1 -0
- package/libs/components/layout/landmarks.min.css +3 -0
- package/libs/components/link/link.css +1 -0
- package/libs/components/link/link.css.map +1 -0
- package/libs/components/link/link.min.css +3 -0
- package/libs/components/nav/nav.css +1 -0
- package/libs/components/nav/nav.css.map +1 -0
- package/libs/components/nav/nav.min.css +3 -0
- package/libs/components/progress/progress.css +1 -0
- package/libs/components/progress/progress.css.map +1 -0
- package/libs/components/progress/progress.min.css +3 -0
- package/libs/components/styles/index.css +1 -0
- package/libs/components/styles/index.css.map +1 -0
- package/libs/components/styles/index.min.css +3 -0
- package/libs/components/tag/tag.css +1 -0
- package/libs/components/tag/tag.css.map +1 -0
- package/libs/components/tag/tag.min.css +3 -0
- package/libs/components/text-to-speech/text-to-speech.css +1 -0
- package/libs/components/text-to-speech/text-to-speech.css.map +1 -0
- package/libs/components/text-to-speech/text-to-speech.min.css +3 -0
- package/libs/hooks.cjs +12 -0
- package/libs/hooks.cjs.map +1 -0
- package/libs/hooks.d.cts +32 -0
- package/libs/hooks.d.ts +32 -0
- package/libs/hooks.js +3 -0
- package/libs/hooks.js.map +1 -0
- package/libs/icons-1f5afc0c.d.ts +318 -0
- package/libs/icons.cjs +12 -0
- package/libs/icons.cjs.map +1 -0
- package/libs/icons.d.cts +2 -0
- package/libs/icons.d.ts +2 -0
- package/libs/icons.js +3 -0
- package/libs/icons.js.map +1 -0
- package/libs/index.cjs +71 -0
- package/libs/index.cjs.map +1 -0
- package/libs/index.css +1 -0
- package/libs/index.css.map +1 -0
- package/libs/index.d.cts +551 -0
- package/libs/index.d.ts +551 -0
- package/libs/index.js +11 -0
- package/libs/index.js.map +1 -0
- package/package.json +125 -0
- package/src/App.css +42 -0
- package/src/App.tsx +35 -0
- package/src/__snapshots__/App.test.tsx.snap +56 -0
- package/src/components/.gitkeep +0 -0
- package/src/components/__snapshots__/fp.test.tsx.snap +3 -0
- package/src/components/badge/badge.scss +20 -0
- package/src/components/badge/badge.stories.tsx +54 -0
- package/src/components/badge/badge.tsx +17 -0
- package/src/components/breadcrumbs/bc-item.tsx +20 -0
- package/src/components/breadcrumbs/breadcrumb.scss +35 -0
- package/src/components/breadcrumbs/breadcrumb.stories.tsx +92 -0
- package/src/components/breadcrumbs/breadcrumb.tsx +218 -0
- package/src/components/buttons/button.scss +115 -0
- package/src/components/buttons/button.stories.tsx +57 -0
- package/src/components/buttons/button.test.tsx +104 -0
- package/src/components/buttons/button.tsx +64 -0
- package/src/components/cards/card-style.scss +0 -0
- package/src/components/cards/card.scss +43 -0
- package/src/components/cards/card.stories.tsx +114 -0
- package/src/components/cards/card.test.tsx +30 -0
- package/src/components/cards/card.tsx +135 -0
- package/src/components/cards/flex-card.tsx +15 -0
- package/src/components/details/details.scss +75 -0
- package/src/components/details/details.stories.tsx +122 -0
- package/src/components/details/details.tsx +77 -0
- package/src/components/form/README.mdx +70 -0
- package/src/components/form/fields.tsx +45 -0
- package/src/components/form/form.scss +87 -0
- package/src/components/form/form.stories.tsx +49 -0
- package/src/components/form/form.tsx +71 -0
- package/src/components/form/input.stories.tsx +155 -0
- package/src/components/form/inputs.tsx +84 -0
- package/src/components/form/select.stories.tsx +38 -0
- package/src/components/form/select.tsx +112 -0
- package/src/components/form/textarea.tsx +87 -0
- package/src/components/fp.test.tsx +56 -0
- package/src/components/fp.tsx +78 -0
- package/src/components/heading/heading.stories.tsx +75 -0
- package/src/components/heading/heading.tsx +27 -0
- package/src/components/icons/components/add.tsx +42 -0
- package/src/components/icons/components/arrow-down.tsx +52 -0
- package/src/components/icons/components/arrow-left.tsx +49 -0
- package/src/components/icons/components/arrow-right.tsx +52 -0
- package/src/components/icons/components/arrow-up.tsx +49 -0
- package/src/components/icons/components/chat.tsx +44 -0
- package/src/components/icons/components/code.tsx +50 -0
- package/src/components/icons/components/copy.tsx +51 -0
- package/src/components/icons/components/down.tsx +33 -0
- package/src/components/icons/components/home.tsx +57 -0
- package/src/components/icons/components/left.tsx +43 -0
- package/src/components/icons/components/minus.tsx +42 -0
- package/src/components/icons/components/pause-solid.tsx +48 -0
- package/src/components/icons/components/pause.tsx +63 -0
- package/src/components/icons/components/play-solid.tsx +44 -0
- package/src/components/icons/components/play.tsx +51 -0
- package/src/components/icons/components/remove.tsx +42 -0
- package/src/components/icons/components/resume-solid.tsx +52 -0
- package/src/components/icons/components/resume.tsx +57 -0
- package/src/components/icons/components/right.tsx +43 -0
- package/src/components/icons/components/star.tsx +38 -0
- package/src/components/icons/components/stop-solid.tsx +44 -0
- package/src/components/icons/components/stop.tsx +54 -0
- package/src/components/icons/components/svg.tsx +44 -0
- package/src/components/icons/components/up.tsx +31 -0
- package/src/components/icons/components/user.tsx +46 -0
- package/src/components/icons/icon.scss +15 -0
- package/src/components/icons/icon.stories.tsx +208 -0
- package/src/components/icons/icon.tsx +100 -0
- package/src/components/icons/index.ts +29 -0
- package/src/components/icons/types.ts +12 -0
- package/src/components/images/README.mdx +43 -0
- package/src/components/images/figure.stories.tsx +34 -0
- package/src/components/images/figure.tsx +44 -0
- package/src/components/images/img.scss +43 -0
- package/src/components/images/img.stories.tsx +24 -0
- package/src/components/images/img.test.tsx +43 -0
- package/src/components/images/img.tsx +93 -0
- package/src/components/images/place-holder.png +0 -0
- package/src/components/kit.tsx +56 -0
- package/src/components/layout/_header.scss +72 -0
- package/src/components/layout/footer.stories.tsx +34 -0
- package/src/components/layout/landmarks.scss +51 -0
- package/src/components/layout/landmarks.stories.tsx +54 -0
- package/src/components/layout/landmarks.tsx +149 -0
- package/src/components/layout/main.stories.tsx +90 -0
- package/src/components/link/link.scss +92 -0
- package/src/components/link/link.stories.tsx +74 -0
- package/src/components/link/link.tsx +48 -0
- package/src/components/list/list.stories.tsx +52 -0
- package/src/components/list/list.tsx +74 -0
- package/src/components/modal/dialog.tsx +50 -0
- package/src/components/modal/modal.tsx +85 -0
- package/src/components/nav/nav.scss +90 -0
- package/src/components/nav/nav.stories.tsx +96 -0
- package/src/components/nav/nav.tsx +76 -0
- package/src/components/popover/node_modules/.vitest/results.json +1 -0
- package/src/components/popover/popover.stories.tsx +31 -0
- package/src/components/popover/popover.test.tsx +39 -0
- package/src/components/popover/popover.tsx +85 -0
- package/src/components/progress/progress.scss +70 -0
- package/src/components/progress/progress.stories.tsx +51 -0
- package/src/components/progress/progress.tsx +82 -0
- package/src/components/readme.stories.mdx +7 -0
- package/src/components/styles/index.css +520 -0
- package/src/components/styles/index.css.map +1 -0
- package/src/components/tables/table-elements.tsx +57 -0
- package/src/components/tables/table.tsx +57 -0
- package/src/components/tag/tag.scss +56 -0
- package/src/components/tag/tag.stories.tsx +39 -0
- package/src/components/tag/tag.tsx +25 -0
- package/src/components/text/text.stories.tsx +67 -0
- package/src/components/text/text.tsx +93 -0
- package/src/components/text-to-speech/README.mdx +192 -0
- package/src/components/text-to-speech/TextInput.tsx +19 -0
- package/src/components/text-to-speech/TextToSpeech.stories.tsx +145 -0
- package/src/components/text-to-speech/TextToSpeech.tsx +94 -0
- package/src/components/text-to-speech/text-to-speech.scss +31 -0
- package/src/components/text-to-speech/useTextToSpeech.mdx +182 -0
- package/src/components/text-to-speech/useTextToSpeech.tsx +176 -0
- package/src/components/text-to-speech/views/TextToSpeechControls.tsx +117 -0
- package/src/components/ui.tsx +67 -0
- package/src/favicon.svg +15 -0
- package/src/hooks/popover/__snapshots__/popover.test.tsx.snap +88 -0
- package/src/hooks/popover/node_modules/.vitest/results.json +1 -0
- package/src/hooks/popover/popover.tsx +71 -0
- package/src/hooks/popover/use-popover.tsx +83 -0
- package/src/hooks.ts +1 -0
- package/src/icons.ts +1 -0
- package/src/index.css +13 -0
- package/src/index.scss +19 -0
- package/src/index.ts +35 -0
- package/src/libs/content.ts +30 -0
- package/src/logo.svg +7 -0
- package/src/main.tsx +10 -0
- package/src/patterns/.gitkeep +0 -0
- package/src/patterns/page/page-header.stories.tsx +44 -0
- package/src/patterns/page/page-header.tsx +78 -0
- package/src/sass/_elements.scss +17 -0
- package/src/sass/_globals.scss +162 -0
- package/src/sass/_layout.scss +51 -0
- package/src/sass/_loading-animation.scss +35 -0
- package/src/sass/_mixins.scss +10 -0
- package/src/sass/_properties.scss +106 -0
- package/src/sass/_reset.scss +183 -0
- package/src/sass/_type.scss +43 -0
- package/src/setupTest.ts +1 -0
- package/src/styles/badge/badge.css +22 -0
- package/src/styles/badge/badge.css.map +1 -0
- package/src/styles/breadcrumbs/breadcrumb.css +42 -0
- package/src/styles/breadcrumbs/breadcrumb.css.map +1 -0
- package/src/styles/buttons/button.css +93 -0
- package/src/styles/buttons/button.css.map +1 -0
- package/src/styles/cards/card-style.css +3 -0
- package/src/styles/cards/card-style.css.map +1 -0
- package/src/styles/cards/card.css +48 -0
- package/src/styles/cards/card.css.map +1 -0
- package/src/styles/details/details.css +69 -0
- package/src/styles/details/details.css.map +1 -0
- package/src/styles/dropdowns/dropdown.css.map +1 -0
- package/src/styles/form/form.css +93 -0
- package/src/styles/form/form.css.map +1 -0
- package/src/styles/form/style.css.map +1 -0
- package/src/styles/icons/icon.css +16 -0
- package/src/styles/icons/icon.css.map +1 -0
- package/src/styles/images/img.css +42 -0
- package/src/styles/images/img.css.map +1 -0
- package/src/styles/index.css +1330 -0
- package/src/styles/index.css.map +1 -0
- package/src/styles/layout/landmarks.css +155 -0
- package/src/styles/layout/landmarks.css.map +1 -0
- package/src/styles/link/link.css +88 -0
- package/src/styles/link/link.css.map +1 -0
- package/src/styles/nav/nav.css +85 -0
- package/src/styles/nav/nav.css.map +1 -0
- package/src/styles/progress/progress.css +54 -0
- package/src/styles/progress/progress.css.map +1 -0
- package/src/styles/progress/sass/progress.css.map +1 -0
- package/src/styles/styles/index.css +562 -0
- package/src/styles/styles/index.css.map +1 -0
- package/src/styles/tag/badge.css.map +1 -0
- package/src/styles/tag/tag.css +71 -0
- package/src/styles/tag/tag.css.map +1 -0
- package/src/styles/text-to-speech/text-to-speech.css +32 -0
- package/src/styles/text-to-speech/text-to-speech.css.map +1 -0
- package/src/test/setup.ts +6 -0
- package/src/types/component-props.ts +36 -0
- package/src/types/index.ts +2 -0
- package/src/types/input-props.ts +28 -0
- package/src/types/shared.ts +57 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { StoryObj, Meta } from '@storybook/react'
|
|
2
|
+
import { within, userEvent, screen } from '@storybook/testing-library'
|
|
3
|
+
import { expect } from '@storybook/jest'
|
|
4
|
+
|
|
5
|
+
import Breadcrumb from './breadcrumb'
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof Breadcrumb> = {
|
|
8
|
+
title: 'FP.REACT Components/Breadcrumb',
|
|
9
|
+
component: Breadcrumb,
|
|
10
|
+
parameters: {
|
|
11
|
+
actions: { argTypesRegex: '^on.*' },
|
|
12
|
+
docs: {
|
|
13
|
+
description: {
|
|
14
|
+
component: 'Breadcrumb description here...',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
args: {
|
|
19
|
+
// @ts-ignore
|
|
20
|
+
children: 'Link',
|
|
21
|
+
},
|
|
22
|
+
} as Story
|
|
23
|
+
|
|
24
|
+
export default meta
|
|
25
|
+
type Story = StoryObj<typeof Breadcrumb>
|
|
26
|
+
|
|
27
|
+
export const BreadcrumbComponent: Story = {
|
|
28
|
+
args: {},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const CustomURL: Story = {
|
|
32
|
+
args: {
|
|
33
|
+
routes: [
|
|
34
|
+
{
|
|
35
|
+
name: 'Products',
|
|
36
|
+
url: '/products',
|
|
37
|
+
path: 'product',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'Shirts',
|
|
41
|
+
url: '/products/shirts',
|
|
42
|
+
path: 'shirts',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'Pants',
|
|
46
|
+
url: '/products/pants',
|
|
47
|
+
path: 'pants',
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
currentRoute: '/product/men/shirts/size-22',
|
|
51
|
+
},
|
|
52
|
+
} as Story
|
|
53
|
+
|
|
54
|
+
export const AstroBreadcrumbs: Story = {
|
|
55
|
+
args: {
|
|
56
|
+
...CustomURL.args,
|
|
57
|
+
currentRoute: '/about',
|
|
58
|
+
},
|
|
59
|
+
} as Story
|
|
60
|
+
|
|
61
|
+
export const EncodedBreadcrumbs: Story = {
|
|
62
|
+
args: {
|
|
63
|
+
routes: [
|
|
64
|
+
{
|
|
65
|
+
name: 'Home',
|
|
66
|
+
path: '/',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'Products',
|
|
70
|
+
path: '/products',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'Shirts',
|
|
74
|
+
path: '/products/shirts',
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
currentRoute: '/products/learning%20in%20public',
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
play: async ({ canvasElement }) => {
|
|
81
|
+
const canvas = within(canvasElement)
|
|
82
|
+
await userEvent.click(screen.getByText('Shirts'))
|
|
83
|
+
expect(screen.getByText('Shirts')).toBeInTheDocument()
|
|
84
|
+
},
|
|
85
|
+
} as Story
|
|
86
|
+
|
|
87
|
+
export const TruncateName: Story = {
|
|
88
|
+
args: {
|
|
89
|
+
...CustomURL.args,
|
|
90
|
+
currentRoute: '/products/AveryLongNameTruncate',
|
|
91
|
+
},
|
|
92
|
+
} as Story
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// Code: Breadcrumb component
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import UI from '#components/ui'
|
|
4
|
+
import { Truncate } from '#libs/content'
|
|
5
|
+
|
|
6
|
+
// TYPES
|
|
7
|
+
|
|
8
|
+
type customRoute = {
|
|
9
|
+
/** The path or id for routing */
|
|
10
|
+
path?: string
|
|
11
|
+
/** The display name */
|
|
12
|
+
name: string
|
|
13
|
+
/** The url if linking out */
|
|
14
|
+
url?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type BreadcrumbProps = {
|
|
18
|
+
/** Array of custom route objects */
|
|
19
|
+
routes?: customRoute[]
|
|
20
|
+
/** Starting route node */
|
|
21
|
+
startRoute?: React.ReactNode
|
|
22
|
+
/** Spacer node between routes */
|
|
23
|
+
spacer?: React.ReactNode
|
|
24
|
+
/** String representing current route */
|
|
25
|
+
currentRoute?: string
|
|
26
|
+
/** Prefix breadcrumb aria-label - "prefix breadcrumb" */
|
|
27
|
+
ariaLabelPrefix?: string
|
|
28
|
+
/** Truncate breadcrumb text after this length */
|
|
29
|
+
truncateLength?: number
|
|
30
|
+
} & React.ComponentProps<typeof UI>
|
|
31
|
+
|
|
32
|
+
// Components
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Items component.
|
|
36
|
+
*
|
|
37
|
+
* @param styles - Styles object for the item.
|
|
38
|
+
* @param id - Id for the item.
|
|
39
|
+
* @param classes - Class names for the item.
|
|
40
|
+
* @param children - Child components.
|
|
41
|
+
* @param props - Other props.
|
|
42
|
+
*/
|
|
43
|
+
const Items = ({
|
|
44
|
+
styles,
|
|
45
|
+
id,
|
|
46
|
+
classes,
|
|
47
|
+
children,
|
|
48
|
+
...props
|
|
49
|
+
}: React.ComponentProps<typeof UI>) => {
|
|
50
|
+
return (
|
|
51
|
+
<li data-list="unstyled inline" {...props}>
|
|
52
|
+
{children}
|
|
53
|
+
</li>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* List component.
|
|
59
|
+
*
|
|
60
|
+
* @param children - The content to render inside the list.
|
|
61
|
+
* @param props - Additional props to pass to the UI component.
|
|
62
|
+
*/
|
|
63
|
+
const List = ({ children, ...props }: React.ComponentProps<typeof UI>) => {
|
|
64
|
+
return (
|
|
65
|
+
<UI as="ol" data-list="unstyled inline" {...props}>
|
|
66
|
+
{children}
|
|
67
|
+
</UI>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Nav component.
|
|
73
|
+
*
|
|
74
|
+
* @param styles - Styles object for the nav.
|
|
75
|
+
* @param id - Id for the nav.
|
|
76
|
+
* @param classes - Class names for the nav.
|
|
77
|
+
* @param children - Child components.
|
|
78
|
+
* @param props - Other props.
|
|
79
|
+
*/
|
|
80
|
+
const Nav = ({
|
|
81
|
+
styles,
|
|
82
|
+
id,
|
|
83
|
+
classes,
|
|
84
|
+
children,
|
|
85
|
+
...props
|
|
86
|
+
}: React.ComponentProps<typeof UI>) => {
|
|
87
|
+
return (
|
|
88
|
+
<UI as="nav" id={id} styles={styles} className={classes} {...props}>
|
|
89
|
+
<List>{children}</List>
|
|
90
|
+
</UI>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Navigation component for breadcrumbs.
|
|
96
|
+
*
|
|
97
|
+
* @param props - Props for the navigation component.
|
|
98
|
+
* @param props.startRoute - Starting route node. Default 'Home'.
|
|
99
|
+
* @param props.currentRoute - String representing current route.
|
|
100
|
+
* @param props.spacer - Spacer node between routes. Default '/'.
|
|
101
|
+
* @param props.routes - Array of custom route objects.
|
|
102
|
+
* @param props.styles - Styles object for the nav.
|
|
103
|
+
* @param props.id - Id for the nav.
|
|
104
|
+
* @param props.classes - Class names for the nav.
|
|
105
|
+
* @param props.children - Child components.
|
|
106
|
+
*/
|
|
107
|
+
export const Breadcrumb = ({
|
|
108
|
+
startRoute = 'Home',
|
|
109
|
+
currentRoute,
|
|
110
|
+
spacer = <>/</>,
|
|
111
|
+
routes,
|
|
112
|
+
styles,
|
|
113
|
+
id,
|
|
114
|
+
classes,
|
|
115
|
+
ariaLabelPrefix,
|
|
116
|
+
truncateLength = 15,
|
|
117
|
+
...props
|
|
118
|
+
}: BreadcrumbProps): React.JSX.Element => {
|
|
119
|
+
const [currentPath, setCurrentPath] = React.useState('')
|
|
120
|
+
React.useEffect(() => {
|
|
121
|
+
const path = currentRoute || window.location.pathname
|
|
122
|
+
if (path.length) {
|
|
123
|
+
setCurrentPath(path)
|
|
124
|
+
}
|
|
125
|
+
}, [])
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Gets the path name for the given path segment.
|
|
129
|
+
*
|
|
130
|
+
* @param pathSegment - The path segment (string or number) to get the path name for.
|
|
131
|
+
* @returns The path name object for the given path segment.
|
|
132
|
+
*/
|
|
133
|
+
const getPathName = (pathSegment: string): customRoute => {
|
|
134
|
+
const route = routes?.find((route) => route.path === pathSegment)
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
path: route?.path || pathSegment,
|
|
138
|
+
name: route?.name || pathSegment,
|
|
139
|
+
url: route?.url || pathSegment,
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Array of path segments from current path */
|
|
144
|
+
const segments = currentPath.split('/').filter((segment) => segment)
|
|
145
|
+
/** Index of last item in segments array */
|
|
146
|
+
const lastSegment = segments.length - 1
|
|
147
|
+
|
|
148
|
+
/** Unique id for breadcrumb */
|
|
149
|
+
const uuid = React.useId()
|
|
150
|
+
|
|
151
|
+
return currentPath.length ? (
|
|
152
|
+
<Nav
|
|
153
|
+
id={id}
|
|
154
|
+
{...props}
|
|
155
|
+
styles={styles}
|
|
156
|
+
className={classes}
|
|
157
|
+
aria-label={ariaLabelPrefix}
|
|
158
|
+
>
|
|
159
|
+
<Items key={`${startRoute}-${uuid}`}>
|
|
160
|
+
<a href="/">{startRoute}</a>
|
|
161
|
+
</Items>
|
|
162
|
+
{segments.length ? (
|
|
163
|
+
segments.map((segment: any, index: number) => {
|
|
164
|
+
const currentSegment = getPathName(segment)
|
|
165
|
+
const { name, url, path } = currentSegment
|
|
166
|
+
if (index === lastSegment) {
|
|
167
|
+
return (
|
|
168
|
+
<>
|
|
169
|
+
{typeof segments[lastSegment] === 'string' &&
|
|
170
|
+
segments[lastSegment].length > 3 &&
|
|
171
|
+
segments[lastSegment] !== segments[lastSegment - 1] && (
|
|
172
|
+
<Items key={`${path || index}-${uuid}`}>
|
|
173
|
+
<>
|
|
174
|
+
<span aria-hidden="true">{spacer}</span>
|
|
175
|
+
<a
|
|
176
|
+
aria-current="page"
|
|
177
|
+
aria-label={
|
|
178
|
+
name.length > truncateLength ? name : undefined
|
|
179
|
+
}
|
|
180
|
+
>
|
|
181
|
+
{Truncate(decodeURIComponent(name), truncateLength)}
|
|
182
|
+
</a>
|
|
183
|
+
</>{' '}
|
|
184
|
+
</Items>
|
|
185
|
+
)}
|
|
186
|
+
</>
|
|
187
|
+
)
|
|
188
|
+
} else {
|
|
189
|
+
return (
|
|
190
|
+
<Items key={`${currentSegment?.name}-${uuid}`}>
|
|
191
|
+
<span aria-hidden="true">{spacer}</span>
|
|
192
|
+
<span>
|
|
193
|
+
<a
|
|
194
|
+
href={url}
|
|
195
|
+
aria-label={name.length > truncateLength ? name : undefined}
|
|
196
|
+
>
|
|
197
|
+
{Truncate(decodeURIComponent(name), truncateLength)}
|
|
198
|
+
</a>
|
|
199
|
+
</span>
|
|
200
|
+
</Items>
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
) : (
|
|
205
|
+
<></>
|
|
206
|
+
)}
|
|
207
|
+
</Nav>
|
|
208
|
+
) : (
|
|
209
|
+
<></>
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export default Breadcrumb
|
|
214
|
+
|
|
215
|
+
Breadcrumb.displayName = 'BreadCrumb'
|
|
216
|
+
Breadcrumb.Nav = Nav
|
|
217
|
+
Breadcrumb.List = List
|
|
218
|
+
Breadcrumb.Items = Items
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
button {
|
|
2
|
+
--btn-sm: calc(12rem / 16);
|
|
3
|
+
--btn-md: calc(16rem / 16);
|
|
4
|
+
--btn-lg: calc(21rem / 16);
|
|
5
|
+
--btn-pill: 100rem;
|
|
6
|
+
--btn-height: calc(40rem / 16);
|
|
7
|
+
--fs: 0.95rem;
|
|
8
|
+
--btn-fs: calc(15rem / 16);
|
|
9
|
+
--btn-bg: lightgray;
|
|
10
|
+
--btn-width: max-content;
|
|
11
|
+
|
|
12
|
+
font-size: var(--btn-fs);
|
|
13
|
+
font-weight: var(--btn-fw, 500);
|
|
14
|
+
height: var(--btn-height, calc(40rem / 16));
|
|
15
|
+
min-height: var(--btn-height);
|
|
16
|
+
place-items: var(--btn-place, center);
|
|
17
|
+
padding-inline: var(--btn-px, calc(var(--btn-fs) + 1.1%));
|
|
18
|
+
padding-block: var(--btn-py, calc(var(--btn-fs) + 0.75%));
|
|
19
|
+
border: var(--btn-bdr, none);
|
|
20
|
+
border-radius: var(--btn-rds, calc(6rem / 16));
|
|
21
|
+
text-decoration: var(--btn-deco, none);
|
|
22
|
+
color: var(--btn-cl, currentColor);
|
|
23
|
+
display: var(--btn-dsp, inline-flex);
|
|
24
|
+
gap: var(--btn-gap, 0.2rem);
|
|
25
|
+
white-space: var(--btn-wspc, inherit);
|
|
26
|
+
margin: var(--btn-spc, 0);
|
|
27
|
+
transition: var(--tran-all);
|
|
28
|
+
background-color: var(--btn-bg, var(--btn));
|
|
29
|
+
outline: none;
|
|
30
|
+
width: var(--btn-width);
|
|
31
|
+
|
|
32
|
+
&[type] {
|
|
33
|
+
background-color: var(--btn-bg, var(--neutral-300));
|
|
34
|
+
--btn-bdr: solid var(--btn-sg);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
&[type='submit'],
|
|
38
|
+
&[style*='submit'] {
|
|
39
|
+
--btn-bg: var(--primary-500, royal-blue);
|
|
40
|
+
--btn-color: white;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
&[disabled],
|
|
44
|
+
&[aria-disabled='true'] {
|
|
45
|
+
cursor: var(--btn-cursor, not-allowed);
|
|
46
|
+
// opacity: var(--btn-opacity, 0.5);
|
|
47
|
+
|
|
48
|
+
&:is(:hover, :focus) {
|
|
49
|
+
transform: none;
|
|
50
|
+
// background-color: var(--btn-bg, lightgray);
|
|
51
|
+
// opacity: var(--btn-opacity, 0.5);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// TODO: add hover/focus with :if()
|
|
56
|
+
&:is(:hover, :focus) {
|
|
57
|
+
// --btn-bg: lightgray;
|
|
58
|
+
background-color: var(--btn-bg, var(--btn, lightgray));
|
|
59
|
+
filter: invert(1) hue-rotate (180deg);
|
|
60
|
+
transform: scale(0.95) var(--line-style, solid);
|
|
61
|
+
outline-offset: var(--line-offset, 5px);
|
|
62
|
+
|
|
63
|
+
&[aria-disabled='true'] {
|
|
64
|
+
transform: none;
|
|
65
|
+
opacity: var(--btn-opacity, 0.5);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
&[type='reset'] {
|
|
70
|
+
--btn-bg: transparent;
|
|
71
|
+
--btn-color: gray;
|
|
72
|
+
--btn-bdr: gray thin solid;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
&[type='submit'] {
|
|
76
|
+
--btn-bg: var(--primary-700, blue);
|
|
77
|
+
--btn-cl: #fff;
|
|
78
|
+
--btn-color: rgb(231, 231, 231);
|
|
79
|
+
--btn-border: none;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
&[data-fp-btn~='pill'],
|
|
83
|
+
&[data-btn~='pill'],
|
|
84
|
+
&[data-style~='pill'] {
|
|
85
|
+
border-radius: var(--btn-pill, 100rem);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
&[data-btn~='sm'] {
|
|
89
|
+
--btn-fs: var(--btn-sm);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
&[data-btn~='md'] {
|
|
93
|
+
--btn-fs: var(--btn-md);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
&[data-btn~='lg'] {
|
|
97
|
+
--btn-fs: var(--btn-lg);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
&[data-btn~='text'] {
|
|
101
|
+
--btn-bg: transparent;
|
|
102
|
+
--btn-cl: currentColor;
|
|
103
|
+
--btn-bdr: none;
|
|
104
|
+
--btn-height: unset;
|
|
105
|
+
--btn-width: unset;
|
|
106
|
+
--btn-py: 0.75rem;
|
|
107
|
+
--btn-px: 0.75rem;
|
|
108
|
+
|
|
109
|
+
&:is(:hover, :focus) {
|
|
110
|
+
background-color: transparent;
|
|
111
|
+
outline: 0.07rem solid var(--btn-cl);
|
|
112
|
+
outline-offset: 0;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { StoryObj, Meta } from '@storybook/react'
|
|
2
|
+
import { within, userEvent } from '@storybook/testing-library'
|
|
3
|
+
import { expect } from '@storybook/jest'
|
|
4
|
+
|
|
5
|
+
import Button from './button'
|
|
6
|
+
import './button.scss'
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof Button> = {
|
|
9
|
+
title: 'FP.React Components/Buttons',
|
|
10
|
+
component: Button,
|
|
11
|
+
args: {
|
|
12
|
+
children: 'Click me',
|
|
13
|
+
},
|
|
14
|
+
parameters: {
|
|
15
|
+
// actions: { argTypesRegex: '^on.*' },
|
|
16
|
+
},
|
|
17
|
+
argTypes: { onClick: { action: 'clicked' } },
|
|
18
|
+
} as Meta
|
|
19
|
+
|
|
20
|
+
export default meta
|
|
21
|
+
type Story = StoryObj<typeof Button>
|
|
22
|
+
|
|
23
|
+
export const ButtonComponent: Story = {
|
|
24
|
+
args: {},
|
|
25
|
+
play: async ({ canvasElement }) => {
|
|
26
|
+
const canvas = within(canvasElement)
|
|
27
|
+
expect(canvas.getByRole('button')).toBeInTheDocument()
|
|
28
|
+
await userEvent.tab()
|
|
29
|
+
expect(canvas.getByRole('button')).toHaveFocus()
|
|
30
|
+
},
|
|
31
|
+
} as Story
|
|
32
|
+
|
|
33
|
+
export const Small: Story = {
|
|
34
|
+
args: {
|
|
35
|
+
'data-btn': 'sm',
|
|
36
|
+
},
|
|
37
|
+
} as Story
|
|
38
|
+
|
|
39
|
+
export const Medium: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
'data-btn': 'md',
|
|
42
|
+
},
|
|
43
|
+
} as Story
|
|
44
|
+
|
|
45
|
+
export const Large: Story = {
|
|
46
|
+
args: {
|
|
47
|
+
'data-btn': 'lg',
|
|
48
|
+
},
|
|
49
|
+
} as Story
|
|
50
|
+
|
|
51
|
+
export const Custom: Story = {
|
|
52
|
+
args: {
|
|
53
|
+
styles: {
|
|
54
|
+
'--btn-fs': '2rem',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
} as Story
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import { Button } from './button'
|
|
4
|
+
import user from '@testing-library/user-event'
|
|
5
|
+
import jest from 'jest-mock'
|
|
6
|
+
import { userEvent } from '@storybook/testing-library'
|
|
7
|
+
|
|
8
|
+
describe('Button', () => {
|
|
9
|
+
it('renders a button element with the correct label', () => {
|
|
10
|
+
render(<Button type="button">Click me</Button>)
|
|
11
|
+
const button = screen.getByText('Click me')
|
|
12
|
+
expect(button.tagName).toBe('BUTTON')
|
|
13
|
+
screen.logTestingPlaygroundURL()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('has the type attribute set to "button" by default', () => {
|
|
17
|
+
render(<Button type="button">Click me</Button>)
|
|
18
|
+
const button = screen.getByText('Click me')
|
|
19
|
+
expect(button).toHaveAttribute('type', 'button')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('calls the onClick handler when clicked', async () => {
|
|
23
|
+
const handleClick = jest.fn()
|
|
24
|
+
const handlePointerEvents = jest.fn()
|
|
25
|
+
render(
|
|
26
|
+
<Button type="button" onClick={handleClick}>
|
|
27
|
+
Click me
|
|
28
|
+
</Button>,
|
|
29
|
+
)
|
|
30
|
+
const button = screen.getByText('Click me')
|
|
31
|
+
await userEvent.click(button)
|
|
32
|
+
expect(handleClick).toHaveBeenCalledTimes(1)
|
|
33
|
+
expect(handlePointerEvents).toHaveBeenCalledTimes(0)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('applies custom styles when provided', () => {
|
|
37
|
+
render(
|
|
38
|
+
<Button type="button" styles={{ backgroundColor: 'red' }}>
|
|
39
|
+
Click me
|
|
40
|
+
</Button>,
|
|
41
|
+
)
|
|
42
|
+
const button = screen.getByRole('button')
|
|
43
|
+
expect(button).toHaveAttribute('style')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('calls the onPointerDown handler when pointer is down', async () => {
|
|
47
|
+
const handlePointerDown = jest.fn()
|
|
48
|
+
render(
|
|
49
|
+
<Button type="button" onPointerDown={handlePointerDown}>
|
|
50
|
+
Click me
|
|
51
|
+
</Button>,
|
|
52
|
+
)
|
|
53
|
+
const button = screen.getByText('Click me')
|
|
54
|
+
await userEvent.click(button)
|
|
55
|
+
expect(handlePointerDown).toHaveBeenCalledTimes(1)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('calls the onPointerOver handler when pointer is over', async () => {
|
|
59
|
+
const handlePointerOver = jest.fn()
|
|
60
|
+
render(
|
|
61
|
+
<Button type="button" onPointerOver={handlePointerOver}>
|
|
62
|
+
Click me
|
|
63
|
+
</Button>,
|
|
64
|
+
)
|
|
65
|
+
const button = screen.getByText('Click me')
|
|
66
|
+
await userEvent.hover(button)
|
|
67
|
+
expect(handlePointerOver).toHaveBeenCalledTimes(1)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('calls the onPointerLeave handler when pointer leaves', async () => {
|
|
71
|
+
const handlePointerEvents = jest.fn()
|
|
72
|
+
render(
|
|
73
|
+
<Button type="button" onPointerLeave={handlePointerEvents}>
|
|
74
|
+
Click me
|
|
75
|
+
</Button>,
|
|
76
|
+
)
|
|
77
|
+
const button = screen.getByText('Click me')
|
|
78
|
+
await userEvent.unhover(button)
|
|
79
|
+
expect(handlePointerEvents).toHaveBeenCalledTimes(1)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('it is disabled when disabled is true', () => {
|
|
83
|
+
const handleClick = jest.fn()
|
|
84
|
+
render(
|
|
85
|
+
<Button
|
|
86
|
+
type="button"
|
|
87
|
+
disabled
|
|
88
|
+
onPointerDown={handleClick}
|
|
89
|
+
onPointerOver={handleClick}
|
|
90
|
+
onPointerLeave={handleClick}
|
|
91
|
+
>
|
|
92
|
+
Click me
|
|
93
|
+
</Button>,
|
|
94
|
+
)
|
|
95
|
+
const button = screen.getByText('Click me')
|
|
96
|
+
expect(button).not.toBeDisabled()
|
|
97
|
+
userEvent.click(button)
|
|
98
|
+
expect(handleClick).toHaveBeenCalledTimes(0)
|
|
99
|
+
userEvent.hover(button)
|
|
100
|
+
expect(handleClick).toHaveBeenCalledTimes(0)
|
|
101
|
+
userEvent.unhover(button)
|
|
102
|
+
expect(handleClick).toHaveBeenCalledTimes(0)
|
|
103
|
+
})
|
|
104
|
+
})
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import UI from '../ui'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
|
|
5
|
+
Partial<React.ComponentProps<typeof UI>> & {
|
|
6
|
+
/**
|
|
7
|
+
* The button type
|
|
8
|
+
* Required - 'button' | 'submit' | 'reset'
|
|
9
|
+
*/
|
|
10
|
+
type: 'button' | 'submit' | 'reset'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const Button = ({
|
|
14
|
+
type = 'button',
|
|
15
|
+
children,
|
|
16
|
+
styles,
|
|
17
|
+
disabled,
|
|
18
|
+
classes,
|
|
19
|
+
onPointerDown,
|
|
20
|
+
onPointerOver,
|
|
21
|
+
onPointerLeave,
|
|
22
|
+
|
|
23
|
+
...props
|
|
24
|
+
}: ButtonProps) => {
|
|
25
|
+
const handlePointerDown = (e: React.PointerEvent<HTMLButtonElement>) => {
|
|
26
|
+
if (!disabled) {
|
|
27
|
+
onPointerDown?.(e)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const handlePointerOver = (e: React.PointerEvent<HTMLButtonElement>) => {
|
|
32
|
+
if (!disabled) {
|
|
33
|
+
onPointerOver?.(e)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const handlePointerLeave = (e: React.PointerEvent<HTMLButtonElement>) => {
|
|
38
|
+
if (!disabled) {
|
|
39
|
+
onPointerLeave?.(e)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Returning a button element. */
|
|
44
|
+
return (
|
|
45
|
+
<UI
|
|
46
|
+
as="button"
|
|
47
|
+
type={type}
|
|
48
|
+
onPointerOver={handlePointerOver}
|
|
49
|
+
onPointerDown={handlePointerDown}
|
|
50
|
+
onPointerLeave={handlePointerLeave}
|
|
51
|
+
style={styles}
|
|
52
|
+
className={classes}
|
|
53
|
+
aria-disabled={disabled}
|
|
54
|
+
onClick={handlePointerDown}
|
|
55
|
+
{...props}
|
|
56
|
+
>
|
|
57
|
+
{children}
|
|
58
|
+
</UI>
|
|
59
|
+
)
|
|
60
|
+
//
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default Button
|
|
64
|
+
Button.displayName = 'Button'
|
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[data-card],
|
|
2
|
+
[data-component~='card'] {
|
|
3
|
+
--card-p: 2rem;
|
|
4
|
+
--card-bg: #fff;
|
|
5
|
+
--card-radius: calc(var(--card-p) / 3);
|
|
6
|
+
--card-shadow: 0 0 1rem rgba(0, 0, 0, 0.1);
|
|
7
|
+
--card-position: relative;
|
|
8
|
+
--card-display: flex;
|
|
9
|
+
--card-direction: column;
|
|
10
|
+
--card-gap: 1rem;
|
|
11
|
+
|
|
12
|
+
position: relative;
|
|
13
|
+
display: var(--card-display);
|
|
14
|
+
display: var(--card-display);
|
|
15
|
+
flex-direction: var(--card-direction);
|
|
16
|
+
gap: var(--card-gap);
|
|
17
|
+
box-shadow: var(--card-shadow);
|
|
18
|
+
border-radius: var(--card-radius);
|
|
19
|
+
background-color: var(--card-bg);
|
|
20
|
+
// height: 100%;
|
|
21
|
+
[data-card-content],
|
|
22
|
+
h3,
|
|
23
|
+
h2 {
|
|
24
|
+
margin-block-end: 0;
|
|
25
|
+
}
|
|
26
|
+
> div {
|
|
27
|
+
height: 100%;
|
|
28
|
+
display: var(--card-display);
|
|
29
|
+
flex-direction: var(--card-direction);
|
|
30
|
+
gap: var(--card-gap);
|
|
31
|
+
article {
|
|
32
|
+
flex: 2;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
+ div {
|
|
37
|
+
margin-block-start: 0;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
[data-card] > * {
|
|
42
|
+
padding: var(--card-p);
|
|
43
|
+
}
|