Skip to content

Commit f536033

Browse files
committed
feat(distributeur): add experimental input components with Storybook documentation
- Add Input, Label, InputContainer, InputUnit, and ItemMessage components - Add corresponding CSS styles with grid-based layout system - Create Storybook stories and MDX documentation for all components - Export experimental components via distributeur/experimental entry point
1 parent 2e51f7d commit f536033

31 files changed

+1482
-6
lines changed

.vscode/settings.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,17 @@
66
"source.fixAll.eslint": "explicit",
77
"source.fixAll.stylelint": "explicit"
88
},
9-
"stylelint.validate": ["css", "scss"],
9+
"stylelint.validate": [
10+
"css",
11+
"scss"
12+
],
1013
"github.copilot.chat.commitMessageGeneration.instructions": [
1114
{
1215
"file": ".github/git-commit-instructions.md"
1316
}
14-
]
17+
],
18+
"search.exclude": {
19+
"**/slash": true,
20+
"**/client": true
21+
}
1522
}

apps/slash-stories/.storybook/main.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { StorybookConfig } from "@storybook/react-vite";
2-
import { dirname, join } from "path";
3-
import { fileURLToPath } from "url";
2+
import { dirname, join } from "node:path";
3+
import { fileURLToPath } from "node:url";
44

