Skip to content

Commit

Permalink
feat(select): add agent select component
Browse files Browse the repository at this point in the history
fixes #23
  • Loading branch information
JLou committed Jan 16, 2024
1 parent 3f1d57c commit 146b3e2
Show file tree
Hide file tree
Showing 15 changed files with 557 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"[mdx]": {
"editor.wordWrap": "on"
}
}
7 changes: 7 additions & 0 deletions config/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ module.exports = {
"@typescript-eslint/no-useless-constructor": "error",

"@typescript-eslint/consistent-type-assertions": "warn",

"import/no-extraneous-dependencies": [
"error",
{
devDependencies: [".storybook/**", "**/*.stories.tsx"],
},
],
},
settings: {
react: {
Expand Down
112 changes: 112 additions & 0 deletions packages/css/src/Form/InputSelect/InputSelect.agent.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
@use "../../common/common.agent.scss" as common;
@use "../core/FormCore.agent.scss";
@use "../../common/grid.scss";
@use "../../common/reboot.scss";

@mixin _set-message-type($color) {
.af-form__input-text {
border: 1px solid $color;
color: $color;
}

.af-form__select-container {
border: 1px solid $color;
color: $color;
}
}

.af-form {
&__select {
position: relative;

&-container {
position: relative;
display: inline-block;
border: 1px solid common.$color-silver;
background: common.$white;

.glyphicon-menu-down {
position: absolute;
top: 50%;
right: 1em;
font-size: 0.7em;
transform: translateY(-50%);
}
}

&--success,
&--valid {
.af-form__select-container {
margin-right: 1rem;
}

&::after {
font-family: common.$font-family-icon;
color: common.$color-malachite;
content: "\EABA";
}

> .af-btn--circle,
> .af-form__message {
display: none;
}
}

&--valid {
&::after {
display: none;
}

.glyphicon-ok {
position: absolute;
top: 50%;
right: -25px;
width: 17px;
margin-left: 2px;
transform: translate(0, -50%);
fill: common.$color-btn-success;
}
}

&--disabled {
.af-form__select-container {
background: common.$color-mercury;
cursor: not-allowed;
}
}

&--error {
@include _set-message-type(common.$color-red-axa);

select {
color: common.$color-red-axa;
}
}

&--warning {
@include _set-message-type(common.$color-orange-dark);
}
}

&__input-select {
position: relative;
z-index: 1;
padding: 0.5em 2.7em 0.5em 1em;
border: 0;
font-size: 1em;
background: transparent;
appearance: none;

&::-ms-expand {
display: none;
}

&:focus {
border-color: common.$color-axa;
}

&--hasinfobulle {
margin-right: 1rem;
}
}
}
40 changes: 40 additions & 0 deletions packages/css/src/Form/InputSelect/InputSelect.agent.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { Meta, StoryObj } from "@storybook/html";
import "../../common/icons.scss";
import "./InputSelect.agent.scss";

const meta: Meta = {
title: "Select",
};

export default meta;

export const Default: StoryObj = {
render: () => {
const btn = document.createElement("div");
btn.innerHTML = `<div class="col-md-2">
<label class="af-form__group-label" for="select-story">Input Select *</label>
</div>
<div class="col-md-10">
<div class="af-form__select">
<div class="af-form__select-container">
<select class="af-form__input-select" id="select-story" name="select-story" type="text">
<option value="" disabled selected>- Sélectionner -</option>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
<option value="4">Option 4</option>
</select>
<span aria-controls="select-story" class="glyphicon glyphicon-menu-down" />
</div><small class="af-form__message">Aide à la saisie</small>
</div>
</div>`;

btn.className = "af-form__group row";

return btn;
},
args: {
label: "Select",
},
argTypes: {},
};
8 changes: 0 additions & 8 deletions packages/css/src/Form/InputText/InputText.agent.scss
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,6 @@
display: none;
}

&__help {
position: absolute;
bottom: -20px;
display: block;
font-size: 0.8125em;
color: common.$color-gray;
}

&__clear {
position: absolute;
top: 0.75rem;
Expand Down
8 changes: 8 additions & 0 deletions packages/css/src/Form/core/FormCore.agent.scss
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@
}
}

&__help {
position: absolute;
bottom: -20px;
display: block;
font-size: 0.8125em;
color: common.$color-gray;
}

