Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
474 changes: 474 additions & 0 deletions src/Follow.tsx

Large diffs are not rendered by default.

22 changes: 15 additions & 7 deletions src/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export namespace InputProps {
state?: "success" | "error" | "default";
/** The message won't be displayed if state is "default" */
stateRelatedMessage?: ReactNode;
addon?: ReactNode;
};

export type RegularInput = Common & {
Expand Down Expand Up @@ -82,6 +83,7 @@ export const Input = memo(
textArea = false,
nativeTextAreaProps,
nativeInputProps,
addon,
...rest
} = props;

Expand Down Expand Up @@ -115,7 +117,6 @@ export const Input = memo(
case "default":
return undefined;
}
assert<Equals<typeof state, never>>(false);
})()
),
classes.root,
Expand Down Expand Up @@ -149,7 +150,6 @@ export const Input = memo(
case "default":
return undefined;
}
assert<Equals<typeof state, never>>(false);
})()
),
classes.nativeInputOrTextArea
Expand All @@ -161,12 +161,21 @@ export const Input = memo(
/>
);

return iconId === undefined ? (
nativeInputOrTextArea
) : (
<div className={fr.cx("fr-input-wrap", iconId)}>
const hasIcon = iconId !== undefined;
const hasAddon = addon !== undefined;
return hasIcon || hasAddon ? (
<div
className={fr.cx(
"fr-input-wrap",
hasIcon && iconId,
hasAddon && "fr-input-wrap--addon"
)}
>
{nativeInputOrTextArea}
{hasAddon && addon}
</div>
) : (
nativeInputOrTextArea
);
})()}
{state !== "default" && (
Expand All @@ -181,7 +190,6 @@ export const Input = memo(
case "success":
return "fr-valid-text";
}
assert<Equals<typeof state, never>>(false);
})()
),
classes.message
Expand Down
2 changes: 1 addition & 1 deletion src/blocks/PasswordInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { useAnalyticsId } from "../tools/useAnalyticsId";