55
/**
66
* This function is used to resolve the absolute path of a package.
@@ -39,6 +39,39 @@ const config: StorybookConfig = {
3939
core: {
4040
disableTelemetry: true,
4141
},
42+
typescript: {
43+
reactDocgen: "react-docgen",
44+
},
45+
// Black magic to make Storybook use the source code of canopee packages instead of the compiled code, preserving TSDoc comments in Storybook UI
46+
async viteFinal(viteConfig) {
47+
// Merge custom configuration into the default config
48+
const { mergeConfig } = await import("vite");
49+
50+
return mergeConfig(viteConfig, {
51+
resolve: {
52+
alias: {
53+
// Point to TypeScript source files to preserve TSDoc in Storybook
54+
// Order matters: more specific paths must come first
55+
"@axa-fr/canopee-react/distributeur/experimental": fileURLToPath(
56+
new URL(
57+
"../../../packages/canopee-react/src/distributeur-experimental.ts",
58+
import.meta.url,
59+
),
60+
),
61+
"@axa-fr/canopee-react/distributeur": fileURLToPath(
62+
new URL(
63+
"../../../packages/canopee-react/src/distributeur.ts",
64+
import.meta.url,
65+
),
66+
),
67+
},
68+
},
69+
optimizeDeps: {
70+
// Exclude canopee packages from pre-bundling to preserve source code
71+
exclude: ["@axa-fr/canopee-react"],
72+
},
73+
});
74+
},
4275
};
4376

4477
export default config;
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import {
2+
Canvas,
3+
Controls,
4+
Description,
5+
Meta,
6+
Primary,
7+
Subtitle,
8+
Title,
9+
} from "@storybook/blocks";
10+
import * as Stories from "./Input.stories";
11+
12+
<Meta of={Stories} title="Form/Experimental/Input" />
13+
14+
<Title />
15+
16+
## ⚠️ Experimental Component
17+
18+
This component is **experimental** and may change in future releases. You can however use it in your projects, but be aware that the API might change.
19+
We welcome your feedback and contributions to improve this component.
20+
21+
## Overview
22+
23+
The `Input` component is a low-level form input component that provides a styled HTML input element. It serves as the foundation for more complex input components like `TextInput`.
24+
25+
This component is a wrapper around the native HTML `input` element with predefined styles and accessibility features. It's typically used in combination with other form components like `Label`, `InputContainer`, and `InputUnit` to create complete form fields.
26+
27+
## Playground
28+
29+
<Primary />
30+
<Controls />
31+
32+
## Usage
33+
34+
The `Input` component forwards all props to the underlying HTML input element, making it fully compatible with standard input attributes.
35+
36+
```tsx
37+
import { Input } from "@axa-fr/design-system-slash-react/experimental";
38+
39+
const MyComponent = () => {
40+
return <Input type="text" placeholder="Enter your name" />;
41+
};
42+
```
43+
44+
## States
45+
46+
### Disabled
47+
48+
Set the `disabled` prop to true to disable the input field.
49+
50+
<Canvas of={Stories.DisabledStory} />
51+
52+
### Required
53+
54+
Set the `required` prop to mark the input as required.
55+
56+
<Canvas of={Stories.RequiredStory} />
57+
58+
### Invalid
59+
60+
Use the `aria-invalid` attribute to mark the input as invalid. This will apply error styling.
61+
62+
<Canvas of={Stories.InvalidStory} />
63+
64+
## Form Integration
65+
66+
This component can be used with form libraries like [react-hook-form](https://react-hook-form.com/) or [Formik](https://formik.org/). It supports controlled and uncontrolled usage patterns and forwards refs properly.
67+
68+
```tsx
69+
import { Input } from "@axa-fr/design-system-slash-react/experimental";
70+
import { useForm } from "react-hook-form";
71+
72+
const MyForm = () => {
73+
const { register, handleSubmit } = useForm();
74+
75+
return (
76+
<form onSubmit={handleSubmit(console.log)}>
77+
<Input {...register("fieldName")} />
78+
</form>
79+
);
80+
};
81+
```
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { Input } from "@axa-fr/canopee-react/distributeur/experimental";
2+
import { Meta, StoryObj } from "@storybook/react";
3+
4+
const meta = {
5+
title: "Experimental/Form/Atoms/Input",
6+
component: Input,
7+
argTypes: {
8+
placeholder: { control: "text" },
9+
disabled: { control: "boolean" },
10+
required: { control: "boolean" },
11+
type: {
12+
control: { type: "select" },
13+
options: ["text", "email", "password", "number", "tel", "url"],
14+
},
15+
onChange: { action: "changed", table: { disable: true } },
16+
},
17+
} satisfies Meta<typeof Input>;
18+
19+
export default meta;
20+
type Story = StoryObj<typeof Input>;
21+
22+
export const DefaultStory: Story = {
23+
name: "Default",
24+
render: (args) => <Input {...args} />,
25+
args: {
26+
placeholder: "Enter text...",
27+
},
28+
};
29+
30+
export const DisabledStory: Story = {
31+
name: "Disabled",
32+
render: (args) => <Input {...args} />,
33+
args: {
34+
placeholder: "Disabled input",
35+
disabled: true,
36+
value: "Cannot edit this",
37+
},
38+
};
39+
40+
export const RequiredStory: Story = {
41+
name: "Required",
42+
render: (args) => <Input {...args} />,
43+
args: {
44+
placeholder: "Required field",
45+
required: true,
46+
},
47+
};
48+
49+
export const InvalidStory: Story = {
50+
name: "Invalid",
51+
render: (args) => <Input {...args} />,
52+
args: {
53+
placeholder: "Invalid input",
54+
"aria-invalid": true,
55+
value: "Invalid value",
56+
},
57+
};
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import {
2+
Canvas,
3+
Controls,
4+
Description,
5+
Meta,
6+
Primary,
7+
Subtitle,
8+
Title,
9+
} from "@storybook/blocks";
10+
import * as Stories from "./InputContainer.stories";
11+
12+
<Meta of={Stories} title="Form/Experimental/InputContainer" />
13+
14+
<Title />
15+
16+
## ⚠️ Experimental Component
17+
18+
This component is **experimental** and may change in future releases. You can however use it in your projects, but be aware that the API might change.
19+
We welcome your feedback and contributions to improve this component.
20+
21+
## Overview
22+
23+
The `InputContainer` component is a layout component that manages the positioning of form elements using CSS Grid. It coordinates the display of labels, inputs, units, and messages in either horizontal or vertical layouts.
24+
25+
This component is essential for building consistent form layouts and is typically used to wrap `Label`, `Input`, `InputUnit`, and `ItemMessage` components together.
26+
27+
## Playground
28+
29+
<Primary />
30+
<Controls />
31+
32+
## Usage
33+
34+
The `InputContainer` component uses CSS Grid to arrange its children. It automatically positions elements based on their CSS class names (grid areas).
35+
36+
```tsx
37+
import {
38+
Input,
39+
InputContainer,
40+
InputUnit,
41+
Label,
42+
} from "@axa-fr/design-system-slash-react/experimental";
43+
44+
const MyFormField = () => {
45+
return (
46+
<InputContainer>
47+
<Label htmlFor="amount">Amount</Label>
48+
<Input id="amount" type="number" />
49+
<InputUnit>€</InputUnit>
50+
</InputContainer>
51+
);
52+
};
53+
```
54+
55+
## Layout Options
56+
57+
### Horizontal Layout (Default)
58+
59+
By default, the container arranges elements horizontally with the label, input, and unit on the same row.
60+
61+
<Canvas of={Stories.HorizontalStory} />
62+
63+
### Vertical Layout
64+
65+
Set the `vertical` prop to true to stack the label above the input.
66+
67+
<Canvas of={Stories.VerticalStory} />
68+
69+
### Without Unit
70+
71+
The unit element is optional. You can omit it when not needed.
72+
73+
<Canvas of={Stories.WithoutUnitStory} />
74+
75+
## Grid Areas
76+
77+
The container defines the following grid areas that child components automatically fill:
78+
79+
- **label**: Filled by `Label` components
80+
- **input**: Filled by `Input` components
81+
- **unit**: Filled by `InputUnit` components
82+
- **helper-message**: Filled by `ItemMessage` components
83+
- **error-message**: Additional area for error messages
84+
85+
Child components are positioned automatically based on their CSS class names. This makes the container flexible and easy to compose with different combinations of form elements.
86+
87+
## Customization
88+
89+
You can pass a custom `className` to override or extend the default styling:
90+
91+
```tsx
92+
<InputContainer className="my-custom-container">{/* ... */}</InputContainer>
93+
```
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import {
2+
Input,
3+
InputContainer,
4+
InputUnit,
5+
Label,
6+
} from "@axa-fr/canopee-react/distributeur/experimental";
7+
import { Meta, StoryObj } from "@storybook/react";
8+
9+
const meta = {
10+
title: "Experimental/Form/Atoms/InputContainer",
11+
component: InputContainer,
12+
argTypes: {
13+
vertical: { control: "boolean" },
14+
},
15+
} satisfies Meta<typeof InputContainer>;
16+
17+
export default meta;
18+
type Story = StoryObj<typeof InputContainer>;
19+
20+
export const HorizontalStory: Story = {
21+
name: "Horizontal Layout",
22+
render: (args) => (
23+
<InputContainer {...args}>
24+
<Label htmlFor="input-1">Label</Label>
25+
<Input id="input-1" placeholder="Enter text" />
26+
<InputUnit></InputUnit>
27+
</InputContainer>
28+
),
29+
args: {
30+
vertical: false,
31+
},
32+
};
33+
34+
export const VerticalStory: Story = {
35+
name: "Vertical Layout",
36+
render: (args) => (
37+
<InputContainer {...args}>
38+
<Label htmlFor="input-2">Label</Label>
39+
<Input id="input-2" placeholder="Enter text" />
40+
<InputUnit></InputUnit>
41+
</InputContainer>
42+
),
43+
args: {
44+
vertical: true,
45+
},
46+
};
47+
48+
export const WithoutUnitStory: Story = {
49+
name: "Without Unit",
50+
render: (args) => (
51+
<InputContainer {...args}>
52+
<Label htmlFor="input-3">Label</Label>
53+
<Input id="input-3" placeholder="Enter text" />
54+
</InputContainer>
55+
),
56+
args: {
57+
vertical: false,
58+
},
59+
};

0 commit comments

Comments
 (0)