&__input-cmplt {
display: inline-flex;
margin-left: 1rem;
Expand Down
45 changes: 45 additions & 0 deletions packages/react/src/Form/Select/Select.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as SelectInputStories from "./SelectInput.stories.tsx";
import * as SelectStories from "./Select.stories.tsx";
import { ArgsTable, Canvas, Meta } from "@storybook/addon-docs";

<Meta of={SelectInputStories} />

# Select

The select component comes in two variants: [**with layout** `InputSelect`](#inputselect--with-label) and [**without** label `Select`](#select-without-label).
`SelectBase` also exists, but is just the `Select` component in `mode="base"`, and will be deprecated in the future.

In most cases you will want to use the version with the label. However, in some cases, the default layout will not work for you.
In that case, you can use the version without the label.

## `InputSelect` — With label

This is the complete component, with label, description, and error message.

<Canvas of={SelectInputStories.SelectInputStory} />

<ArgsTable story="Select with label" />

### Required

The component can be required. In that case, the label will be followed by a red asterisk. In order to make the component required, you need to add to the `classModifier` the value `required`.

### Status messages

The component can be in one of 4 states: the default one which will display the help message, `success`, `error`, and `warning`.
In order to display the message and color the component, you need to pass the `message`, `messageType` props and set `forceDisplayMessage` to `true`.

<Canvas of={SelectInputStories.SelectWithStatus} />

### Placeholders

The component comes with 2 modes : `base` and `default`. The only difference is that the `base` mode **never** displays the placeholder.
If you are not sure which mode to use, use the `default` mode.

## `Select` Without label

The component without the label is a bare-bones version of the component. It is useful when you need to customize the layout of the component.

<Canvas of={SelectStories.SelectStory} />

<ArgsTable story="Select" />
43 changes: 43 additions & 0 deletions packages/react/src/Form/Select/Select.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Meta, StoryObj } from "@storybook/react";
import { ComponentProps } from "react";
import { Select } from "./Select";

const options = [
{ value: "fun", label: "For fun" },
{ value: "work", label: "For work" },
{ value: "drink", label: "For drink" },
];

const meta: Meta<typeof Select> = {
component: Select,
title: "Components/Form/Input/Select",
argTypes: { onChange: { action: "onChange" } },
};

export default meta;

type StoryProps = ComponentProps<typeof Select>;
type Story = StoryObj<StoryProps>;

export const SelectStory: Story = {
name: "Select",
tags: ["Form", "Input"],
render: ({ onChange, ...args }) => <Select onChange={onChange} {...args} />,
args: {
mode: "default",
className: "",
options,
placeholder: "- Select -",
name: "name",
id: "nameid",
disabled: false,
},
argTypes: {
mode: {
control: {
type: "select",
options: ["default", "base"],
},
},
},
};
13 changes: 13 additions & 0 deletions packages/react/src/Form/Select/Select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ComponentProps } from "react";
import { SelectBase } from "./SelectBase";
import { SelectDefault } from "./SelectDefault";

type SelectProps = ComponentProps<typeof SelectDefault> & {
mode?: "default" | "base";
};
const Select = ({ mode = "default", children, ...props }: SelectProps) => {
const DynamicComponent = mode === "default" ? SelectDefault : SelectBase;
return <DynamicComponent {...props}>{children}</DynamicComponent>;
};

export { Select };
50 changes: 50 additions & 0 deletions packages/react/src/Form/Select/SelectBase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import "@axa-fr/design-system-css/dist/Form/InputSelect/InputSelect.agent.scss";
import {
ComponentPropsWithoutRef,
forwardRef,
OptionHTMLAttributes,
} from "react";
import { getComponentClassName } from "../core";

type Props = Omit<
ComponentPropsWithoutRef<"select"> & {
options: OptionHTMLAttributes<HTMLOptionElement>[];
classModifier?: string;
},
"required"
>;

/**
* @deprecated Use Select instead
*/
const SelectBase = forwardRef<HTMLSelectElement, Props>(
({ options, id, className, classModifier, ...otherProps }, inputRef) => {
const componentClassName = getComponentClassName(
className,
classModifier,
"af-form__input-select",
);
return (
<div className="af-form__select-container">
<select
{...otherProps}
id={id}
className={componentClassName}
ref={inputRef}
required={classModifier?.includes("required")}
>
{options.map(({ label, ...opt }) => (
<option key={opt.value?.toString()} {...opt}>
{label}
</option>
))}
</select>
<span aria-controls={id} className="glyphicon glyphicon-menu-down" />
</div>
);
},
);

SelectBase.displayName = "SelectBase";

export { SelectBase };
Loading

0 comments on commit 146b3e2

Please sign in to comment.