Skip to content

Commit ffb6737

Browse files
committed
Better Input API
1 parent 9e88927 commit ffb6737

File tree

2 files changed

+63
-44
lines changed

2 files changed

+63
-44
lines changed

src/Input.tsx

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,46 @@ import { fr } from "./fr";
77
import { cx } from "./tools/cx";
88
import type { FrIconClassName, RiIconClassName } from "./fr/generatedFromCss/classNames";
99

10-
export type InputProps = InputProps.Input | InputProps.TextArea;
10+
export type InputProps = {
11+
className?: string;
12+
label: ReactNode;
13+
hintText?: ReactNode;
14+
/** default: false */
15+
disabled?: boolean;
16+
iconId?: FrIconClassName | RiIconClassName;
17+
classes?: Partial<
18+
Record<"root" | "label" | "description" | "nativeInputOrTextArea" | "message", string>
19+
>;
20+
} & (InputProps.WithSpecialState | InputProps.WithoutSpecialState) &
21+
(InputProps.WithoutTextArea | InputProps.WithTextArea);
1122

1223
export namespace InputProps {
13-
export type Common = {
14-
className?: string;
15-
label: ReactNode;
16-
hintText?: ReactNode;
17-
/** default: false */
18-
disabled?: boolean;
19-
message?: {
20-
type: "success" | "error";
21-
text: ReactNode;
22-
};
23-
iconId?: FrIconClassName | RiIconClassName;
24-
classes?: Partial<
25-
Record<"root" | "label" | "description" | "nativeInputOrTextArea" | "message", string>
26-
>;
24+
export type WithSpecialState = {
25+
state: "success" | "error";
26+
stateRelatedMessage: ReactNode;
2727
};
2828

29-
export type Input = Common & {
29+
export type WithoutSpecialState = {
30+
state?: never;
31+
stateRelatedMessage?: never;
32+
};
33+
34+
export type WithoutTextArea = {
3035
/** Default: false */
3136
isTextArea?: false;
3237
/** Props forwarded to the underlying <input /> element */
3338
nativeInputProps?: InputHTMLAttributes<HTMLInputElement>;
39+
3440
nativeTextAreaProps?: never;
3541
};
3642

37-
export type TextArea = Common & {
43+
export type WithTextArea = {
3844
/** Default: false */
3945
isTextArea: true;
40-
nativeInputProps?: never;
4146
/** Props forwarded to the underlying <textarea /> element */
4247
nativeTextAreaProps?: TextareaHTMLAttributes<HTMLTextAreaElement>;
48+
49+
nativeInputProps?: never;
4350
};
4451
}
4552

@@ -53,16 +60,18 @@ export const Input = memo(
5360
label,
5461
hintText,
5562
disabled = false,
56-
message,
5763
iconId: iconId_props,
58-
isTextArea = false,
59-
nativeInputProps = {},
60-
nativeTextAreaProps = {},
6164
classes = {},
65+
state,
66+
stateRelatedMessage,
67+
isTextArea = false,
68+
nativeTextAreaProps,
69+
nativeInputProps,
6270
...rest
6371
} = props;
6472

65-
const nativeInputOrTextAreaProps = isTextArea ? nativeTextAreaProps : nativeInputProps;
73+
const nativeInputOrTextAreaProps =
74+
(isTextArea ? nativeTextAreaProps : nativeInputProps) ?? {};
6675

6776
const NativeInputOrTexArea = isTextArea ? "textarea" : "input";
6877

@@ -82,15 +91,15 @@ export const Input = memo(
8291
fr.cx(
8392
"fr-input-group",
8493
disabled && "fr-input-group--disabled",
85-
message !== undefined &&
94+
state !== undefined &&
8695
(() => {
87-
switch (message.type) {
96+
switch (state) {
8897
case "error":
8998
return "fr-input-group--error";
9099
case "success":
91100
return "fr-input-group--valid";
92101
}
93-
assert<Equals<typeof message.type, never>>(false);
102+
assert<Equals<typeof state, never>>(false);
94103
})()
95104
),
96105
classes.root,
@@ -110,31 +119,29 @@ export const Input = memo(
110119
className={cx(
111120
fr.cx(
112121
"fr-input",
113-
message !== undefined &&
122+
state !== undefined &&
114123
(() => {
115-
switch (message.type) {
124+
switch (state) {
116125
case "error":
117126
return "fr-input--error";
118127
case "success":
119128
return "fr-input--valid";
120129
}
121-
assert<Equals<typeof message.type, never>>(false);
130+
assert<Equals<typeof state, never>>(false);
122131
})()
123132
),
124133
classes.nativeInputOrTextArea
125134
)}
126135
disabled={disabled || undefined}
127136
aria-describedby={messageId}
128-
type={isTextArea ? undefined : nativeInputProps.type ?? "text"}
137+
type={nativeInputProps?.type ?? "text"}
129138
id={inputId}
130139
/>
131140
);
132141

133142
const iconId =
134143
iconId_props ??
135-
(!isTextArea && nativeInputProps.type === "date"
136-
? "ri-calendar-line"
137-
: undefined);
144+
(nativeInputProps?.type === "date" ? "ri-calendar-line" : undefined);
138145

139146
return iconId === undefined ? (
140147
nativeInputOrTextArea
@@ -144,9 +151,25 @@ export const Input = memo(
144151
</div>
145152
);
146153
})()}
147-
{message !== undefined && (
148-
<p id={messageId} className={cx(fr.cx("fr-error-text"), classes.message)}>
149-
{message.text}
154+
{stateRelatedMessage !== undefined && (
155+
<p
156+
id={messageId}
157+
className={cx(
158+
fr.cx(
159+
(() => {
160+
switch (state) {
161+
case "error":
162+
return "fr-error-text";
163+
case "success":
164+
return "fr-valid-text";
165+
}
166+
assert<Equals<typeof state, never>>(false);
167+
})()
168+
),
169+
classes.message
170+
)}
171+
>
172+
{stateRelatedMessage}
150173
</p>
151174
)}
152175
</div>

stories/Input.stories.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,14 @@ export const Default = getStory({
1919

2020
export const WithErrorMessage = getStory({
2121
"label": "Label champs de saisie",
22-
"message": {
23-
"type": "error",
24-
"text": "Texte d’erreur obligatoire"
25-
}
22+
"state": "error",
23+
"stateRelatedMessage": "Texte d’erreur obligatoire"
2624
});
2725

2826
export const WithSuccessMessage = getStory({
2927
"label": "Label champs de saisie",
30-
"message": {
31-
"type": "success",
32-
"text": "Texte de validation"
33-
}
28+
"state": "success",
29+
"stateRelatedMessage": "Texte de validation"
3430
});
3531

3632
export const Disabled = getStory({

0 commit comments

Comments
 (0)