@pnx-mixtape/mxds 0.1.0-rc.4 → 0.1.0-rc.5
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/.claude/skills/create-mxds-component/SKILL.md +77 -0
- package/DESIGN.md +595 -0
- package/README.md +1 -2
- package/dist/build/accordion.entry.js +2 -1
- package/dist/build/autocomplete.entry.js +1 -1
- package/dist/build/back-to-top.css +1 -0
- package/dist/build/back-to-top.entry.js +1 -0
- package/dist/build/button.css +1 -1
- package/dist/build/card.css +1 -1
- package/dist/build/chunks/Accordion-D2wp8hBG.js +44 -0
- package/dist/build/chunks/AccordionGroup-BYtoAFxg.js +47 -0
- package/dist/build/chunks/AccordionMobile-D3sTeiM5.js +26 -0
- package/dist/build/chunks/Autocomplete-kRo8RNbC.js +135 -0
- package/dist/build/chunks/BackToTop-4M-Wl7z3.js +64 -0
- package/dist/build/chunks/ClosableAlert-yy84gae6.js +55 -0
- package/dist/build/chunks/CookieConsent-wd8gQ2Oe.js +18 -0
- package/dist/build/chunks/Dialog-DWoXW-7Z.js +48 -0
- package/dist/build/chunks/DropMenu-CSTZq0Zt.js +68 -0
- package/dist/build/chunks/FeedbackForm-BochXEDx.js +64 -0
- package/dist/build/chunks/Gallery-Rkfv7Mbb.js +165 -0
- package/dist/build/chunks/GlobalToggle-CzlkpLpc.js +82 -0
- package/dist/build/chunks/InPageNavigation-D1Kk-rCm.js +86 -0
- package/dist/build/chunks/Navigation-CDQnN61h.js +147 -0
- package/dist/build/chunks/Popover-CiTAftNU.js +30 -0
- package/dist/build/chunks/Sticky-DoVndENm.js +51 -0
- package/dist/build/chunks/Tabs-BDlxCDNh.js +160 -0
- package/dist/build/chunks/Translate-bmM38j6z.js +46 -0
- package/dist/build/chunks/cookie-compliance-CG6x-TcV.js +119 -0
- package/dist/build/chunks/enums-BrHxPZZ_.js +36 -0
- package/dist/build/chunks/polyfills-D-qh1a_2.js +33 -0
- package/dist/build/chunks/utilities-Dd-08SW6.js +255 -0
- package/dist/build/content-block.css +1 -1
- package/dist/build/cookie-consent.entry.js +1 -1
- package/dist/build/dialog.entry.js +1 -1
- package/dist/build/drop-menu.entry.js +1 -1
- package/dist/build/elements.js +147 -1
- package/dist/build/feedback-form.css +1 -0
- package/dist/build/feedback-form.entry.js +1 -0
- package/dist/build/filters.entry.js +145 -1
- package/dist/build/gallery.entry.js +1 -1
- package/dist/build/global-alert.entry.js +1 -1
- package/dist/build/header.css +1 -1
- package/dist/build/header.entry.js +1 -1
- package/dist/build/icon.css +1 -1
- package/dist/build/in-page-navigation.entry.js +1 -1
- package/dist/build/navigation.entry.js +1 -1
- package/dist/build/pagination.css +1 -1
- package/dist/build/popover.entry.js +1 -1
- package/dist/build/react.js +2270 -1
- package/dist/build/sticky.entry.js +1 -1
- package/dist/build/tabs.entry.js +1 -1
- package/dist/build/translate.css +1 -0
- package/dist/build/translate.entry.js +1 -0
- package/dist/build/utility-list.entry.js +53 -1
- package/dist/types/Component/BackToTop/BackToTop.d.ts +20 -0
- package/dist/types/Component/BackToTop/BackToTop.d.ts.map +1 -0
- package/dist/types/Component/BackToTop/Elements/BackToTop.d.ts +27 -0
- package/dist/types/Component/BackToTop/Elements/BackToTop.d.ts.map +1 -0
- package/dist/types/Component/BackToTop/back-to-top.entry.d.ts +2 -0
- package/dist/types/Component/BackToTop/back-to-top.entry.d.ts.map +1 -0
- package/dist/types/Component/DropMenu/DropMenu.d.ts +2 -1
- package/dist/types/Component/DropMenu/DropMenu.d.ts.map +1 -1
- package/dist/types/Component/Tabs/Components/TabDropMenu.d.ts.map +1 -1
- package/dist/types/Component/Tabs/Elements/Tabs.d.ts.map +1 -1
- package/dist/types/Component/Translate/Elements/Translate.d.ts +23 -0
- package/dist/types/Component/Translate/Elements/Translate.d.ts.map +1 -0
- package/dist/types/Component/Translate/Translate.d.ts +37 -0
- package/dist/types/Component/Translate/Translate.d.ts.map +1 -0
- package/dist/types/Component/Translate/languages.d.ts +11 -0
- package/dist/types/Component/Translate/languages.d.ts.map +1 -0
- package/dist/types/Component/Translate/translate.entry.d.ts +2 -0
- package/dist/types/Component/Translate/translate.entry.d.ts.map +1 -0
- package/dist/types/Form/FeedbackForm/Elements/FeedbackForm.d.ts +37 -0
- package/dist/types/Form/FeedbackForm/Elements/FeedbackForm.d.ts.map +1 -0
- package/dist/types/Form/FeedbackForm/FeedbackForm.d.ts +36 -0
- package/dist/types/Form/FeedbackForm/FeedbackForm.d.ts.map +1 -0
- package/dist/types/Form/FeedbackForm/feedback-form.entry.d.ts +2 -0
- package/dist/types/Form/FeedbackForm/feedback-form.entry.d.ts.map +1 -0
- package/dist/types/Utility/Elements/google-translate-widget.d.ts +91 -0
- package/dist/types/Utility/Elements/google-translate-widget.d.ts.map +1 -0
- package/dist/types/Utility/utilities.d.ts +2 -0
- package/dist/types/Utility/utilities.d.ts.map +1 -1
- package/dist/types/elements.d.ts +3 -0
- package/dist/types/elements.d.ts.map +1 -1
- package/dist/types/enums.d.ts +3 -1
- package/dist/types/enums.d.ts.map +1 -1
- package/dist/types/react.d.ts +3 -0
- package/dist/types/react.d.ts.map +1 -1
- package/docs/API.md +1215 -0
- package/docs/CSSGuidelines.md +891 -0
- package/docs/CodeQuality.md +940 -0
- package/docs/ComponentArchitecture.md +714 -0
- package/docs/Contributing.md +626 -0
- package/docs/MemoryManagement.md +598 -0
- package/docs/Migrations.md +6 -0
- package/docs/README.md +100 -0
- package/docs/Security.md +444 -0
- package/docs/Testing.md +739 -0
- package/docs/Troubleshooting.md +674 -0
- package/package.json +6 -3
- package/src/Atom/Button/Button.tsx +1 -1
- package/src/Atom/Button/__snapshots__/Button.stories.tsx.snap +2 -2
- package/src/Atom/Button/_buttons.css +1 -0
- package/src/Atom/Icon/README.md +1 -1
- package/src/Atom/Icon/_icon.css +1 -0
- package/src/Atom/Icon/images/thumb.svg +1 -0
- package/src/Atom/Media/__snapshots__/Media.stories.ts.snap +4 -9
- package/src/Atom/Media/media.twig +4 -5
- package/src/Component/BackToTop/BackToTop.stories.ts +127 -0
- package/src/Component/BackToTop/BackToTop.stories.tsx +53 -0
- package/src/Component/BackToTop/BackToTop.tsx +120 -0
- package/src/Component/BackToTop/Elements/BackToTop.ts +119 -0
- package/src/Component/BackToTop/README.md +164 -0
- package/src/Component/BackToTop/__snapshots__/BackToTop.stories.ts.snap +24 -0
- package/src/Component/BackToTop/__snapshots__/BackToTop.stories.tsx.snap +3 -0
- package/src/Component/BackToTop/back-to-top.css +70 -0
- package/src/Component/BackToTop/back-to-top.entry.js +1 -0
- package/src/Component/BackToTop/back-to-top.twig +26 -0
- package/src/Component/Card/Components/CardContent.tsx +1 -1
- package/src/Component/Card/Components/CardMedia.tsx +1 -1
- package/src/Component/Card/__snapshots__/Card.stories.tsx.snap +3 -3
- package/src/Component/Card/card.css +1 -1
- package/src/Component/Carousel/Carousel.stories.ts +66 -36
- package/src/Component/Carousel/__snapshots__/Carousel.stories.tsx.snap +5 -5
- package/src/Component/ContentBlock/Components/ContentBlockMedia.tsx +1 -1
- package/src/Component/ContentBlock/content-block.css +1 -1
- package/src/Component/DropMenu/DropMenu.stories.ts +21 -2
- package/src/Component/DropMenu/DropMenu.tsx +14 -4
- package/src/Component/DropMenu/__snapshots__/DropMenu.stories.ts.snap +3 -3
- package/src/Component/DropMenu/__snapshots__/DropMenu.stories.tsx.snap +2 -2
- package/src/Component/DropMenu/drop-menu.twig +7 -1
- package/src/Component/HeroBanner/__snapshots__/HeroBanner.stories.tsx.snap +1 -1
- package/src/Component/InPageNavigation/__snapshots__/InPageNavigation.stories.ts.snap +48 -13
- package/src/Component/InPageNavigation/in-page-navigation.twig +4 -6
- package/src/Component/ListItem/Components/ListItemMedia.tsx +1 -1
- package/src/Component/Pagination/Pagination.stories.ts +57 -27
- package/src/Component/Pagination/pagination.css +16 -7
- package/src/Component/Popover/__snapshots__/Popover.stories.tsx.snap +3 -3
- package/src/Component/Tabs/Components/TabDropMenu.tsx +7 -1
- package/src/Component/Tabs/Elements/Tabs.ts +13 -3
- package/src/Component/Tabs/Tabs.stories.ts +33 -12
- package/src/Component/Tabs/Tabs.stories.tsx +28 -14
- package/src/Component/Tabs/__snapshots__/Tabs.stories.ts.snap +3 -3
- package/src/Component/Tabs/__snapshots__/Tabs.stories.tsx.snap +2 -2
- package/src/Component/Translate/Elements/Translate.ts +76 -0
- package/src/Component/Translate/README.md +283 -0
- package/src/Component/Translate/Translate.stories.ts +87 -0
- package/src/Component/Translate/Translate.stories.tsx +77 -0
- package/src/Component/Translate/Translate.tsx +213 -0
- package/src/Component/Translate/__snapshots__/Translate.stories.ts.snap +18 -0
- package/src/Component/Translate/__snapshots__/Translate.stories.tsx.snap +3 -0
- package/src/Component/Translate/languages.ts +39 -0
- package/src/Component/Translate/translate.css +62 -0
- package/src/Component/Translate/translate.entry.js +1 -0
- package/src/Component/Translate/translate.twig +44 -0
- package/src/Form/Autocomplete/Autocomplete.stories.tsx +2 -1
- package/src/Form/FeedbackForm/Elements/FeedbackForm.ts +122 -0
- package/src/Form/FeedbackForm/FeedbackForm.stories.ts +99 -0
- package/src/Form/FeedbackForm/FeedbackForm.stories.tsx +73 -0
- package/src/Form/FeedbackForm/FeedbackForm.tsx +196 -0
- package/src/Form/FeedbackForm/README.md +233 -0
- package/src/Form/FeedbackForm/__snapshots__/FeedbackForm.stories.ts.snap +125 -0
- package/src/Form/FeedbackForm/__snapshots__/FeedbackForm.stories.tsx.snap +5 -0
- package/src/Form/FeedbackForm/feedback-form.css +74 -0
- package/src/Form/FeedbackForm/feedback-form.entry.js +1 -0
- package/src/Form/FeedbackForm/feedback-form.twig +80 -0
- package/src/Layout/Header/Components/GlobalToggle.tsx +1 -1
- package/src/Layout/Header/Header.stories.ts +53 -48
- package/src/Layout/Header/Header.stories.tsx +35 -22
- package/src/Layout/Header/Header.tsx +2 -2
- package/src/Layout/Header/__snapshots__/Header.stories.ts.snap +14 -10
- package/src/Layout/Header/__snapshots__/Header.stories.tsx.snap +2 -2
- package/src/Layout/Header/_toggles.css +7 -7
- package/src/Layout/Page/__snapshots__/Page.stories.tsx.snap +1 -1
- package/src/Utility/Elements/google-translate-widget.ts +293 -0
- package/src/Utility/global.d.ts +4 -0
- package/src/Utility/utilities.ts +2 -0
- package/src/elements.ts +3 -0
- package/src/enums.ts +2 -0
- package/src/react.ts +3 -0
- package/dist/build/chunks/Accordion-GN0sME4W.js +0 -1
- package/dist/build/chunks/AccordionGroup-Bzqp5__q.js +0 -1
- package/dist/build/chunks/AccordionMobile-DG5jSe8Q.js +0 -1
- package/dist/build/chunks/Autocomplete-B4RtkAyh.js +0 -1
- package/dist/build/chunks/ClosableAlert-DEhj8Nxm.js +0 -1
- package/dist/build/chunks/CookieConsent-BkCMRVST.js +0 -1
- package/dist/build/chunks/Dialog-Haahn-Yn.js +0 -1
- package/dist/build/chunks/DropMenu-1baQc8Yc.js +0 -1
- package/dist/build/chunks/Gallery-D8NGXy2Q.js +0 -1
- package/dist/build/chunks/GlobalToggle-B52fdBQu.js +0 -1
- package/dist/build/chunks/InPageNavigation-Dyn2TR-N.js +0 -1
- package/dist/build/chunks/Navigation-hOrfLq7h.js +0 -1
- package/dist/build/chunks/Popover-Cxg-s5Jn.js +0 -1
- package/dist/build/chunks/Sticky-BGCuuEJK.js +0 -1
- package/dist/build/chunks/Tabs-DYy1YTdU.js +0 -15
- package/dist/build/chunks/cookie-compliance-Bbm-0vpj.js +0 -1
- package/dist/build/chunks/enums-DLV_1NAf.js +0 -1
- package/dist/build/chunks/polyfills-C7pRt54M.js +0 -1
- package/dist/build/chunks/utilities-_Bq3TxAQ.js +0 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react-vite"
|
|
2
|
+
import { expect, fn, waitFor } from "storybook/test"
|
|
3
|
+
import Component from "./FeedbackForm"
|
|
4
|
+
import "./feedback-form.css"
|
|
5
|
+
import { Page } from "../../../.storybook-react/decorators"
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof Component> = {
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
component: Component,
|
|
10
|
+
decorators: [Page],
|
|
11
|
+
args: {
|
|
12
|
+
id: "feedback",
|
|
13
|
+
secondaryText: "If you need a response, ",
|
|
14
|
+
secondaryLinkText: "contact us",
|
|
15
|
+
secondaryLinkHref: "#contact",
|
|
16
|
+
onSubmit: fn(),
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default meta
|
|
21
|
+
|
|
22
|
+
export const FeedbackForm: StoryObj<typeof meta> = {
|
|
23
|
+
play: async ({ canvasElement, canvas, userEvent, step, args }) => {
|
|
24
|
+
const wrapper = canvasElement.querySelector(".mx-feedback-form") as HTMLElement
|
|
25
|
+
const comment = canvas.getByLabelText("How can we make it better?") as HTMLTextAreaElement
|
|
26
|
+
|
|
27
|
+
await step("Comment field is hidden until a rating is chosen", async () => {
|
|
28
|
+
await expect(comment).not.toBeVisible()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
await step("Choosing a rating reveals and focuses the comment field", async () => {
|
|
32
|
+
await userEvent.click(canvas.getByRole("radio", { name: "Yes" }))
|
|
33
|
+
await waitFor(() => expect(comment).toBeVisible())
|
|
34
|
+
await expect(comment).toHaveFocus()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
await step("Submitting calls onSubmit with the chosen rating and comment", async () => {
|
|
38
|
+
await userEvent.type(comment, "More examples would help.")
|
|
39
|
+
await userEvent.click(canvas.getByRole("button", { name: "Submit" }))
|
|
40
|
+
await waitFor(() =>
|
|
41
|
+
expect(args.onSubmit).toHaveBeenCalledWith(
|
|
42
|
+
expect.objectContaining({ rating: "yes", comment: "More examples would help." }),
|
|
43
|
+
),
|
|
44
|
+
)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
await expect(wrapper).toBeInTheDocument()
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** With `commentRequired`, an empty comment blocks submission and shows an inline error. */
|
|
52
|
+
export const RequiredComment: StoryObj<typeof meta> = {
|
|
53
|
+
args: {
|
|
54
|
+
id: "feedback-required",
|
|
55
|
+
commentRequired: true,
|
|
56
|
+
onSubmit: fn(),
|
|
57
|
+
},
|
|
58
|
+
play: async ({ canvas, userEvent, step, args }) => {
|
|
59
|
+
await step("Reveal the comment field", async () => {
|
|
60
|
+
await userEvent.click(canvas.getByRole("radio", { name: "No" }))
|
|
61
|
+
await waitFor(() => expect(canvas.getByLabelText("How can we make it better?")).toBeVisible())
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
await step(
|
|
65
|
+
"Submitting an empty required comment shows the error and blocks onSubmit",
|
|
66
|
+
async () => {
|
|
67
|
+
await userEvent.click(canvas.getByRole("button", { name: "Submit" }))
|
|
68
|
+
await waitFor(() => expect(canvas.getByRole("alert")).toBeVisible())
|
|
69
|
+
await expect(args.onSubmit).not.toHaveBeenCalled()
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
},
|
|
73
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { JSX, FormEventHandler, ChangeEventHandler, useId, useRef, useState } from "react"
|
|
2
|
+
import classNames from "classnames"
|
|
3
|
+
import { ButtonModifiers, IconRotateModifier } from "@pnx-mixtape/ids-shape"
|
|
4
|
+
import Button from "../../Atom/Button/Button"
|
|
5
|
+
import Icon from "../../Atom/Icon/Icon"
|
|
6
|
+
import Link from "../../Atom/Link/Link"
|
|
7
|
+
import FormItem from "../FormItem/FormItem"
|
|
8
|
+
import FormLabel from "../Label/FormLabel"
|
|
9
|
+
import { Icons } from "../../enums"
|
|
10
|
+
import { validators } from "../../Utility/utilities"
|
|
11
|
+
|
|
12
|
+
const SUBMIT_MODIFIERS = [ButtonModifiers.DARK]
|
|
13
|
+
|
|
14
|
+
export type FeedbackFormSubmitDetail = {
|
|
15
|
+
rating: string | null
|
|
16
|
+
comment: string
|
|
17
|
+
form: HTMLFormElement
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type FeedbackFormProps = {
|
|
21
|
+
id?: string
|
|
22
|
+
name?: string
|
|
23
|
+
question?: string
|
|
24
|
+
yesLabel?: string
|
|
25
|
+
noLabel?: string
|
|
26
|
+
/** Optional secondary copy, e.g. pointing readers to an alternative contact channel. */
|
|
27
|
+
secondaryText?: string
|
|
28
|
+
secondaryLinkText?: string
|
|
29
|
+
secondaryLinkHref?: string
|
|
30
|
+
commentLabel?: string
|
|
31
|
+
commentName?: string
|
|
32
|
+
commentPlaceholder?: string
|
|
33
|
+
commentRequired?: boolean
|
|
34
|
+
requiredErrorMessage?: string
|
|
35
|
+
submitLabel?: string
|
|
36
|
+
/** Progressive-enhancement form target — the consumer's `onSubmit` can still take over. */
|
|
37
|
+
action?: string
|
|
38
|
+
method?: string
|
|
39
|
+
/** Consumer-supplied top-level notice, e.g. after a failed async submit. */
|
|
40
|
+
errorMessage?: string
|
|
41
|
+
/** Reflected as `aria-busy`/`disabled` on the submit button while the consumer's own submit handling is in flight. */
|
|
42
|
+
pending?: boolean
|
|
43
|
+
className?: string
|
|
44
|
+
/** Called with the form's values on a valid submit — this component never fetches/submits over the network itself. */
|
|
45
|
+
onSubmit?: (detail: FeedbackFormSubmitDetail) => void
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const FeedbackForm = ({
|
|
49
|
+
id,
|
|
50
|
+
name = "rating",
|
|
51
|
+
question = "Was the information on this page useful?",
|
|
52
|
+
yesLabel = "Yes",
|
|
53
|
+
noLabel = "No",
|
|
54
|
+
secondaryText,
|
|
55
|
+
secondaryLinkText,
|
|
56
|
+
secondaryLinkHref,
|
|
57
|
+
commentLabel = "How can we make it better?",
|
|
58
|
+
commentName = "comment",
|
|
59
|
+
commentPlaceholder,
|
|
60
|
+
commentRequired = false,
|
|
61
|
+
requiredErrorMessage = "This field is required.",
|
|
62
|
+
submitLabel = "Submit",
|
|
63
|
+
action,
|
|
64
|
+
method = "post",
|
|
65
|
+
errorMessage,
|
|
66
|
+
pending = false,
|
|
67
|
+
className,
|
|
68
|
+
onSubmit,
|
|
69
|
+
}: FeedbackFormProps): JSX.Element => {
|
|
70
|
+
const generatedId = useId()
|
|
71
|
+
const rootId = id ?? generatedId
|
|
72
|
+
const commentId = `${rootId}-comment`
|
|
73
|
+
const commentRef = useRef<HTMLTextAreaElement>(null)
|
|
74
|
+
const revealedRef = useRef(false)
|
|
75
|
+
const [showError, setShowError] = useState(false)
|
|
76
|
+
|
|
77
|
+
const handleRatingChange: ChangeEventHandler<HTMLInputElement> = () => {
|
|
78
|
+
if (revealedRef.current) return
|
|
79
|
+
revealedRef.current = true
|
|
80
|
+
commentRef.current?.focus()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const handleSubmit: FormEventHandler<HTMLFormElement> = event => {
|
|
84
|
+
const comment = commentRef.current
|
|
85
|
+
|
|
86
|
+
if (commentRequired && comment && !validators.notEmpty(comment.value)) {
|
|
87
|
+
event.preventDefault()
|
|
88
|
+
setShowError(true)
|
|
89
|
+
comment.focus()
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
setShowError(false)
|
|
94
|
+
if (!onSubmit) return
|
|
95
|
+
// Hand full control to the consumer — this component never submits over
|
|
96
|
+
// the network itself, so the native POST must not also fire.
|
|
97
|
+
event.preventDefault()
|
|
98
|
+
const rating = new FormData(event.currentTarget).get(name) as string | null
|
|
99
|
+
onSubmit({ rating, comment: comment?.value ?? "", form: event.currentTarget })
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div className={classNames("mx-feedback-form", "is-enhanced", className)}>
|
|
104
|
+
<form action={action} method={method} noValidate onSubmit={handleSubmit}>
|
|
105
|
+
{errorMessage && (
|
|
106
|
+
<div className="mx-feedback-form__error" role="alert">
|
|
107
|
+
{errorMessage}
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
110
|
+
|
|
111
|
+
<fieldset className="mx-fieldset mx-fieldset--plain mx-feedback-form__rating">
|
|
112
|
+
<legend className="mx-legend">{question}</legend>
|
|
113
|
+
<div className="mx-feedback-form__option">
|
|
114
|
+
<input
|
|
115
|
+
type="radio"
|
|
116
|
+
className="sr-only"
|
|
117
|
+
id={`${rootId}-yes`}
|
|
118
|
+
name={name}
|
|
119
|
+
value="yes"
|
|
120
|
+
onChange={handleRatingChange}
|
|
121
|
+
/>
|
|
122
|
+
<label
|
|
123
|
+
className="mx-button mx-button--dark mx-button--outline"
|
|
124
|
+
htmlFor={`${rootId}-yes`}
|
|
125
|
+
>
|
|
126
|
+
<Icon icon={Icons.THUMB} />
|
|
127
|
+
<span>{yesLabel}</span>
|
|
128
|
+
</label>
|
|
129
|
+
</div>
|
|
130
|
+
<div className="mx-feedback-form__option">
|
|
131
|
+
<input
|
|
132
|
+
type="radio"
|
|
133
|
+
className="sr-only"
|
|
134
|
+
id={`${rootId}-no`}
|
|
135
|
+
name={name}
|
|
136
|
+
value="no"
|
|
137
|
+
onChange={handleRatingChange}
|
|
138
|
+
/>
|
|
139
|
+
<label
|
|
140
|
+
className="mx-button mx-button--dark mx-button--outline"
|
|
141
|
+
htmlFor={`${rootId}-no`}
|
|
142
|
+
>
|
|
143
|
+
<Icon icon={Icons.THUMB} rotate={IconRotateModifier.ONEEIGHTY} />
|
|
144
|
+
<span>{noLabel}</span>
|
|
145
|
+
</label>
|
|
146
|
+
</div>
|
|
147
|
+
</fieldset>
|
|
148
|
+
|
|
149
|
+
{(secondaryText || secondaryLinkText) && (
|
|
150
|
+
<p className="mx-feedback-form__secondary">
|
|
151
|
+
{secondaryText}
|
|
152
|
+
{secondaryLinkText && secondaryLinkHref && (
|
|
153
|
+
<Link to={secondaryLinkHref}>{secondaryLinkText}</Link>
|
|
154
|
+
)}
|
|
155
|
+
</p>
|
|
156
|
+
)}
|
|
157
|
+
|
|
158
|
+
<div className="mx-feedback-form__reveal">
|
|
159
|
+
<FormItem>
|
|
160
|
+
<FormLabel id={commentId} text={commentLabel} />
|
|
161
|
+
<textarea
|
|
162
|
+
ref={commentRef}
|
|
163
|
+
className="mx-input__textarea"
|
|
164
|
+
id={commentId}
|
|
165
|
+
name={commentName}
|
|
166
|
+
placeholder={commentPlaceholder}
|
|
167
|
+
required={commentRequired}
|
|
168
|
+
aria-describedby={`status-${commentId}`}
|
|
169
|
+
/>
|
|
170
|
+
</FormItem>
|
|
171
|
+
<div
|
|
172
|
+
className="mx-error-message"
|
|
173
|
+
id={`status-${commentId}`}
|
|
174
|
+
role="alert"
|
|
175
|
+
hidden={!showError}
|
|
176
|
+
>
|
|
177
|
+
{requiredErrorMessage}
|
|
178
|
+
</div>
|
|
179
|
+
<div className="mx-form-actions">
|
|
180
|
+
<Button
|
|
181
|
+
type="submit"
|
|
182
|
+
modifiers={SUBMIT_MODIFIERS}
|
|
183
|
+
disabled={pending}
|
|
184
|
+
data-pending={pending}
|
|
185
|
+
aria-busy={pending}
|
|
186
|
+
>
|
|
187
|
+
{submitLabel}
|
|
188
|
+
</Button>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
</form>
|
|
192
|
+
</div>
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export default FeedbackForm
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# FeedbackForm
|
|
2
|
+
|
|
3
|
+
A yes/no "was this page useful?" control that reveals a free-text comment field once a rating
|
|
4
|
+
is chosen.
|
|
5
|
+
|
|
6
|
+
**This component never submits anywhere itself.** Progressive enhancement, native `<form
|
|
7
|
+
action>` submission still works — but the Web Component dispatches a cancelable
|
|
8
|
+
`mx-feedback-form-submit` event (and React calls an `onSubmit` prop) before the form submits, so
|
|
9
|
+
any backend can be wired up by the consumer: a Drupal webform AJAX endpoint, a REST call,
|
|
10
|
+
whatever. This keeps the component usable whether you're posting to a classic HTML form action
|
|
11
|
+
or handling submission entirely in JS.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## CSS Import
|
|
16
|
+
|
|
17
|
+
```css
|
|
18
|
+
@import "@pnx-mixtape/mxds/src/Form/FeedbackForm/feedback-form.css";
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Twig
|
|
24
|
+
|
|
25
|
+
```twig
|
|
26
|
+
{% include '@mixtape/Form/FeedbackForm/feedback-form.twig' with {
|
|
27
|
+
id: 'feedback',
|
|
28
|
+
secondaryText: 'If you need a response, ',
|
|
29
|
+
secondaryLinkText: 'contact us',
|
|
30
|
+
secondaryLinkHref: '/contact',
|
|
31
|
+
} %}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**feedback-form.twig props:**
|
|
35
|
+
|
|
36
|
+
| Prop | Type | Default | Description |
|
|
37
|
+
| ---------------------- | ----------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
|
|
38
|
+
| `id` | `string` | `feedback-form` | Root id, used to derive the rating/comment field ids |
|
|
39
|
+
| `name` | `string` | `rating` | `name` on the two rating radios |
|
|
40
|
+
| `question` | `string` | `Was the information on this page useful?` | Rating fieldset `<legend>` text |
|
|
41
|
+
| `yesLabel` / `noLabel` | `string` | `Yes` / `No` | Rating radio labels |
|
|
42
|
+
| `secondaryText` | `string` | — | Optional copy shown below the rating (e.g. pointing to another contact channel) |
|
|
43
|
+
| `secondaryLinkText` | `string` | — | Optional link text — only rendered together with `secondaryLinkHref` |
|
|
44
|
+
| `secondaryLinkHref` | `string` | — | Optional link destination |
|
|
45
|
+
| `commentLabel` | `string` | `How can we make it better?` | Label for the revealed comment field |
|
|
46
|
+
| `commentName` | `string` | `comment` | `name` on the comment `<textarea>` |
|
|
47
|
+
| `commentPlaceholder` | `string` | — | Comment `<textarea>` placeholder |
|
|
48
|
+
| `commentRequired` | `boolean` | `false` | Adds `required` to the comment field, and enables the required-field error message |
|
|
49
|
+
| `requiredErrorMessage` | `string` | `This field is required.` | Message shown when `commentRequired` is set and the comment is submitted empty |
|
|
50
|
+
| `submitLabel` | `string` | `Submit` | Submit button text |
|
|
51
|
+
| `action` | `string` | — | `<form action>` — progressive-enhancement fallback target |
|
|
52
|
+
| `method` | `string` | `post` | `<form method>` |
|
|
53
|
+
| `errorMessage` | `string` | — | Consumer-supplied top-level notice (e.g. after a failed async submit), rendered as a `role="alert"` block |
|
|
54
|
+
| `attributes` | `Attribute` | — | Optional Drupal-style attributes object — `mx-feedback-form` is always merged on top |
|
|
55
|
+
|
|
56
|
+
Registers as `<mx-feedback-form>`. Import the element to activate its behaviour:
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
import "@pnx-mixtape/mxds/src/Form/FeedbackForm/Elements/FeedbackForm"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## HTML + Web Component
|
|
65
|
+
|
|
66
|
+
```html
|
|
67
|
+
<mx-feedback-form class="mx-feedback-form">
|
|
68
|
+
<form method="post">
|
|
69
|
+
<fieldset class="mx-fieldset mx-fieldset--plain mx-feedback-form__rating">
|
|
70
|
+
<legend class="mx-legend">Was the information on this page useful?</legend>
|
|
71
|
+
<div class="mx-feedback-form__option">
|
|
72
|
+
<input type="radio" class="sr-only" id="feedback-yes" name="rating" value="yes" />
|
|
73
|
+
<label class="mx-button mx-button--dark mx-button--outline" for="feedback-yes">
|
|
74
|
+
<span class="mx-icon mx-icon--thumb"></span>
|
|
75
|
+
<span>Yes</span>
|
|
76
|
+
</label>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="mx-feedback-form__option">
|
|
79
|
+
<input type="radio" class="sr-only" id="feedback-no" name="rating" value="no" />
|
|
80
|
+
<label class="mx-button mx-button--dark mx-button--outline" for="feedback-no">
|
|
81
|
+
<span class="mx-icon mx-icon--thumb mx-icon--rotate-180"></span>
|
|
82
|
+
<span>No</span>
|
|
83
|
+
</label>
|
|
84
|
+
</div>
|
|
85
|
+
</fieldset>
|
|
86
|
+
<div class="mx-feedback-form__reveal">
|
|
87
|
+
<!-- FormItem-wrapped comment textarea, error status, submit button -->
|
|
88
|
+
</div>
|
|
89
|
+
</form>
|
|
90
|
+
</mx-feedback-form>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Reveal behaviour:** the comment field and submit button are always visible and submittable in
|
|
94
|
+
the static/no-JS markup. Once the `<mx-feedback-form>` element connects, it adds an
|
|
95
|
+
`is-enhanced` class to itself — only then does CSS hide the reveal block until a rating radio is
|
|
96
|
+
checked (`:has()`-driven). This means a blocked or failed script load can never leave the form's
|
|
97
|
+
only submit path permanently hidden — see [Accessibility](#accessibility).
|
|
98
|
+
|
|
99
|
+
**Validation:** when `commentRequired` is set and the comment is empty on submit, the Web
|
|
100
|
+
Component prevents the native submit, marks the textarea `aria-invalid="true"`, reveals the
|
|
101
|
+
pre-rendered `role="alert"` status message, and moves focus to the comment field — rather than
|
|
102
|
+
relying on the browser's native validation bubble (`novalidate` is set on the form).
|
|
103
|
+
|
|
104
|
+
**Submit contract:** on a valid submit, the Web Component dispatches a cancelable
|
|
105
|
+
`mx-feedback-form-submit` `CustomEvent` with `detail: { rating, comment, form }` _before_ the
|
|
106
|
+
form actually submits. If a listener calls `preventDefault()` on that event, the native
|
|
107
|
+
submission is also cancelled — full consumer ownership of the request. If nothing cancels it,
|
|
108
|
+
the browser proceeds with the form's `action`/`method` as normal.
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
const root = document.querySelector("mx-feedback-form")
|
|
112
|
+
|
|
113
|
+
root.addEventListener("mx-feedback-form-submit", async event => {
|
|
114
|
+
event.preventDefault() // take over submission ourselves
|
|
115
|
+
const { rating, comment } = event.detail
|
|
116
|
+
await fetch("/api/feedback", {
|
|
117
|
+
method: "POST",
|
|
118
|
+
body: JSON.stringify({ rating, comment }),
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Pending state:** while your own async submit is in flight, reflect it with the `pending`
|
|
124
|
+
attribute:
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
root.setAttribute("pending", "")
|
|
128
|
+
// ... await your request ...
|
|
129
|
+
root.removeAttribute("pending")
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
This adds mxds's standard `pending` custom state (`internals_.states`, so `:state(pending)` is
|
|
133
|
+
selectable in CSS) and disables/`aria-busy`s the submit button.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## React
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
import { FeedbackForm } from "@pnx-mixtape/mxds/react"
|
|
141
|
+
|
|
142
|
+
const Example = () => {
|
|
143
|
+
const [pending, setPending] = useState(false)
|
|
144
|
+
|
|
145
|
+
const handleSubmit = async ({ rating, comment }) => {
|
|
146
|
+
setPending(true)
|
|
147
|
+
await fetch("/api/feedback", { method: "POST", body: JSON.stringify({ rating, comment }) })
|
|
148
|
+
setPending(false)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<FeedbackForm
|
|
153
|
+
id="feedback"
|
|
154
|
+
secondaryText="If you need a response, "
|
|
155
|
+
secondaryLinkText="contact us"
|
|
156
|
+
secondaryLinkHref="/contact"
|
|
157
|
+
pending={pending}
|
|
158
|
+
onSubmit={handleSubmit}
|
|
159
|
+
/>
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**`FeedbackForm` props:** same set as the Twig props above (camelCase, no `attributes`), plus:
|
|
165
|
+
|
|
166
|
+
| Prop | Type | Default | Description |
|
|
167
|
+
| ----------- | --------------------------------------------- | ------- | ------------------------------------------------------------------------------- |
|
|
168
|
+
| `pending` | `boolean` | `false` | Reflected as `aria-busy`/`disabled` on the submit button |
|
|
169
|
+
| `onSubmit` | `(detail: { rating, comment, form }) => void` | — | Called on a valid submit — see [React ⇄ Twig parity](#react--twig-parity) below |
|
|
170
|
+
| `className` | `string` | — | Extra class on the root |
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### React ⇄ Twig parity
|
|
175
|
+
|
|
176
|
+
- **Submit ownership differs by design.** In Twig/Web Component, `mx-feedback-form-submit` is a
|
|
177
|
+
_cancelable_ event — the native form submission proceeds unless a listener calls
|
|
178
|
+
`preventDefault()`. In React, supplying `onSubmit` always prevents the native submission (a
|
|
179
|
+
React app wiring up `onSubmit` expects full control, not an opt-in interception) — there's no
|
|
180
|
+
React equivalent of "let it submit natively AND notify me."
|
|
181
|
+
- `pending` has no Twig prop — it's a runtime-only concept. The Twig/Web-Component equivalent is
|
|
182
|
+
the `pending` HTML _attribute_, toggled by your own JS after render (see above), not a
|
|
183
|
+
template-render-time prop.
|
|
184
|
+
- `yesIcon`, `noIcon`, `commentItem`, and `submitButton` in `feedback-form.twig` are internal
|
|
185
|
+
`{% set x %}...{% endset %}` template locals used to pre-render nested `Icon`/`FormItem`/
|
|
186
|
+
`Button` markup for composition — not props, and not present in React (which composes the
|
|
187
|
+
equivalent components directly in JSX). `pnpm audit-parity`'s static extractor doesn't
|
|
188
|
+
recognize the `{% set x %}...{% endset %}` capture-block form (only the inline `{% set x = … %}`
|
|
189
|
+
form), so it misreports these as Twig-only props — a known tool limitation, not a real
|
|
190
|
+
divergence.
|
|
191
|
+
- `commentPlaceholder` and `commentRequired` genuinely are used in Twig — passed through to
|
|
192
|
+
`Form/Textarea/input-textarea.twig` inside a nested `{% include ... with {...} %}` call. The
|
|
193
|
+
audit tool's extractor doesn't scan inside `with {}` object literals, so it misreports these as
|
|
194
|
+
unused — also a known tool limitation.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Modifiers
|
|
199
|
+
|
|
200
|
+
No BEM modifier classes. `mx-feedback-form--l`-style breakpoint modifiers don't apply here —
|
|
201
|
+
this component isn't visibility/breakpoint-gated the way `BackToTop`/`Sticky` are.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Accessibility
|
|
206
|
+
|
|
207
|
+
- The rating group uses a real `<fieldset>`/`<legend>` — no `role="radiogroup"` needed, this is
|
|
208
|
+
the native equivalent.
|
|
209
|
+
- Each rating option is a real, focusable `<input type="radio">` paired with a `<label>` styled
|
|
210
|
+
as a button (reusing the `Button` atom's own classes/custom properties — `mx-button
|
|
211
|
+
mx-button--dark mx-button--outline`, with `:has(input:checked)` undoing the outline to show
|
|
212
|
+
the selected state). The input itself is visually hidden with `.sr-only` (clip, not
|
|
213
|
+
`display:none`) so it stays in the tab order and the accessibility tree — only its default
|
|
214
|
+
circular appearance is hidden, keyboard operation (Tab to focus, arrow keys to move within the
|
|
215
|
+
native radio group, Space/click to select) is unaffected. The visible label still carries icon
|
|
216
|
+
- text, not icon-only/visually-hidden text.
|
|
217
|
+
- **The comment field and submit button are reachable without JavaScript.** The reveal-on-rating
|
|
218
|
+
behaviour is only switched on once JS is confirmed running (`is-enhanced`, added by the Web
|
|
219
|
+
Component's `connectedCallback`; always present in React) — a blocked or failed script load
|
|
220
|
+
never leaves the form's only path to submit permanently hidden.
|
|
221
|
+
- The required-field error is a `role="alert"` region, programmatically associated with the
|
|
222
|
+
comment field via `aria-describedby`, and focus moves to the field when validation fails.
|
|
223
|
+
- The top-level `errorMessage` notice is also `role="alert"`.
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Customisation via CSS custom properties
|
|
228
|
+
|
|
229
|
+
```css
|
|
230
|
+
.mx-feedback-form {
|
|
231
|
+
--feedback-form-icon-size: var(--spacing-m); /* size of the thumb icons */
|
|
232
|
+
}
|
|
233
|
+
```
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`Feedback Form 1`] = `
|
|
4
|
+
"
|
|
5
|
+
|
|
6
|
+
<mx-feedback-form class="mx-feedback-form is-enhanced">
|
|
7
|
+
<form action="" method="post" novalidate="">
|
|
8
|
+
|
|
9
|
+
<fieldset class="mx-fieldset mx-fieldset--plain mx-feedback-form__rating">
|
|
10
|
+
<legend class="mx-legend">Was the information on this page useful?</legend>
|
|
11
|
+
<div class="mx-feedback-form__option">
|
|
12
|
+
<input type="radio" class="sr-only" id="feedback-yes" name="rating" value="yes">
|
|
13
|
+
<label class="mx-button mx-button--dark mx-button--outline" for="feedback-yes">
|
|
14
|
+
|
|
15
|
+
<span class="mx-icon mx-icon--thumb"></span>
|
|
16
|
+
<span>Yes</span>
|
|
17
|
+
</label>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="mx-feedback-form__option">
|
|
20
|
+
<input type="radio" class="sr-only" id="feedback-no" name="rating" value="no">
|
|
21
|
+
<label class="mx-button mx-button--dark mx-button--outline" for="feedback-no">
|
|
22
|
+
|
|
23
|
+
<span class="mx-icon mx-icon--thumb mx-icon--rotate-180"></span>
|
|
24
|
+
<span>No</span>
|
|
25
|
+
</label>
|
|
26
|
+
</div>
|
|
27
|
+
</fieldset>
|
|
28
|
+
|
|
29
|
+
<p class="mx-feedback-form__secondary">
|
|
30
|
+
Your rating will help us improve the website.
|
|
31
|
+
</p>
|
|
32
|
+
|
|
33
|
+
<div class="mx-feedback-form__reveal">
|
|
34
|
+
|
|
35
|
+
<div class="mx-form-item">
|
|
36
|
+
|
|
37
|
+
<label class="mx-label" for="feedback-comment">
|
|
38
|
+
How can we make it better?
|
|
39
|
+
</label>
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
<textarea aria-describedby="unique-0" class="mx-input__textarea" id="feedback-comment" name="unique-0"></textarea>
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div class="mx-error-message" id="unique-0" role="alert" hidden="">This field is required.</div>
|
|
50
|
+
<div class="mx-form-actions">
|
|
51
|
+
|
|
52
|
+
<button class="mx-button mx-button--dark" type="submit">
|
|
53
|
+
|
|
54
|
+
<span>Submit</span>
|
|
55
|
+
|
|
56
|
+
</button>
|
|
57
|
+
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</form>
|
|
61
|
+
</mx-feedback-form>
|
|
62
|
+
"
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
exports[`Required Comment 1`] = `
|
|
66
|
+
"
|
|
67
|
+
|
|
68
|
+
<mx-feedback-form class="mx-feedback-form is-enhanced">
|
|
69
|
+
<form action="" method="post" novalidate="">
|
|
70
|
+
|
|
71
|
+
<fieldset class="mx-fieldset mx-fieldset--plain mx-feedback-form__rating">
|
|
72
|
+
<legend class="mx-legend">Was the information on this page useful?</legend>
|
|
73
|
+
<div class="mx-feedback-form__option">
|
|
74
|
+
<input type="radio" class="sr-only" id="feedback-required-yes" name="rating" value="yes">
|
|
75
|
+
<label class="mx-button mx-button--dark mx-button--outline" for="feedback-required-yes">
|
|
76
|
+
|
|
77
|
+
<span class="mx-icon mx-icon--thumb"></span>
|
|
78
|
+
<span>Yes</span>
|
|
79
|
+
</label>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="mx-feedback-form__option">
|
|
82
|
+
<input type="radio" class="sr-only" id="feedback-required-no" name="rating" value="no">
|
|
83
|
+
<label class="mx-button mx-button--dark mx-button--outline" for="feedback-required-no">
|
|
84
|
+
|
|
85
|
+
<span class="mx-icon mx-icon--thumb mx-icon--rotate-180"></span>
|
|
86
|
+
<span>No</span>
|
|
87
|
+
</label>
|
|
88
|
+
</div>
|
|
89
|
+
</fieldset>
|
|
90
|
+
|
|
91
|
+
<p class="mx-feedback-form__secondary">
|
|
92
|
+
Your rating will help us improve the website.
|
|
93
|
+
</p>
|
|
94
|
+
|
|
95
|
+
<div class="mx-feedback-form__reveal">
|
|
96
|
+
|
|
97
|
+
<div class="mx-form-item">
|
|
98
|
+
|
|
99
|
+
<label class="mx-label" for="feedback-required-comment">
|
|
100
|
+
How can we make it better?
|
|
101
|
+
</label>
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
<textarea aria-describedby="unique-0" class="mx-input__textarea" id="feedback-required-comment" name="unique-0" required="" aria-invalid="true"></textarea>
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div class="mx-error-message" id="unique-0" role="alert">This field is required.</div>
|
|
112
|
+
<div class="mx-form-actions">
|
|
113
|
+
|
|
114
|
+
<button class="mx-button mx-button--dark" type="submit">
|
|
115
|
+
|
|
116
|
+
<span>Submit</span>
|
|
117
|
+
|
|
118
|
+
</button>
|
|
119
|
+
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</form>
|
|
123
|
+
</mx-feedback-form>
|
|
124
|
+
"
|
|
125
|
+
`;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`Feedback Form 1`] = `"<div class="mx-feedback-form is-enhanced"><form method="post" novalidate=""><fieldset class="mx-fieldset mx-fieldset--plain mx-feedback-form__rating"><legend class="mx-legend">Was the information on this page useful?</legend><div class="mx-feedback-form__option"><input class="sr-only" id="feedback-yes" type="radio" value="yes" name="rating"><label class="mx-button mx-button--dark mx-button--outline" for="feedback-yes"><span class="mx-icon mx-icon--thumb"></span><span>Yes</span></label></div><div class="mx-feedback-form__option"><input class="sr-only" id="feedback-no" type="radio" value="no" name="rating"><label class="mx-button mx-button--dark mx-button--outline" for="feedback-no"><span class="mx-icon mx-icon--thumb mx-icon--rotate-180"></span><span>No</span></label></div></fieldset><p class="mx-feedback-form__secondary">If you need a response, <a href="#contact" class="mx-link">contact us</a></p><div class="mx-feedback-form__reveal"><div class="mx-form-item"><label class="mx-label" for="feedback-comment">How can we make it better?</label><textarea class="mx-input__textarea" id="feedback-comment" name="comment" aria-describedby="status-feedback-comment"></textarea></div><div class="mx-error-message" id="status-feedback-comment" role="alert" hidden="">This field is required.</div><div class="mx-form-actions"><button class="mx-button mx-button--dark" type="submit" data-pending="false" aria-busy="false"><span>Submit</span></button></div></div></form></div>"`;
|
|
4
|
+
|
|
5
|
+
exports[`Required Comment 1`] = `"<div class="mx-feedback-form is-enhanced"><form method="post" novalidate=""><fieldset class="mx-fieldset mx-fieldset--plain mx-feedback-form__rating"><legend class="mx-legend">Was the information on this page useful?</legend><div class="mx-feedback-form__option"><input class="sr-only" id="feedback-required-yes" type="radio" value="yes" name="rating"><label class="mx-button mx-button--dark mx-button--outline" for="feedback-required-yes"><span class="mx-icon mx-icon--thumb"></span><span>Yes</span></label></div><div class="mx-feedback-form__option"><input class="sr-only" id="feedback-required-no" type="radio" value="no" name="rating"><label class="mx-button mx-button--dark mx-button--outline" for="feedback-required-no"><span class="mx-icon mx-icon--thumb mx-icon--rotate-180"></span><span>No</span></label></div></fieldset><p class="mx-feedback-form__secondary">If you need a response, <a href="#contact" class="mx-link">contact us</a></p><div class="mx-feedback-form__reveal"><div class="mx-form-item"><label class="mx-label" for="feedback-required-comment">How can we make it better?</label><textarea class="mx-input__textarea" id="feedback-required-comment" name="comment" required="" aria-describedby="status-feedback-required-comment"></textarea></div><div class="mx-error-message" id="status-feedback-required-comment" role="alert">This field is required.</div><div class="mx-form-actions"><button class="mx-button mx-button--dark" type="submit" data-pending="false" aria-busy="false"><span>Submit</span></button></div></div></form></div>"`;
|