export type PasswordInputProps = Omit<
InputProps.Common,
"state" | "stateRelatedMessage" | "iconId" | "classes"
"state" | "stateRelatedMessage" | "iconId" | "classes" | "addon"
> & {
classes?: Partial<Record<"root" | "input" | "label" | "checkbox", string>>;
/** Default "Your password must contain:", if empty string the hint wont be displayed */
Expand Down
2 changes: 1 addition & 1 deletion stories/ButtonsGroup.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const { meta, getStory } = getStoryFactory({
"control": { "type": "select" }
},
"buttons": {
"description": `An array of ButtonProps (at least 2, RGAA)`,
"description": `An array of ButtonProps (at least 1)`,
"control": { "type": null }
}
},
Expand Down
186 changes: 186 additions & 0 deletions stories/Follow.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { Follow, type FollowProps } from "../dist/Follow";
import { sectionName } from "./sectionName";
import { getStoryFactory } from "./getStory";
import { action } from "@storybook/addon-actions";
import React from "react";

const { meta, getStory } = getStoryFactory<FollowProps>({
sectionName,
wrappedComponent: { Follow },
description: `
- [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/lettre-d-information-et-reseaux-sociaux)
- [See DSFR demos](https://main--ds-gouv.netlify.app/example/component/follow/)
- [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/Follow.tsx)`,
argTypes: {
classes: {
control: { type: null },
description:
'Add custom classes for various inner elements. Possible keys are "root", "container", "row", "newsletter-col", "newsletter", "newsletter-title", "newsletter-desc", "newsletter-form-wrapper", "newsletter-form-hint", "social-col", "social", "social-title", "social-buttons", "social-buttons-each"'
}
},
disabledProps: ["lang"]
});

export default meta;

const defaultSocialButtons: [FollowProps.SocialButton, ...FollowProps.SocialButton[]] = [
{
type: "facebook",
linkProps: {
href: "#facebook"
}
},
{
type: "twitter-x",
linkProps: {
href: "#twitter"
}
},
{
type: "linkedin",
linkProps: {
href: "#linkedin"
}
},
{
type: "instagram",
linkProps: {
href: "#instagram"
}
},
{
type: "youtube",
linkProps: {
href: "#youtube"
}
}
];

export const Default = getStory({
newsletter: {
buttonProps: {
onClick: action("Default onClick")
},
form: {
formComponent: ({ children }) => <form action="#">{children}</form>,
inputProps: {
label: undefined
},
success: false
}
},
social: {
buttons: defaultSocialButtons
}
});

export const SocialOnly = getStory({
social: {
buttons: defaultSocialButtons
}
});

export const NewsletterOnly = getStory({
newsletter: {
buttonProps: {
onClick: action("NewsletterOnly onClick")
}
}
});

export const NewsletterOnlyButtonAsLink = getStory({
newsletter: {
buttonProps: {
linkProps: {
href: "#"
}
}
}
});

export const NewsletterOnlyWithDescription = getStory({
newsletter: {
buttonProps: {
onClick: action("NewsletterOnlyWithDescription onClick")
},
desc: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas varius tortor nibh, sit amet tempor nibh finibus et."
}
});

export const NewsletterOnlyWithForm = getStory({
newsletter: {
buttonProps: {
onClick: action("NewsletterOnlyWithForm onClick")
},
desc: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas varius tortor nibh, sit amet tempor nibh finibus et.",
form: {
formComponent: ({ children }) => <form action="#">{children}</form>,
success: false
}
}
});

export const SocialAndNewsletter = getStory({
newsletter: {
buttonProps: {
onClick: action("SocialAndNewsletter onClick")
},
desc: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas varius tortor nibh, sit amet tempor nibh finibus et."
},
social: {
buttons: defaultSocialButtons
}
});

export const SocialAndNewsletterWithForm = getStory({
newsletter: {
buttonProps: {
onClick: action("SocialAndNewsletterWithForm onClick")
},
desc: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas varius tortor nibh, sit amet tempor nibh finibus et.",
form: {
formComponent: ({ children }) => <form action="#">{children}</form>,
success: false
}
},
social: {
buttons: defaultSocialButtons
}
});

export const SocialAndNewsletterWithFormSuccess = getStory({
newsletter: {
buttonProps: {
onClick: action("SocialAndNewsletterWithFormSuccess onClick")
},
desc: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas varius tortor nibh, sit amet tempor nibh finibus et.",
form: {
formComponent: ({ children }) => <form action="#">{children}</form>,
success: true
}
},
social: {
buttons: defaultSocialButtons
}
});

export const SocialAndNewsletterWithFormError = getStory({
newsletter: {
buttonProps: {
onClick: action("SocialAndNewsletterWithFormError onClick")
},
desc: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas varius tortor nibh, sit amet tempor nibh finibus et.",
form: {
formComponent: ({ children }) => <form action="#">{children}</form>,
success: false,
inputProps: {
state: "error",
stateRelatedMessage:
"Le format de l’adresse electronique saisie n’est pas valide. Le format attendu est : nom@exemple.org"
}
}
},
social: {
buttons: defaultSocialButtons
}
});
7 changes: 7 additions & 0 deletions stories/Input.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { sectionName } from "./sectionName";
import { getStoryFactory } from "./getStory";
import { assert } from "tsafe/assert";
import type { Equals } from "tsafe";
import Button from "../dist/Button";
import React from "react";

const { meta, getStory } = getStoryFactory({
sectionName,
Expand Down Expand Up @@ -131,3 +133,8 @@ export const WithPlaceholder = getStory({
"placeholder": "https://"
}
});

export const WithButtonAddon = getStory({
"label": "Label champs de saisie",
"addon": <Button>Valider</Button>
});
54 changes: 54 additions & 0 deletions test/integration/next-appdir/app/Follow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"use client";

import { Follow as BaseFollow } from "@codegouvfr/react-dsfr/Follow";
import { useState } from 'react'

export const Follow = () => {
const [success, setSuccess] = useState(false)
return <BaseFollow
newsletter={{
desc: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas varius tortor nibh, sit amet tempor nibh finibus et.",
buttonProps: {
onClick: () => setSuccess(true)
},
form: {
formComponent: ({ children }) => <form action="#">{children}</form>,
success,
}
}}
social= {{
buttons: [
{
type: "facebook",
linkProps: {
href: "#facebook"
}
},
{
type: "twitter-x",
linkProps: {
href: "#twitter"
}
},
{
type: "linkedin",
linkProps: {
href: "#linkedin"
}
},
{
type: "instagram",
linkProps: {
href: "#instagram"
}
},
{
type: "youtube",
linkProps: {
href: "#youtube"
}
}
]
}}
/>
}
2 changes: 2 additions & 0 deletions test/integration/next-appdir/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { headers } from "next/headers";
import { getScriptNonceFromHeader } from "next/dist/server/app-render/get-script-nonce-from-header"; // or use your own implementation
import style from "./main.module.css";
import { cx } from '@codegouvfr/react-dsfr/tools/cx';
import { Follow } from './Follow';


export default function RootLayout({ children }: { children: JSX.Element; }) {
Expand Down Expand Up @@ -81,6 +82,7 @@ export default function RootLayout({ children }: { children: JSX.Element; }) {
<div className={cx(style.container)}>
{children}
</div>
<Follow />
<Footer
accessibility="fully compliant"
contentDescription={`
Expand Down