@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,87 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--input-border-color: gray;
|
|
3
|
+
--input-appearance: none;
|
|
4
|
+
--input-bg: inherit;
|
|
5
|
+
--input-border: none;
|
|
6
|
+
--input-outline: thin solid var(--input-border-color);
|
|
7
|
+
--input-px: 0.6rem;
|
|
8
|
+
--input-py: 0.4rem;
|
|
9
|
+
--input-radius: --var(--radius);
|
|
10
|
+
--input-fs: var(--fs);
|
|
11
|
+
--input-w: clamp(200px, 100%, 500px);
|
|
12
|
+
--placeholder-color: gray;
|
|
13
|
+
--placeholder-style: italic;
|
|
14
|
+
--placeholder-fs: smaller;
|
|
15
|
+
--form-direction: column;
|
|
16
|
+
--select-arrow: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20'><polyline points='6,9 10,13 14,9' stroke='%23000000' stroke-width='1.5' fill='none' /></svg>");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
form {
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: var(--form-direction);
|
|
22
|
+
gap: 1rem;
|
|
23
|
+
> div {
|
|
24
|
+
display: flex;
|
|
25
|
+
gap: 1rem;
|
|
26
|
+
flex-direction: var(--form-direction);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
label {
|
|
30
|
+
display: block;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
input[type]:not([type='checkbox'], [type='radio']),
|
|
35
|
+
textarea,
|
|
36
|
+
select {
|
|
37
|
+
-webkit-appearance: var(--input-appearance);
|
|
38
|
+
-moz-appearance: var(--input-appearance);
|
|
39
|
+
appearance: var(--input-appearance);
|
|
40
|
+
width: var(--input-w);
|
|
41
|
+
border: var(--input-border);
|
|
42
|
+
outline: var(--input-outline);
|
|
43
|
+
padding-inline: var(--input-px);
|
|
44
|
+
padding-block: var(--input-py);
|
|
45
|
+
border-radius: var(--input-radius);
|
|
46
|
+
background-color: var(--input-bg, #fff);
|
|
47
|
+
|
|
48
|
+
&::placeholder {
|
|
49
|
+
color: var(--placeholder-color);
|
|
50
|
+
font-style: var(--placeholder-style);
|
|
51
|
+
font-size: var(--placeholder-fs);
|
|
52
|
+
text-transform: capitalize;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
&:focus-visible, &:focus {
|
|
56
|
+
outline-width: medium;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
&[aria-required='true'] {
|
|
61
|
+
&::placeholder {
|
|
62
|
+
color: var(--color-required, var(--placeholder-color));
|
|
63
|
+
font-weight: 600;
|
|
64
|
+
&::after {
|
|
65
|
+
content: '* ';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
&[aria-disabled='true'] {
|
|
71
|
+
--input-border-color: lightgray;
|
|
72
|
+
cursor: not-allowed;
|
|
73
|
+
text-transform: capitalize;
|
|
74
|
+
text-decoration: line-through;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
select {
|
|
79
|
+
border: var(--input-outline);
|
|
80
|
+
outline: none;
|
|
81
|
+
-webkit-appearance: none; /* Remove default arrow in Chrome and Safari */
|
|
82
|
+
-moz-appearance: none; /* Remove default arrow in Firefox */
|
|
83
|
+
appearance: none; /* Remove default arrow in other browsers */
|
|
84
|
+
background: var(--select-arrow) no-repeat;
|
|
85
|
+
background-position: right 0.5rem top 50%;
|
|
86
|
+
padding-inline-end: 0;
|
|
87
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
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 Form from './form'
|
|
6
|
+
import './form.scss'
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof Form> = {
|
|
9
|
+
title: 'FP.REACT Forms/Examples',
|
|
10
|
+
component: Form,
|
|
11
|
+
parameters: {
|
|
12
|
+
docs: {
|
|
13
|
+
description: {
|
|
14
|
+
component: 'Form description here...',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
args: {
|
|
19
|
+
children: 'Link',
|
|
20
|
+
name: 'my-form',
|
|
21
|
+
},
|
|
22
|
+
} as Story
|
|
23
|
+
|
|
24
|
+
export default meta
|
|
25
|
+
type Story = StoryObj<typeof Form>
|
|
26
|
+
|
|
27
|
+
export const FormComponent: Story = {
|
|
28
|
+
args: {
|
|
29
|
+
children: (
|
|
30
|
+
<>
|
|
31
|
+
<Form.Field label="Name" labelFor="name" id="name-field">
|
|
32
|
+
<Form.Input id="name" name="name" />
|
|
33
|
+
</Form.Field>
|
|
34
|
+
<Form.Field label="Email" labelFor="email">
|
|
35
|
+
<Form.Input id="email" name="email" type="email" />
|
|
36
|
+
</Form.Field>
|
|
37
|
+
<Form.Field label="Message" labelFor="message">
|
|
38
|
+
<Form.Textarea id="message" name="message" />
|
|
39
|
+
</Form.Field>
|
|
40
|
+
<button type="submit">Submit Form</button>
|
|
41
|
+
</>
|
|
42
|
+
),
|
|
43
|
+
},
|
|
44
|
+
play: async ({ canvasElement }) => {
|
|
45
|
+
const canvas = within(canvasElement)
|
|
46
|
+
const form = canvas.getByRole('form')
|
|
47
|
+
await expect(form).toBeInTheDocument()
|
|
48
|
+
},
|
|
49
|
+
} as Story
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import UI from '#components/fp'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import Input from './inputs'
|
|
5
|
+
import Field from './fields'
|
|
6
|
+
import Select from './select'
|
|
7
|
+
import Textarea from './textarea'
|
|
8
|
+
|
|
9
|
+
export type FormProps = Partial<React.ComponentProps<typeof UI>> &
|
|
10
|
+
React.ComponentProps<'form'>
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Form component
|
|
14
|
+
* @param {Object} props - Form component props
|
|
15
|
+
* @param {string} [id] - Unique identifier for form
|
|
16
|
+
* @param {string} [name] - Name for form
|
|
17
|
+
* @param {Object} [styles] - Inline styles
|
|
18
|
+
* @param {string} [classes] - Additional classes
|
|
19
|
+
* @param {ReactNode} children - Child elements
|
|
20
|
+
* @param {string} [action] - Form action URL
|
|
21
|
+
* @param {('get'|'post')} [formMethod='post'] - Form method
|
|
22
|
+
* @param {Function} [onSubmit] - Submit callback
|
|
23
|
+
* @param {string} [target] - Form submit target
|
|
24
|
+
* @param {boolean} [noValidate=false] - Disable validation
|
|
25
|
+
* @param {Object} ...props - Additional props
|
|
26
|
+
*/
|
|
27
|
+
export const Form = ({
|
|
28
|
+
id,
|
|
29
|
+
name,
|
|
30
|
+
styles,
|
|
31
|
+
classes,
|
|
32
|
+
children,
|
|
33
|
+
action,
|
|
34
|
+
formMethod,
|
|
35
|
+
onSubmit,
|
|
36
|
+
target,
|
|
37
|
+
noValidate,
|
|
38
|
+
...props
|
|
39
|
+
}: FormProps) => {
|
|
40
|
+
const onSubmitCallback = (e: React.FormEvent<HTMLFormElement>) => {
|
|
41
|
+
if (onSubmit) {
|
|
42
|
+
e.preventDefault()
|
|
43
|
+
onSubmit?.(e)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<UI
|
|
49
|
+
as="form"
|
|
50
|
+
id={id}
|
|
51
|
+
name={name}
|
|
52
|
+
className={classes}
|
|
53
|
+
styles={styles}
|
|
54
|
+
action={action}
|
|
55
|
+
novalidate={noValidate}
|
|
56
|
+
method={formMethod}
|
|
57
|
+
onSubmit={onSubmitCallback}
|
|
58
|
+
target={target}
|
|
59
|
+
{...props}
|
|
60
|
+
>
|
|
61
|
+
{children}
|
|
62
|
+
</UI>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default Form
|
|
67
|
+
Form.displayName = 'Form'
|
|
68
|
+
Form.Field = Field
|
|
69
|
+
Form.Input = Input
|
|
70
|
+
Form.Select = Select
|
|
71
|
+
Form.Textarea = Textarea
|
|
@@ -0,0 +1,155 @@
|
|
|
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 Input from './inputs'
|
|
6
|
+
import './form.scss'
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof Input> = {
|
|
9
|
+
title: 'FP.REACT Forms/Inputs',
|
|
10
|
+
component: Input,
|
|
11
|
+
args: {},
|
|
12
|
+
parameters: {
|
|
13
|
+
docs: {
|
|
14
|
+
description: {
|
|
15
|
+
component:
|
|
16
|
+
'Use the `<Input type="***"/>` component to render an any input element -- text, email, number etc. Pass props like `name`, `value`, `placeholder` etc to control the input.',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
} as Story
|
|
21
|
+
|
|
22
|
+
export default meta
|
|
23
|
+
type Story = StoryObj<typeof Input>
|
|
24
|
+
|
|
25
|
+
export const InputComponent: Story = {
|
|
26
|
+
args: {},
|
|
27
|
+
play: async ({ canvasElement }) => {
|
|
28
|
+
const canvas = within(canvasElement)
|
|
29
|
+
expect(canvas.getByRole('textbox')).toBeInTheDocument()
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//required input story
|
|
34
|
+
export const RequiredInput: Story = {
|
|
35
|
+
parameters: {
|
|
36
|
+
docs: {
|
|
37
|
+
description: {
|
|
38
|
+
story:
|
|
39
|
+
'Displays a required input `aria-required="true"` on any input type the placeholder displays an `*` at the start of a default placeholder text to indicate it is required',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
args: {
|
|
44
|
+
type: 'text',
|
|
45
|
+
required: true,
|
|
46
|
+
placeholder: 'This Field is required (placeholder)',
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
play: async ({ canvasElement }) => {
|
|
50
|
+
const canvas = within(canvasElement)
|
|
51
|
+
const input = canvas.getByRole('textbox')
|
|
52
|
+
expect(input).toBeRequired()
|
|
53
|
+
|
|
54
|
+
await userEvent.type(input, 'test')
|
|
55
|
+
expect(input).toBeValid()
|
|
56
|
+
|
|
57
|
+
await userEvent.clear(input)
|
|
58
|
+
|
|
59
|
+
userEvent.type(input, '\n')
|
|
60
|
+
expect(input).toBeInvalid()
|
|
61
|
+
},
|
|
62
|
+
} as Story
|
|
63
|
+
|
|
64
|
+
export const DefaultRequired: Story = {
|
|
65
|
+
args: {
|
|
66
|
+
type: 'text',
|
|
67
|
+
required: true,
|
|
68
|
+
},
|
|
69
|
+
} as Story
|
|
70
|
+
|
|
71
|
+
export const InputDisabled: Story = {
|
|
72
|
+
parameters: {
|
|
73
|
+
docs: {
|
|
74
|
+
description: {
|
|
75
|
+
story:
|
|
76
|
+
'Displays a disabled input `aria-disabled="true"` on any input type',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
args: {
|
|
81
|
+
type: 'text',
|
|
82
|
+
isDisabled: true,
|
|
83
|
+
},
|
|
84
|
+
} as Story
|
|
85
|
+
|
|
86
|
+
export const EmailInput: Story = {
|
|
87
|
+
args: {
|
|
88
|
+
type: 'email',
|
|
89
|
+
},
|
|
90
|
+
play: async ({ canvasElement }) => {
|
|
91
|
+
const canvas = within(canvasElement)
|
|
92
|
+
const input = canvas.getByRole('textbox')
|
|
93
|
+
expect(input).toHaveAttribute('type', 'email')
|
|
94
|
+
|
|
95
|
+
await userEvent.type(input, 'test@example.com')
|
|
96
|
+
expect(input).toHaveValue('test@example.com')
|
|
97
|
+
},
|
|
98
|
+
} as Story
|
|
99
|
+
|
|
100
|
+
export const PasswordInput: Story = {
|
|
101
|
+
args: {
|
|
102
|
+
type: 'password',
|
|
103
|
+
},
|
|
104
|
+
play: async ({ canvasElement }) => {
|
|
105
|
+
const canvas = within(canvasElement)
|
|
106
|
+
const input = canvas.getByPlaceholderText(/password/i)
|
|
107
|
+
expect(input).toHaveAttribute('type', 'password')
|
|
108
|
+
|
|
109
|
+
await userEvent.type(input, 'password')
|
|
110
|
+
expect(input).toHaveValue('password')
|
|
111
|
+
},
|
|
112
|
+
} as Story
|
|
113
|
+
|
|
114
|
+
export const SearchInput: Story = {
|
|
115
|
+
args: {
|
|
116
|
+
type: 'search',
|
|
117
|
+
},
|
|
118
|
+
play: async ({ canvasElement }) => {
|
|
119
|
+
const canvas = within(canvasElement)
|
|
120
|
+
const input = canvas.getByRole('searchbox')
|
|
121
|
+
expect(input).toHaveAttribute('type', 'search')
|
|
122
|
+
|
|
123
|
+
await userEvent.type(input, 'search term')
|
|
124
|
+
expect(input).toHaveValue('search term')
|
|
125
|
+
},
|
|
126
|
+
} as Story
|
|
127
|
+
|
|
128
|
+
export const TelInput: Story = {
|
|
129
|
+
args: {
|
|
130
|
+
type: 'tel',
|
|
131
|
+
},
|
|
132
|
+
play: async ({ canvasElement }) => {
|
|
133
|
+
const canvas = within(canvasElement)
|
|
134
|
+
const input = canvas.getByRole('textbox')
|
|
135
|
+
expect(input).toHaveAttribute('type', 'tel')
|
|
136
|
+
|
|
137
|
+
await userEvent.type(input, '1234567890')
|
|
138
|
+
expect(input).toHaveValue('1234567890')
|
|
139
|
+
},
|
|
140
|
+
} as Story
|
|
141
|
+
|
|
142
|
+
// URL text input story
|
|
143
|
+
export const UrlInput: Story = {
|
|
144
|
+
args: {
|
|
145
|
+
type: 'url',
|
|
146
|
+
},
|
|
147
|
+
play: async ({ canvasElement }) => {
|
|
148
|
+
const canvas = within(canvasElement)
|
|
149
|
+
const input = canvas.getByRole('textbox')
|
|
150
|
+
expect(input).toHaveAttribute('type', 'url')
|
|
151
|
+
|
|
152
|
+
await userEvent.type(input, 'https://example.com')
|
|
153
|
+
expect(input).toHaveValue('https://example.com')
|
|
154
|
+
},
|
|
155
|
+
} as Story
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import FP from '../fp'
|
|
3
|
+
|
|
4
|
+
export type InputProps = {
|
|
5
|
+
/**
|
|
6
|
+
* The type of the input.
|
|
7
|
+
*/
|
|
8
|
+
type?: 'text' | 'password' | 'email' | 'number' | 'tel' | 'url' | 'search'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Set the element as disabled
|
|
12
|
+
*/
|
|
13
|
+
isDisabled?: boolean
|
|
14
|
+
} & React.ComponentProps<typeof FP>
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Input component that renders an HTML input element.
|
|
18
|
+
* @param {InputProps} props - The input component props.
|
|
19
|
+
* @returns {JSX.Element} - The input component.
|
|
20
|
+
*/
|
|
21
|
+
export const Input = ({
|
|
22
|
+
type = 'text',
|
|
23
|
+
name,
|
|
24
|
+
value,
|
|
25
|
+
placeholder,
|
|
26
|
+
id,
|
|
27
|
+
styles,
|
|
28
|
+
classes,
|
|
29
|
+
isDisabled,
|
|
30
|
+
disabled,
|
|
31
|
+
readonly,
|
|
32
|
+
required,
|
|
33
|
+
ref,
|
|
34
|
+
onChange,
|
|
35
|
+
onBlur,
|
|
36
|
+
onPointerDown,
|
|
37
|
+
...props
|
|
38
|
+
}: InputProps): JSX.Element => {
|
|
39
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
40
|
+
if (onChange && !disabled) {
|
|
41
|
+
onChange?.(e)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
|
46
|
+
if (onBlur && !disabled) {
|
|
47
|
+
onBlur?.(e)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
52
|
+
if (onPointerDown && !disabled) {
|
|
53
|
+
e.preventDefault()
|
|
54
|
+
onPointerDown?.(e)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<FP
|
|
60
|
+
as="input"
|
|
61
|
+
id={id}
|
|
62
|
+
type={type}
|
|
63
|
+
placeholder={placeholder || `${required ? '*' : ''} ${type} input `}
|
|
64
|
+
className={classes}
|
|
65
|
+
styles={styles}
|
|
66
|
+
onChange={handleChange}
|
|
67
|
+
onBlur={handleBlur}
|
|
68
|
+
onKeyDown={handleKeyDown}
|
|
69
|
+
value={value}
|
|
70
|
+
name={name}
|
|
71
|
+
ref={ref}
|
|
72
|
+
aria-disabled={isDisabled}
|
|
73
|
+
tabIndex={isDisabled ? -1 : undefined}
|
|
74
|
+
aria-readonly={readonly}
|
|
75
|
+
aria-required={required}
|
|
76
|
+
required={required}
|
|
77
|
+
readOnly={readonly}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
Input.displayName = 'Input'
|
|
84
|
+
export default Input
|
|
@@ -0,0 +1,38 @@
|
|
|
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 Select from './select'
|
|
6
|
+
import React from 'react'
|
|
7
|
+
const meta: Meta<typeof Select> = {
|
|
8
|
+
title: 'FP.REACT Forms/Select',
|
|
9
|
+
component: Select,
|
|
10
|
+
parameters: {
|
|
11
|
+
docs: {
|
|
12
|
+
description: {
|
|
13
|
+
component: 'Base select/Combobox component',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
args: {
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
children: (
|
|
20
|
+
<>
|
|
21
|
+
<Select.Option selectValue="value" selectLabel="Option 1" />
|
|
22
|
+
<Select.Option selectValue="value" selectLabel="Option 2" />
|
|
23
|
+
<Select.Option selectValue="value" selectLabel="Option 3" />
|
|
24
|
+
</>
|
|
25
|
+
),
|
|
26
|
+
},
|
|
27
|
+
} as Story
|
|
28
|
+
|
|
29
|
+
export default meta
|
|
30
|
+
type Story = StoryObj<typeof Select>
|
|
31
|
+
|
|
32
|
+
export const SelectComponent: Story = {
|
|
33
|
+
args: {},
|
|
34
|
+
play: async ({ canvasElement }) => {
|
|
35
|
+
// const canvas = within(canvasElement)
|
|
36
|
+
// expect(canvas.getByRole('combobox')).toBeInTheDocument()
|
|
37
|
+
},
|
|
38
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import UI from '../ui'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
export type SelectProps = React.ComponentProps<typeof UI>
|
|
5
|
+
|
|
6
|
+
export type SelectOptionsProps = {
|
|
7
|
+
/**
|
|
8
|
+
* Label for the select option
|
|
9
|
+
*/
|
|
10
|
+
selectLabel: string
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Value for the select option. Can be a number or string.
|
|
14
|
+
*/
|
|
15
|
+
selectValue: string
|
|
16
|
+
} & SelectProps
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Option component for select.
|
|
20
|
+
* @param {SelectOptionsProps} param0 - The component props.
|
|
21
|
+
* @param {string} param0.selectValue - Value for the option.
|
|
22
|
+
* @param {string} [param0.selectLabel] - Label for the option.
|
|
23
|
+
*/
|
|
24
|
+
export const Option = ({ selectValue, selectLabel }: SelectOptionsProps) => {
|
|
25
|
+
return (
|
|
26
|
+
<option role="option" value={selectValue}>
|
|
27
|
+
{selectLabel || selectValue}
|
|
28
|
+
</option>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Select component props.
|
|
34
|
+
* @param {string} [id] - Unique id for the select.
|
|
35
|
+
* @param {string} [name] - Name for the select input.
|
|
36
|
+
* @param {React.CSSProperties} [styles] - Inline styles.
|
|
37
|
+
* @param {string} [classes] - CSS classes.
|
|
38
|
+
* @param {boolean} [disabled] - Whether select is disabled.
|
|
39
|
+
* @param {React.ReactNode} [children] - Child elements.
|
|
40
|
+
* @param {boolean} [required] - Whether select is required.
|
|
41
|
+
* @param {string | number | string[] | undefined} [selected] - Selected option value(s).
|
|
42
|
+
* @param {React.FocusEventHandler<HTMLSelectElement>} [onBlur] - Blur event handler.
|
|
43
|
+
* @param {React.ChangeEventHandler<HTMLSelectElement>} [onChange] - Change event handler.
|
|
44
|
+
* @param {(e: React.ChangeEvent<HTMLSelectElement>) => void} [onSelectionChange] - Selection change handler.
|
|
45
|
+
* @param {(e: React.PointerEvent<HTMLSelectElement>) => void} [onPointerDown] - Pointer down handler.
|
|
46
|
+
* @param {React.Ref<HTMLSelectElement>} [ref] - Ref for the select element.
|
|
47
|
+
*/
|
|
48
|
+
export const Select = ({
|
|
49
|
+
id,
|
|
50
|
+
name,
|
|
51
|
+
styles,
|
|
52
|
+
classes,
|
|
53
|
+
disabled,
|
|
54
|
+
children,
|
|
55
|
+
required,
|
|
56
|
+
selected,
|
|
57
|
+
onBlur,
|
|
58
|
+
onSelectionChange,
|
|
59
|
+
onPointerDown,
|
|
60
|
+
ref,
|
|
61
|
+
...props
|
|
62
|
+
}: SelectProps) => {
|
|
63
|
+
const handleOnChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
64
|
+
if (onSelectionChange && !disabled) onSelectionChange?.(e)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const handlePointerDown = (e: React.PointerEvent<HTMLSelectElement>) => {
|
|
68
|
+
if (onPointerDown && !disabled) onPointerDown?.(e)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const handleOnBlur = (e: React.FocusEvent<HTMLSelectElement>) => {
|
|
72
|
+
if (onBlur && !disabled) onBlur?.(e)
|
|
73
|
+
const handleOnChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
74
|
+
if (onSelectionChange && !disabled) onSelectionChange?.(e)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const handlePointerDown = (e: React.PointerEvent<HTMLSelectElement>) => {
|
|
78
|
+
if (onPointerDown && !disabled) onPointerDown?.(e)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const handleOnBlur = (e: React.FocusEvent<HTMLSelectElement>) => {
|
|
82
|
+
if (onBlur && !disabled) onBlur?.(e)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<UI
|
|
87
|
+
as="select"
|
|
88
|
+
id={id}
|
|
89
|
+
ref={ref}
|
|
90
|
+
name={name}
|
|
91
|
+
className={classes}
|
|
92
|
+
selected={selected}
|
|
93
|
+
onChange={handleOnChange}
|
|
94
|
+
onPointerDown={handlePointerDown}
|
|
95
|
+
onBlur={handleOnBlur}
|
|
96
|
+
required={required}
|
|
97
|
+
aria-required={required} // Accessibility
|
|
98
|
+
disabled={disabled}
|
|
99
|
+
aria-disabled={disabled ? true : false}
|
|
100
|
+
style={styles}
|
|
101
|
+
{...props} // Accessibility
|
|
102
|
+
>
|
|
103
|
+
<option value="" />
|
|
104
|
+
</UI>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export default Select
|
|
109
|
+
Select.displayName = 'Select' // Remove this line
|
|
110
|
+
Select.Option = Option // Remove this line
|
|
111
|
+
|
|
112
|
+
// export const MemoizedSelect = React.memo(Select)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import UI from '../ui'
|
|
3
|
+
|
|
4
|
+
export type TextareaProps = React.ComponentProps<'textarea'> &
|
|
5
|
+
React.ComponentProps<typeof UI>
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Textarea component.
|
|
9
|
+
*
|
|
10
|
+
* @param value - The value of the textarea.
|
|
11
|
+
* @param rows - The number of rows.
|
|
12
|
+
* @param cols - The number of columns.
|
|
13
|
+
* @param id - The id of the textarea.
|
|
14
|
+
* @param name - The name of the textarea.
|
|
15
|
+
* @param required - Whether the textarea is required.
|
|
16
|
+
* @param disabled - Whether the textarea is disabled.
|
|
17
|
+
* @param readOnly - Whether the textarea is read only.
|
|
18
|
+
* @param onBlur - Blur event handler.
|
|
19
|
+
* @param onPointerDown - Pointer down event handler.
|
|
20
|
+
* @param onChange - Change event handler.
|
|
21
|
+
* @param ref - Ref for the textarea.
|
|
22
|
+
* @param styles - Styles object for the textarea.
|
|
23
|
+
* @param textareaRef - Ref specifically for the textarea element.
|
|
24
|
+
* @param props - Other props.
|
|
25
|
+
*/
|
|
26
|
+
export const Textarea = ({
|
|
27
|
+
id,
|
|
28
|
+
classes,
|
|
29
|
+
value,
|
|
30
|
+
rows = 5,
|
|
31
|
+
cols = 25,
|
|
32
|
+
name,
|
|
33
|
+
required,
|
|
34
|
+
disabled,
|
|
35
|
+
readOnly,
|
|
36
|
+
onBlur,
|
|
37
|
+
onPointerDown,
|
|
38
|
+
onChange,
|
|
39
|
+
ref,
|
|
40
|
+
styles,
|
|
41
|
+
placeholder,
|
|
42
|
+
...props
|
|
43
|
+
}: TextareaProps) => {
|
|
44
|
+
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
45
|
+
if (onChange && !disabled) {
|
|
46
|
+
onChange?.(e)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const handleBlur = (e: React.FocusEvent<HTMLTextAreaElement>) => {
|
|
51
|
+
if (onBlur && !disabled) {
|
|
52
|
+
onBlur?.(e)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const handlePointerDown = (e: React.PointerEvent<HTMLTextAreaElement>) => {
|
|
57
|
+
if (onPointerDown && !disabled) {
|
|
58
|
+
onPointerDown?.(e)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<UI
|
|
64
|
+
as="textarea"
|
|
65
|
+
id={id}
|
|
66
|
+
name={name}
|
|
67
|
+
rows={rows}
|
|
68
|
+
cols={cols}
|
|
69
|
+
styles={styles}
|
|
70
|
+
className={classes}
|
|
71
|
+
data-style="textarea"
|
|
72
|
+
required={required}
|
|
73
|
+
value={value}
|
|
74
|
+
aria-disabled={disabled}
|
|
75
|
+
readOnly={readOnly}
|
|
76
|
+
onChange={handleChange}
|
|
77
|
+
onBlur={handleBlur}
|
|
78
|
+
onPointerDown={handlePointerDown}
|
|
79
|
+
ref={ref}
|
|
80
|
+
placeholder={placeholder || `${required ? '*' : ''} Message`}
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default Textarea
|
|
87
|
+
Textarea.displayName = 'Textarea'
|