@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.
Files changed (297) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +52 -0
  3. package/dist/chunk-77CZU5XZ.cjs +9 -0
  4. package/dist/chunk-77CZU5XZ.cjs.map +1 -0
  5. package/dist/chunk-D43FJIRQ.cjs +31 -0
  6. package/dist/chunk-D43FJIRQ.cjs.map +1 -0
  7. package/dist/chunk-GJWMCDFS.js +9 -0
  8. package/dist/chunk-GJWMCDFS.js.map +1 -0
  9. package/dist/chunk-PCDUGD3C.js +5 -0
  10. package/dist/chunk-PCDUGD3C.js.map +1 -0
  11. package/dist/hooks.cjs +10 -0
  12. package/dist/hooks.cjs.map +1 -0
  13. package/dist/hooks.d.cts +32 -0
  14. package/dist/hooks.d.ts +32 -0
  15. package/dist/hooks.js +8 -0
  16. package/dist/hooks.js.map +1 -0
  17. package/dist/icon-e6044c73.d.ts +227 -0
  18. package/dist/icons.cjs +73 -0
  19. package/dist/icons.cjs.map +1 -0
  20. package/dist/icons.d.cts +252 -0
  21. package/dist/icons.d.ts +252 -0
  22. package/dist/icons.js +4 -0
  23. package/dist/icons.js.map +1 -0
  24. package/dist/index.cjs +59 -0
  25. package/dist/index.cjs.map +1 -0
  26. package/dist/index.d.cts +566 -0
  27. package/dist/index.d.ts +566 -0
  28. package/dist/index.js +11 -0
  29. package/dist/index.js.map +1 -0
  30. package/libs/chunk-GCGKYLDG.js +7 -0
  31. package/libs/chunk-GCGKYLDG.js.map +1 -0
  32. package/libs/chunk-PDD4N5P5.cjs +10 -0
  33. package/libs/chunk-PDD4N5P5.cjs.map +1 -0
  34. package/libs/chunk-QHIABQNQ.js +8 -0
  35. package/libs/chunk-QHIABQNQ.js.map +1 -0
  36. package/libs/chunk-ZOHIKF6I.cjs +31 -0
  37. package/libs/chunk-ZOHIKF6I.cjs.map +1 -0
  38. package/libs/components/badge/badge.css +1 -0
  39. package/libs/components/badge/badge.css.map +1 -0
  40. package/libs/components/badge/badge.min.css +3 -0
  41. package/libs/components/breadcrumbs/breadcrumb.css +1 -0
  42. package/libs/components/breadcrumbs/breadcrumb.css.map +1 -0
  43. package/libs/components/breadcrumbs/breadcrumb.min.css +3 -0
  44. package/libs/components/buttons/button.css +1 -0
  45. package/libs/components/buttons/button.css.map +1 -0
  46. package/libs/components/buttons/button.min.css +3 -0
  47. package/libs/components/cards/card-style.css +1 -0
  48. package/libs/components/cards/card-style.css.map +1 -0
  49. package/libs/components/cards/card-style.min.css +3 -0
  50. package/libs/components/cards/card.css +1 -0
  51. package/libs/components/cards/card.css.map +1 -0
  52. package/libs/components/cards/card.min.css +3 -0
  53. package/libs/components/details/details.css +1 -0
  54. package/libs/components/details/details.css.map +1 -0
  55. package/libs/components/details/details.min.css +3 -0
  56. package/libs/components/form/form.css +1 -0
  57. package/libs/components/form/form.css.map +1 -0
  58. package/libs/components/form/form.min.css +3 -0
  59. package/libs/components/icons/icon.css +1 -0
  60. package/libs/components/icons/icon.css.map +1 -0
  61. package/libs/components/icons/icon.min.css +3 -0
  62. package/libs/components/images/img.css +1 -0
  63. package/libs/components/images/img.css.map +1 -0
  64. package/libs/components/images/img.min.css +3 -0
  65. package/libs/components/layout/landmarks.css +1 -0
  66. package/libs/components/layout/landmarks.css.map +1 -0
  67. package/libs/components/layout/landmarks.min.css +3 -0
  68. package/libs/components/link/link.css +1 -0
  69. package/libs/components/link/link.css.map +1 -0
  70. package/libs/components/link/link.min.css +3 -0
  71. package/libs/components/nav/nav.css +1 -0
  72. package/libs/components/nav/nav.css.map +1 -0
  73. package/libs/components/nav/nav.min.css +3 -0
  74. package/libs/components/progress/progress.css +1 -0
  75. package/libs/components/progress/progress.css.map +1 -0
  76. package/libs/components/progress/progress.min.css +3 -0
  77. package/libs/components/styles/index.css +1 -0
  78. package/libs/components/styles/index.css.map +1 -0
  79. package/libs/components/styles/index.min.css +3 -0
  80. package/libs/components/tag/tag.css +1 -0
  81. package/libs/components/tag/tag.css.map +1 -0
  82. package/libs/components/tag/tag.min.css +3 -0
  83. package/libs/components/text-to-speech/text-to-speech.css +1 -0
  84. package/libs/components/text-to-speech/text-to-speech.css.map +1 -0
  85. package/libs/components/text-to-speech/text-to-speech.min.css +3 -0
  86. package/libs/hooks.cjs +12 -0
  87. package/libs/hooks.cjs.map +1 -0
  88. package/libs/hooks.d.cts +32 -0
  89. package/libs/hooks.d.ts +32 -0
  90. package/libs/hooks.js +3 -0
  91. package/libs/hooks.js.map +1 -0
  92. package/libs/icons-1f5afc0c.d.ts +318 -0
  93. package/libs/icons.cjs +12 -0
  94. package/libs/icons.cjs.map +1 -0
  95. package/libs/icons.d.cts +2 -0
  96. package/libs/icons.d.ts +2 -0
  97. package/libs/icons.js +3 -0
  98. package/libs/icons.js.map +1 -0
  99. package/libs/index.cjs +71 -0
  100. package/libs/index.cjs.map +1 -0
  101. package/libs/index.css +1 -0
  102. package/libs/index.css.map +1 -0
  103. package/libs/index.d.cts +551 -0
  104. package/libs/index.d.ts +551 -0
  105. package/libs/index.js +11 -0
  106. package/libs/index.js.map +1 -0
  107. package/package.json +125 -0
  108. package/src/App.css +42 -0
  109. package/src/App.tsx +35 -0
  110. package/src/__snapshots__/App.test.tsx.snap +56 -0
  111. package/src/components/.gitkeep +0 -0
  112. package/src/components/__snapshots__/fp.test.tsx.snap +3 -0
  113. package/src/components/badge/badge.scss +20 -0
  114. package/src/components/badge/badge.stories.tsx +54 -0
  115. package/src/components/badge/badge.tsx +17 -0
  116. package/src/components/breadcrumbs/bc-item.tsx +20 -0
  117. package/src/components/breadcrumbs/breadcrumb.scss +35 -0
  118. package/src/components/breadcrumbs/breadcrumb.stories.tsx +92 -0
  119. package/src/components/breadcrumbs/breadcrumb.tsx +218 -0
  120. package/src/components/buttons/button.scss +115 -0
  121. package/src/components/buttons/button.stories.tsx +57 -0
  122. package/src/components/buttons/button.test.tsx +104 -0
  123. package/src/components/buttons/button.tsx +64 -0
  124. package/src/components/cards/card-style.scss +0 -0
  125. package/src/components/cards/card.scss +43 -0
  126. package/src/components/cards/card.stories.tsx +114 -0
  127. package/src/components/cards/card.test.tsx +30 -0
  128. package/src/components/cards/card.tsx +135 -0
  129. package/src/components/cards/flex-card.tsx +15 -0
  130. package/src/components/details/details.scss +75 -0
  131. package/src/components/details/details.stories.tsx +122 -0
  132. package/src/components/details/details.tsx +77 -0
  133. package/src/components/form/README.mdx +70 -0
  134. package/src/components/form/fields.tsx +45 -0
  135. package/src/components/form/form.scss +87 -0
  136. package/src/components/form/form.stories.tsx +49 -0
  137. package/src/components/form/form.tsx +71 -0
  138. package/src/components/form/input.stories.tsx +155 -0
  139. package/src/components/form/inputs.tsx +84 -0
  140. package/src/components/form/select.stories.tsx +38 -0
  141. package/src/components/form/select.tsx +112 -0
  142. package/src/components/form/textarea.tsx +87 -0
  143. package/src/components/fp.test.tsx +56 -0
  144. package/src/components/fp.tsx +78 -0
  145. package/src/components/heading/heading.stories.tsx +75 -0
  146. package/src/components/heading/heading.tsx +27 -0
  147. package/src/components/icons/components/add.tsx +42 -0
  148. package/src/components/icons/components/arrow-down.tsx +52 -0
  149. package/src/components/icons/components/arrow-left.tsx +49 -0
  150. package/src/components/icons/components/arrow-right.tsx +52 -0
  151. package/src/components/icons/components/arrow-up.tsx +49 -0
  152. package/src/components/icons/components/chat.tsx +44 -0
  153. package/src/components/icons/components/code.tsx +50 -0
  154. package/src/components/icons/components/copy.tsx +51 -0
  155. package/src/components/icons/components/down.tsx +33 -0
  156. package/src/components/icons/components/home.tsx +57 -0
  157. package/src/components/icons/components/left.tsx +43 -0
  158. package/src/components/icons/components/minus.tsx +42 -0
  159. package/src/components/icons/components/pause-solid.tsx +48 -0
  160. package/src/components/icons/components/pause.tsx +63 -0
  161. package/src/components/icons/components/play-solid.tsx +44 -0
  162. package/src/components/icons/components/play.tsx +51 -0
  163. package/src/components/icons/components/remove.tsx +42 -0
  164. package/src/components/icons/components/resume-solid.tsx +52 -0
  165. package/src/components/icons/components/resume.tsx +57 -0
  166. package/src/components/icons/components/right.tsx +43 -0
  167. package/src/components/icons/components/star.tsx +38 -0
  168. package/src/components/icons/components/stop-solid.tsx +44 -0
  169. package/src/components/icons/components/stop.tsx +54 -0
  170. package/src/components/icons/components/svg.tsx +44 -0
  171. package/src/components/icons/components/up.tsx +31 -0
  172. package/src/components/icons/components/user.tsx +46 -0
  173. package/src/components/icons/icon.scss +15 -0
  174. package/src/components/icons/icon.stories.tsx +208 -0
  175. package/src/components/icons/icon.tsx +100 -0
  176. package/src/components/icons/index.ts +29 -0
  177. package/src/components/icons/types.ts +12 -0
  178. package/src/components/images/README.mdx +43 -0
  179. package/src/components/images/figure.stories.tsx +34 -0
  180. package/src/components/images/figure.tsx +44 -0
  181. package/src/components/images/img.scss +43 -0
  182. package/src/components/images/img.stories.tsx +24 -0
  183. package/src/components/images/img.test.tsx +43 -0
  184. package/src/components/images/img.tsx +93 -0
  185. package/src/components/images/place-holder.png +0 -0
  186. package/src/components/kit.tsx +56 -0
  187. package/src/components/layout/_header.scss +72 -0
  188. package/src/components/layout/footer.stories.tsx +34 -0
  189. package/src/components/layout/landmarks.scss +51 -0
  190. package/src/components/layout/landmarks.stories.tsx +54 -0
  191. package/src/components/layout/landmarks.tsx +149 -0
  192. package/src/components/layout/main.stories.tsx +90 -0
  193. package/src/components/link/link.scss +92 -0
  194. package/src/components/link/link.stories.tsx +74 -0
  195. package/src/components/link/link.tsx +48 -0
  196. package/src/components/list/list.stories.tsx +52 -0
  197. package/src/components/list/list.tsx +74 -0
  198. package/src/components/modal/dialog.tsx +50 -0
  199. package/src/components/modal/modal.tsx +85 -0
  200. package/src/components/nav/nav.scss +90 -0
  201. package/src/components/nav/nav.stories.tsx +96 -0
  202. package/src/components/nav/nav.tsx +76 -0
  203. package/src/components/popover/node_modules/.vitest/results.json +1 -0
  204. package/src/components/popover/popover.stories.tsx +31 -0
  205. package/src/components/popover/popover.test.tsx +39 -0
  206. package/src/components/popover/popover.tsx +85 -0
  207. package/src/components/progress/progress.scss +70 -0
  208. package/src/components/progress/progress.stories.tsx +51 -0
  209. package/src/components/progress/progress.tsx +82 -0
  210. package/src/components/readme.stories.mdx +7 -0
  211. package/src/components/styles/index.css +520 -0
  212. package/src/components/styles/index.css.map +1 -0
  213. package/src/components/tables/table-elements.tsx +57 -0
  214. package/src/components/tables/table.tsx +57 -0
  215. package/src/components/tag/tag.scss +56 -0
  216. package/src/components/tag/tag.stories.tsx +39 -0
  217. package/src/components/tag/tag.tsx +25 -0
  218. package/src/components/text/text.stories.tsx +67 -0
  219. package/src/components/text/text.tsx +93 -0
  220. package/src/components/text-to-speech/README.mdx +192 -0
  221. package/src/components/text-to-speech/TextInput.tsx +19 -0
  222. package/src/components/text-to-speech/TextToSpeech.stories.tsx +145 -0
  223. package/src/components/text-to-speech/TextToSpeech.tsx +94 -0
  224. package/src/components/text-to-speech/text-to-speech.scss +31 -0
  225. package/src/components/text-to-speech/useTextToSpeech.mdx +182 -0
  226. package/src/components/text-to-speech/useTextToSpeech.tsx +176 -0
  227. package/src/components/text-to-speech/views/TextToSpeechControls.tsx +117 -0
  228. package/src/components/ui.tsx +67 -0
  229. package/src/favicon.svg +15 -0
  230. package/src/hooks/popover/__snapshots__/popover.test.tsx.snap +88 -0
  231. package/src/hooks/popover/node_modules/.vitest/results.json +1 -0
  232. package/src/hooks/popover/popover.tsx +71 -0
  233. package/src/hooks/popover/use-popover.tsx +83 -0
  234. package/src/hooks.ts +1 -0
  235. package/src/icons.ts +1 -0
  236. package/src/index.css +13 -0
  237. package/src/index.scss +19 -0
  238. package/src/index.ts +35 -0
  239. package/src/libs/content.ts +30 -0
  240. package/src/logo.svg +7 -0
  241. package/src/main.tsx +10 -0
  242. package/src/patterns/.gitkeep +0 -0
  243. package/src/patterns/page/page-header.stories.tsx +44 -0
  244. package/src/patterns/page/page-header.tsx +78 -0
  245. package/src/sass/_elements.scss +17 -0
  246. package/src/sass/_globals.scss +162 -0
  247. package/src/sass/_layout.scss +51 -0
  248. package/src/sass/_loading-animation.scss +35 -0
  249. package/src/sass/_mixins.scss +10 -0
  250. package/src/sass/_properties.scss +106 -0
  251. package/src/sass/_reset.scss +183 -0
  252. package/src/sass/_type.scss +43 -0
  253. package/src/setupTest.ts +1 -0
  254. package/src/styles/badge/badge.css +22 -0
  255. package/src/styles/badge/badge.css.map +1 -0
  256. package/src/styles/breadcrumbs/breadcrumb.css +42 -0
  257. package/src/styles/breadcrumbs/breadcrumb.css.map +1 -0
  258. package/src/styles/buttons/button.css +93 -0
  259. package/src/styles/buttons/button.css.map +1 -0
  260. package/src/styles/cards/card-style.css +3 -0
  261. package/src/styles/cards/card-style.css.map +1 -0
  262. package/src/styles/cards/card.css +48 -0
  263. package/src/styles/cards/card.css.map +1 -0
  264. package/src/styles/details/details.css +69 -0
  265. package/src/styles/details/details.css.map +1 -0
  266. package/src/styles/dropdowns/dropdown.css.map +1 -0
  267. package/src/styles/form/form.css +93 -0
  268. package/src/styles/form/form.css.map +1 -0
  269. package/src/styles/form/style.css.map +1 -0
  270. package/src/styles/icons/icon.css +16 -0
  271. package/src/styles/icons/icon.css.map +1 -0
  272. package/src/styles/images/img.css +42 -0
  273. package/src/styles/images/img.css.map +1 -0
  274. package/src/styles/index.css +1330 -0
  275. package/src/styles/index.css.map +1 -0
  276. package/src/styles/layout/landmarks.css +155 -0
  277. package/src/styles/layout/landmarks.css.map +1 -0
  278. package/src/styles/link/link.css +88 -0
  279. package/src/styles/link/link.css.map +1 -0
  280. package/src/styles/nav/nav.css +85 -0
  281. package/src/styles/nav/nav.css.map +1 -0
  282. package/src/styles/progress/progress.css +54 -0
  283. package/src/styles/progress/progress.css.map +1 -0
  284. package/src/styles/progress/sass/progress.css.map +1 -0
  285. package/src/styles/styles/index.css +562 -0
  286. package/src/styles/styles/index.css.map +1 -0
  287. package/src/styles/tag/badge.css.map +1 -0
  288. package/src/styles/tag/tag.css +71 -0
  289. package/src/styles/tag/tag.css.map +1 -0
  290. package/src/styles/text-to-speech/text-to-speech.css +32 -0
  291. package/src/styles/text-to-speech/text-to-speech.css.map +1 -0
  292. package/src/test/setup.ts +6 -0
  293. package/src/types/component-props.ts +36 -0
  294. package/src/types/index.ts +2 -0
  295. package/src/types/input-props.ts +28 -0
  296. package/src/types/shared.ts +57 -0
  297. 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 '&#47;'.
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 = <>&#47;</>,
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
+ }