@pnx-mixtape/mxds 0.1.0-rc.2 → 0.1.0-rc.3
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/.storybook/{preview.ts → preview.tsx} +4 -0
- package/.storybook/src/docs.tsx +29 -0
- package/dist/build/accordion.entry.js +1 -1
- package/dist/build/autocomplete.css +1 -1
- package/dist/build/autocomplete.entry.js +1 -1
- package/dist/build/base.css +1 -1
- package/dist/build/breadcrumb.css +1 -1
- package/dist/build/button.css +1 -1
- package/dist/build/card.css +1 -1
- package/dist/build/chunks/{Accordion-z-dH6nFH.js → Accordion-GN0sME4W.js} +1 -1
- package/dist/build/chunks/{AccordionMobile-BYPPNyf5.js → AccordionMobile-DG5jSe8Q.js} +1 -1
- package/dist/build/chunks/{Autocomplete-2L6tLLuh.js → Autocomplete-B4RtkAyh.js} +1 -1
- package/dist/build/chunks/{ClosableAlert-DdXbF1J2.js → ClosableAlert-DEhj8Nxm.js} +1 -1
- package/dist/build/chunks/{Dialog-Ds8gzhri.js → Dialog-Haahn-Yn.js} +1 -1
- package/dist/build/chunks/Gallery-D8NGXy2Q.js +1 -0
- package/dist/build/chunks/GlobalToggle-B52fdBQu.js +1 -0
- package/dist/build/chunks/{InPageNavigation-CVieEl9u.js → InPageNavigation-Dyn2TR-N.js} +1 -1
- package/dist/build/chunks/Navigation-hOrfLq7h.js +1 -0
- package/dist/build/chunks/{Sticky-cWUVSOXN.js → Sticky-BGCuuEJK.js} +1 -1
- package/dist/build/chunks/{Tabs-DA_Ez8pv.js → Tabs-DYy1YTdU.js} +1 -1
- package/dist/build/chunks/enums-DLV_1NAf.js +1 -0
- package/dist/build/chunks/utilities-_Bq3TxAQ.js +1 -0
- package/dist/build/cookie-consent.css +1 -1
- package/dist/build/dialog.entry.js +1 -1
- package/dist/build/elements.js +1 -1
- package/dist/build/filters.css +1 -1
- package/dist/build/filters.entry.js +1 -1
- package/dist/build/form.css +1 -1
- package/dist/build/gallery.entry.js +1 -1
- package/dist/build/global-alert.entry.js +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/list-item.css +1 -1
- package/dist/build/navigation.entry.js +1 -1
- package/dist/build/one-time-code.css +1 -0
- package/dist/build/popover.css +1 -1
- package/dist/build/print.css +1 -1
- package/dist/build/react.js +1 -1
- package/dist/build/steps.css +1 -1
- package/dist/build/sticky.entry.js +1 -1
- package/dist/build/tabs.css +1 -1
- package/dist/build/tabs.entry.js +1 -1
- package/dist/build/utilities.css +1 -1
- package/dist/types/Atom/Link/Link.d.ts.map +1 -1
- package/dist/types/Component/Accordion/Accordion.d.ts.map +1 -1
- package/dist/types/Component/Dialog/Dialog.d.ts.map +1 -1
- package/dist/types/Component/Filters/Elements/Filters.d.ts +20 -1
- package/dist/types/Component/Filters/Elements/Filters.d.ts.map +1 -1
- package/dist/types/Component/Filters/Filters.d.ts +7 -2
- package/dist/types/Component/Filters/Filters.d.ts.map +1 -1
- package/dist/types/Component/Gallery/Gallery.d.ts.map +1 -1
- package/dist/types/Component/Pagination/Components/PaginationContext.d.ts.map +1 -1
- package/dist/types/Component/Tabs/Tabs.d.ts.map +1 -1
- package/dist/types/Form/Autocomplete/Autocomplete.d.ts +2 -2
- package/dist/types/Form/Autocomplete/Autocomplete.d.ts.map +1 -1
- package/dist/types/Form/Autocomplete/Elements/Autocomplete.d.ts +10 -0
- package/dist/types/Form/Autocomplete/Elements/Autocomplete.d.ts.map +1 -1
- package/dist/types/Form/OneTimeCode/OneTimeCode.d.ts +12 -0
- package/dist/types/Form/OneTimeCode/OneTimeCode.d.ts.map +1 -0
- package/dist/types/Utility/Hooks/useIsomorphicLayoutEffect.d.ts +4 -0
- package/dist/types/Utility/Hooks/useIsomorphicLayoutEffect.d.ts.map +1 -0
- package/dist/types/enums.d.ts +4 -0
- package/dist/types/enums.d.ts.map +1 -1
- package/dist/types/react.d.ts +1 -0
- package/dist/types/react.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/Atom/Button/__snapshots__/Button.stories.ts.snap +2 -3
- package/src/Atom/Button/_buttons.css +16 -0
- package/src/Atom/Button/button.twig +4 -1
- package/src/Atom/DefinitionList/DefinitionList.stories.ts +3 -0
- package/src/Atom/Icon/_icon.css +3 -0
- package/src/Atom/Link/Link.tsx +16 -3
- package/src/Atom/Link/__snapshots__/Link.stories.ts.snap +7 -12
- package/src/Atom/Link/__snapshots__/Link.stories.tsx.snap +1 -1
- package/src/Atom/Link/_links.css +9 -0
- package/src/Atom/Link/link.twig +8 -2
- package/src/Atom/LinkedImage/linked-image.twig +1 -2
- package/src/Component/Accordion/Accordion.stories.ts +31 -0
- package/src/Component/Accordion/Accordion.tsx +3 -2
- package/src/Component/Accordion/AccordionItem.stories.ts +27 -0
- package/src/Component/Accordion/__snapshots__/Accordion.stories.ts.snap +36 -0
- package/src/Component/Accordion/__snapshots__/AccordionItem.stories.ts.snap +16 -0
- package/src/Component/Breadcrumb/__snapshots__/Breadcrumb.stories.ts.snap +3 -6
- package/src/Component/Breadcrumb/breadcrumb.css +4 -0
- package/src/Component/Card/__snapshots__/Card.stories.ts.snap +11 -22
- package/src/Component/Card/card.css +2 -2
- package/src/Component/Carousel/Carousel.stories.ts +55 -0
- package/src/Component/Carousel/Carousel.stories.tsx +74 -0
- package/src/Component/Carousel/__snapshots__/Carousel.stories.ts.snap +273 -88
- package/src/Component/Carousel/__snapshots__/Carousel.stories.tsx.snap +5 -3
- package/src/Component/ContentBlock/__snapshots__/ContentBlock.stories.ts.snap +12 -24
- package/src/Component/CookieConsent/cookie-consent.css +3 -1
- package/src/Component/Dialog/Dialog.stories.ts +33 -0
- package/src/Component/Dialog/Dialog.stories.tsx +25 -0
- package/src/Component/Dialog/Dialog.tsx +2 -2
- package/src/Component/Dialog/__snapshots__/Dialog.stories.ts.snap +33 -2
- package/src/Component/Dialog/__snapshots__/Dialog.stories.tsx.snap +2 -0
- package/src/Component/DropMenu/DropMenu.stories.ts +39 -1
- package/src/Component/DropMenu/DropMenu.stories.tsx +29 -0
- package/src/Component/DropMenu/DropMenu.tsx +1 -1
- package/src/Component/DropMenu/__snapshots__/DropMenu.stories.ts.snap +14 -0
- package/src/Component/DropMenu/__snapshots__/DropMenu.stories.tsx.snap +3 -1
- package/src/Component/Filters/Elements/Filters.ts +71 -1
- package/src/Component/Filters/FilterItem.stories.ts +2 -1
- package/src/Component/Filters/FilterItem.tsx +1 -1
- package/src/Component/Filters/Filters.figma.ts +1 -1
- package/src/Component/Filters/Filters.stories.ts +95 -3
- package/src/Component/Filters/Filters.stories.tsx +246 -17
- package/src/Component/Filters/Filters.tsx +145 -29
- package/src/Component/Filters/README.md +102 -37
- package/src/Component/Filters/__snapshots__/FilterItem.stories.tsx.snap +3 -3
- package/src/Component/Filters/__snapshots__/Filters.stories.ts.snap +82 -4
- package/src/Component/Filters/__snapshots__/Filters.stories.tsx.snap +9 -3
- package/src/Component/Filters/filters.css +11 -0
- package/src/Component/Gallery/Gallery.tsx +2 -2
- package/src/Component/HeroBanner/HeroBanner.stories.ts +1 -1
- package/src/Component/HeroBanner/__snapshots__/HeroBanner.stories.ts.snap +4 -8
- package/src/Component/HeroSearch/__snapshots__/HeroSearch.stories.ts.snap +5 -10
- package/src/Component/InPageAlert/__snapshots__/InPageAlert.stories.ts.snap +6 -12
- package/src/Component/InPageNavigation/InPageNavigation.stories.ts +50 -0
- package/src/Component/InPageNavigation/InPageNavigation.stories.tsx +78 -0
- package/src/Component/InPageNavigation/__snapshots__/InPageNavigation.stories.ts.snap +35 -1
- package/src/Component/InPageNavigation/__snapshots__/InPageNavigation.stories.tsx.snap +2 -0
- package/src/Component/LinkList/__snapshots__/LinkList.stories.ts.snap +6 -12
- package/src/Component/ListItem/__snapshots__/ListItem.stories.ts.snap +7 -14
- package/src/Component/ListItem/list-item.css +1 -1
- package/src/Component/Navigation/Dropdown.stories.tsx +46 -0
- package/src/Component/Navigation/Navigation.stories.ts +56 -0
- package/src/Component/Navigation/__snapshots__/Dropdown.stories.tsx.snap +2 -0
- package/src/Component/Navigation/__snapshots__/Navigation.stories.ts.snap +57 -0
- package/src/Component/Pagination/Components/PaginationContext.tsx +2 -1
- package/src/Component/Pagination/Components/PaginationItem.tsx +1 -1
- package/src/Component/Pagination/Pagination.stories.ts +36 -0
- package/src/Component/Pagination/Pagination.stories.tsx +31 -0
- package/src/Component/Pagination/__snapshots__/Pagination.stories.ts.snap +69 -12
- package/src/Component/Pagination/__snapshots__/Pagination.stories.tsx.snap +3 -1
- package/src/Component/Pagination/pagination-item.twig +1 -1
- package/src/Component/Popover/__snapshots__/Popover.stories.ts.snap +13 -26
- package/src/Component/Popover/popover.css +1 -1
- package/src/Component/SideNavigation/__snapshots__/SideNavigation.stories.ts.snap +1 -2
- package/src/Component/SocialLinks/__snapshots__/SocialLinks.stories.ts.snap +6 -9
- package/src/Component/Steps/Steps.stories.ts +1 -1
- package/src/Component/Steps/steps.css +13 -0
- package/src/Component/Tabs/Tabs.stories.ts +22 -0
- package/src/Component/Tabs/Tabs.stories.tsx +24 -0
- package/src/Component/Tabs/Tabs.tsx +2 -2
- package/src/Component/Tabs/__snapshots__/Tabs.stories.ts.snap +18 -0
- package/src/Component/Tabs/__snapshots__/Tabs.stories.tsx.snap +3 -1
- package/src/Component/Tabs/tabs.css +2 -0
- package/src/Form/Autocomplete/Autocomplete.stories.ts +81 -4
- package/src/Form/Autocomplete/Autocomplete.stories.tsx +85 -1
- package/src/Form/Autocomplete/Autocomplete.tsx +19 -6
- package/src/Form/Autocomplete/Elements/Autocomplete.ts +24 -1
- package/src/Form/Autocomplete/__snapshots__/Autocomplete.stories.ts.snap +38 -4
- package/src/Form/Autocomplete/__snapshots__/Autocomplete.stories.tsx.snap +8 -4
- package/src/Form/Autocomplete/autocomplete.css +12 -1
- package/src/Form/Autocomplete/autocomplete.twig +1 -1
- package/src/Form/OneTimeCode/OneTimeCode.stories.ts +20 -0
- package/src/Form/OneTimeCode/OneTimeCode.stories.tsx +18 -0
- package/src/Form/OneTimeCode/OneTimeCode.tsx +56 -0
- package/src/Form/OneTimeCode/README.md +83 -0
- package/src/Form/OneTimeCode/__snapshots__/OneTimeCode.stories.ts.snap +7 -0
- package/src/Form/OneTimeCode/__snapshots__/OneTimeCode.stories.tsx.snap +3 -0
- package/src/Form/OneTimeCode/one-time-code.css +13 -0
- package/src/Form/OneTimeCode/one-time-code.twig +31 -0
- package/src/Form/__snapshots__/Validation.stories.ts.snap +1 -1
- package/src/Form/form.css +90 -32
- package/src/Layout/Footer/__snapshots__/Footer.stories.ts.snap +18 -33
- package/src/Layout/Header/Header.stories.ts +33 -0
- package/src/Layout/Header/Header.stories.tsx +31 -0
- package/src/Layout/Header/__snapshots__/Header.stories.ts.snap +50 -0
- package/src/Layout/Header/__snapshots__/Header.stories.tsx.snap +2 -0
- package/src/Layout/Masthead/__snapshots__/Masthead.stories.ts.snap +8 -16
- package/src/Layout/Section/__snapshots__/Background.stories.ts.snap +2 -4
- package/src/Layout/Section/__snapshots__/Section.stories.ts.snap +4 -8
- package/src/Utility/Hooks/useIsomorphicLayoutEffect.ts +8 -0
- package/src/Utility/_mixins.css +24 -0
- package/src/Utility/print.css +1 -1
- package/src/Utility/utilities.css +20 -0
- package/src/_custom-media.css +1 -0
- package/src/enums.ts +5 -0
- package/src/react.ts +1 -0
- package/tools/test-utils/screen-reader.ts +33 -0
- package/dist/build/chunks/Gallery-BT3cxS5m.js +0 -1
- package/dist/build/chunks/GlobalToggle-DQHho97V.js +0 -1
- package/dist/build/chunks/Navigation-B9Wug-ok.js +0 -1
- package/dist/build/chunks/disclosure-widget-BETzOHLO.js +0 -1
- package/dist/build/chunks/utilities-C5dAO2YA.js +0 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import {
|
|
5
5
|
createContext,
|
|
6
|
-
|
|
6
|
+
SubmitEvent,
|
|
7
7
|
ChangeEventHandler,
|
|
8
8
|
FormEventHandler,
|
|
9
9
|
Fragment,
|
|
@@ -12,21 +12,35 @@ import {
|
|
|
12
12
|
ReactNode,
|
|
13
13
|
use,
|
|
14
14
|
useCallback,
|
|
15
|
+
useEffect,
|
|
15
16
|
useMemo,
|
|
16
17
|
useRef,
|
|
17
18
|
useState,
|
|
18
19
|
} from "react"
|
|
19
20
|
import classNames from "classnames"
|
|
21
|
+
import { FocusScope } from "react-aria"
|
|
22
|
+
import useIsomorphicLayoutEffect from "../../Utility/Hooks/useIsomorphicLayoutEffect"
|
|
20
23
|
import useMediaQuery from "../../Utility/Hooks/useMediaQuery"
|
|
24
|
+
import { DEBOUNCE_MS } from "../../enums"
|
|
21
25
|
|
|
22
26
|
type FiltersContextValue = {
|
|
23
27
|
counts: Map<string, number>
|
|
28
|
+
pending: boolean
|
|
29
|
+
/** Total number of filter groups with at least one active value — 0 means the form is empty. */
|
|
30
|
+
totalCount: number
|
|
24
31
|
}
|
|
25
32
|
|
|
26
|
-
const FiltersContext = createContext<FiltersContextValue>({
|
|
33
|
+
const FiltersContext = createContext<FiltersContextValue>({
|
|
34
|
+
counts: new Map(),
|
|
35
|
+
pending: false,
|
|
36
|
+
totalCount: 0,
|
|
37
|
+
})
|
|
27
38
|
|
|
28
39
|
export const useFiltersContext = (): FiltersContextValue => use(FiltersContext)
|
|
29
40
|
|
|
41
|
+
const FOCUSABLE_SELECTOR =
|
|
42
|
+
'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
|
43
|
+
|
|
30
44
|
type FiltersProps = PropsWithChildren & {
|
|
31
45
|
title: string
|
|
32
46
|
action?: string
|
|
@@ -35,13 +49,15 @@ type FiltersProps = PropsWithChildren & {
|
|
|
35
49
|
actions?: ReactNode[]
|
|
36
50
|
/** "down" = collapsible panel on mobile (uses native <details>). "right" is not yet supported. */
|
|
37
51
|
direction?: "down" | "right"
|
|
52
|
+
/** Auto-submits the form as filters change — immediately for checkbox/radio/select fields, debounced (~400ms) for text fields — and on reset. `onSubmit` still needs `event.preventDefault()` to avoid a real page navigation, same as manual submission, but now it can fire on every keystroke so it's more important than ever to wire up. */
|
|
38
53
|
instant?: boolean
|
|
39
54
|
/** When true, the entire filter panel collapses into a <details> accordion on mobile. */
|
|
40
55
|
collapsible?: boolean
|
|
41
56
|
sticky?: boolean
|
|
42
57
|
/** Show a counter badge on the title with the total number of active filter groups. */
|
|
43
58
|
counter?: boolean
|
|
44
|
-
|
|
59
|
+
/** Called on form submit — do the actual filtering here (fetch, router navigation, etc). Return a promise to drive the built-in pending state (`aria-busy` on the form, which also shows a spinner) while it resolves. */
|
|
60
|
+
onSubmit?: (event: SubmitEvent<HTMLFormElement>) => void | Promise<void>
|
|
45
61
|
onReset?: FormEventHandler<HTMLFormElement>
|
|
46
62
|
onChange?: ChangeEventHandler<HTMLFormElement>
|
|
47
63
|
className?: string
|
|
@@ -53,7 +69,7 @@ const Filters = ({
|
|
|
53
69
|
action,
|
|
54
70
|
items,
|
|
55
71
|
actions,
|
|
56
|
-
direction,
|
|
72
|
+
direction = "down",
|
|
57
73
|
instant = false,
|
|
58
74
|
collapsible = false,
|
|
59
75
|
sticky = false,
|
|
@@ -68,6 +84,24 @@ const Filters = ({
|
|
|
68
84
|
const formRef = useRef<HTMLFormElement>(null)
|
|
69
85
|
const [counts, setCounts] = useState<Map<string, number>>(new Map())
|
|
70
86
|
const isMobile: boolean = useMediaQuery(mediaQuery)
|
|
87
|
+
const [isOpen, setIsOpen] = useState(!isMobile)
|
|
88
|
+
// Keyed only on `direction` (static per instance), not `isOpen`/`isMobile` -
|
|
89
|
+
// those change at runtime, and swapping a child's wrapper *component type*
|
|
90
|
+
// on every toggle would unmount/remount the form fields inside it, wiping
|
|
91
|
+
// uncontrolled input values each time the drawer opens or closes. `contain`
|
|
92
|
+
// is reactive on an always-mounted FocusScope, so it's driven dynamically
|
|
93
|
+
// below instead; autoFocus/restoreFocus are handled manually further down,
|
|
94
|
+
// since react-aria's versions only fire correctly on a fresh mount.
|
|
95
|
+
const Wrapper = direction === "right" ? FocusScope : Fragment
|
|
96
|
+
const isDrawerOpen = direction === "right" && isOpen && isMobile
|
|
97
|
+
const restoreFocusRef = useRef<HTMLElement | null>(null)
|
|
98
|
+
|
|
99
|
+
// Tracks which `onSubmit` call is the most recent, so an out-of-order
|
|
100
|
+
// (slower) earlier response can't clear pending after a newer submit has
|
|
101
|
+
// already started.
|
|
102
|
+
const submitTokenRef = useRef<number>(0)
|
|
103
|
+
const [pending, setPending] = useState<boolean>(false)
|
|
104
|
+
const instantDebounceRef = useRef<ReturnType<typeof setTimeout>>(null)
|
|
71
105
|
|
|
72
106
|
const totalCount = useMemo(() => [...counts.values()].filter(n => n > 0).length, [counts])
|
|
73
107
|
|
|
@@ -82,20 +116,87 @@ const Filters = ({
|
|
|
82
116
|
if (value) next.set(key, (next.get(key) ?? 0) + 1)
|
|
83
117
|
})
|
|
84
118
|
setCounts(next)
|
|
85
|
-
onChange(event)
|
|
119
|
+
onChange?.(event)
|
|
120
|
+
|
|
121
|
+
if (!instant) return
|
|
122
|
+
|
|
123
|
+
// `event.target` is typed as the form itself (the generic param of
|
|
124
|
+
// ChangeEventHandler<HTMLFormElement>), but at runtime this fires via
|
|
125
|
+
// onChangeCapture on whichever field actually changed.
|
|
126
|
+
const target = event.target as unknown as HTMLInputElement | HTMLSelectElement
|
|
127
|
+
const isImmediate =
|
|
128
|
+
target instanceof HTMLSelectElement ||
|
|
129
|
+
(target instanceof HTMLInputElement && ["checkbox", "radio"].includes(target.type))
|
|
130
|
+
|
|
131
|
+
clearTimeout(instantDebounceRef.current)
|
|
132
|
+
if (isImmediate) {
|
|
133
|
+
form.requestSubmit()
|
|
134
|
+
} else {
|
|
135
|
+
instantDebounceRef.current = setTimeout(() => form.requestSubmit(), DEBOUNCE_MS)
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
[onChange, instant],
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
const handleSubmit = useCallback(
|
|
142
|
+
(event: SubmitEvent<HTMLFormElement>): void => {
|
|
143
|
+
const result = onSubmit?.(event)
|
|
144
|
+
if (result) {
|
|
145
|
+
const token = ++submitTokenRef.current
|
|
146
|
+
setPending(true)
|
|
147
|
+
result.finally(() => {
|
|
148
|
+
if (submitTokenRef.current === token) setPending(false)
|
|
149
|
+
})
|
|
150
|
+
}
|
|
86
151
|
},
|
|
87
|
-
[
|
|
152
|
+
[onSubmit],
|
|
88
153
|
)
|
|
89
154
|
|
|
90
155
|
const handleReset = useCallback<FormEventHandler<HTMLFormElement>>(
|
|
91
156
|
e => {
|
|
92
157
|
setCounts(new Map())
|
|
158
|
+
clearTimeout(instantDebounceRef.current)
|
|
93
159
|
onReset?.(e)
|
|
160
|
+
// The "reset" event fires *before* the browser actually clears field
|
|
161
|
+
// values (per the HTML reset algorithm), so requestSubmit() here would
|
|
162
|
+
// submit the stale, pre-reset state. Defer to the next task (reusing
|
|
163
|
+
// the debounce ref, since any pending debounce was just cleared
|
|
164
|
+
// above), by which point the native reset has already applied.
|
|
165
|
+
if (instant) {
|
|
166
|
+
instantDebounceRef.current = setTimeout(() => formRef.current?.requestSubmit(), 0)
|
|
167
|
+
}
|
|
94
168
|
},
|
|
95
|
-
[onReset],
|
|
169
|
+
[onReset, instant],
|
|
96
170
|
)
|
|
97
171
|
|
|
98
|
-
const
|
|
172
|
+
const handleToggle = useCallback(({ newState }) => {
|
|
173
|
+
setIsOpen(newState === "open")
|
|
174
|
+
}, [])
|
|
175
|
+
|
|
176
|
+
useEffect(() => () => clearTimeout(instantDebounceRef.current), [])
|
|
177
|
+
useIsomorphicLayoutEffect(() => {
|
|
178
|
+
// oxlint-disable-next-line react/set-state-in-effect -- synchronizes state with the viewport breakpoint, an external system
|
|
179
|
+
setIsOpen(!isMobile)
|
|
180
|
+
}, [isMobile])
|
|
181
|
+
|
|
182
|
+
// Replaces FocusScope's own `autoFocus`/`restoreFocus` (which only fire
|
|
183
|
+
// correctly on a fresh mount - see the Wrapper comment above): move focus
|
|
184
|
+
// into the drawer each time it opens, and restore it to whatever triggered
|
|
185
|
+
// the open each time it closes.
|
|
186
|
+
useIsomorphicLayoutEffect(() => {
|
|
187
|
+
if (!isDrawerOpen) {
|
|
188
|
+
restoreFocusRef.current?.focus()
|
|
189
|
+
restoreFocusRef.current = null
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
restoreFocusRef.current = document.activeElement as HTMLElement | null
|
|
193
|
+
formRef.current?.querySelector<HTMLElement>(FOCUSABLE_SELECTOR)?.focus()
|
|
194
|
+
}, [isDrawerOpen])
|
|
195
|
+
|
|
196
|
+
const contextValue = useMemo(
|
|
197
|
+
() => ({ counts, pending, totalCount }),
|
|
198
|
+
[counts, pending, totalCount],
|
|
199
|
+
)
|
|
99
200
|
|
|
100
201
|
const titleNode = (
|
|
101
202
|
<>
|
|
@@ -113,37 +214,52 @@ const Filters = ({
|
|
|
113
214
|
ref={formRef}
|
|
114
215
|
className="mx-filters__form mx-vertical-flow"
|
|
115
216
|
action={action}
|
|
217
|
+
aria-busy={pending}
|
|
116
218
|
onChangeCapture={handleChange}
|
|
117
|
-
onSubmit={
|
|
219
|
+
onSubmit={handleSubmit}
|
|
118
220
|
onReset={handleReset}
|
|
119
221
|
>
|
|
120
|
-
{
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
222
|
+
<Wrapper {...(direction === "right" ? { contain: isDrawerOpen } : {})}>
|
|
223
|
+
{direction === "right" && (
|
|
224
|
+
<div className="mx-filters__title">
|
|
225
|
+
{title}
|
|
226
|
+
<button
|
|
227
|
+
className="mx-button mx-button--icon-only mx-icon mx-icon--close"
|
|
228
|
+
type="button"
|
|
229
|
+
onClick={() => setIsOpen(false)}
|
|
230
|
+
>
|
|
231
|
+
Close filters
|
|
232
|
+
</button>
|
|
233
|
+
</div>
|
|
234
|
+
)}
|
|
235
|
+
{items?.map((item, i) => (
|
|
236
|
+
// Opaque ReactNode with no stable id; index is the only available key.
|
|
237
|
+
// oxlint-disable-next-line react/no-array-index-key
|
|
238
|
+
<Fragment key={i}>{item}</Fragment>
|
|
239
|
+
))}
|
|
240
|
+
{children}
|
|
241
|
+
{actions && actions.length > 0 && (
|
|
242
|
+
<div className="mx-form-actions">
|
|
243
|
+
{actions.map((actionNode, i) => (
|
|
244
|
+
// Opaque ReactNode with no stable id; index is the only available key.
|
|
245
|
+
// oxlint-disable-next-line react/no-array-index-key
|
|
246
|
+
<Fragment key={i}>{actionNode}</Fragment>
|
|
247
|
+
))}
|
|
248
|
+
</div>
|
|
249
|
+
)}
|
|
250
|
+
</Wrapper>
|
|
135
251
|
</form>
|
|
136
252
|
)
|
|
137
253
|
|
|
138
254
|
const body = collapsible ? (
|
|
139
|
-
<details className="mx-accordion" open={
|
|
255
|
+
<details className="mx-accordion" open={isOpen} onToggle={handleToggle}>
|
|
140
256
|
<summary className="mx-accordion__toggle mx-filters__title">
|
|
141
257
|
{titleNode}
|
|
142
258
|
<span
|
|
143
|
-
className={classNames(
|
|
144
|
-
"mx-icon",
|
|
145
|
-
|
|
146
|
-
)}
|
|
259
|
+
className={classNames("mx-icon", {
|
|
260
|
+
"mx-icon--chevron-right": direction === "right",
|
|
261
|
+
"mx-icon--chevron-down": direction === "down",
|
|
262
|
+
})}
|
|
147
263
|
aria-hidden="true"
|
|
148
264
|
/>
|
|
149
265
|
</summary>
|
|
@@ -41,17 +41,17 @@ import "@pnx-mixtape/mxds/src/Component/Filters/filters.entry.js"
|
|
|
41
41
|
|
|
42
42
|
**`filters.twig` props** (`Filters` type from `ids-shape`):
|
|
43
43
|
|
|
44
|
-
| Prop | Type | Required | Default | Description
|
|
45
|
-
| ------------- | ---------------------------- | -------- | --------- |
|
|
46
|
-
| `title` | `string` | yes | `Filters` | Title shown above the form, and used as the collapsed accordion summary on mobile
|
|
47
|
-
| `action` | `string` | yes | — | The form `action` attribute
|
|
48
|
-
| `items` | `FilterItem[] \| Collection` | yes | — | Rendered FilterItem strings
|
|
49
|
-
| `actions` | `Button[] \| Collection` | — | — | Submit/reset buttons rendered inside `.mx-form-actions`
|
|
50
|
-
| `collapsible` | `boolean` | — | `false` | Wraps the whole component in `<mx-accordionmobile>` so it collapses below `720px` and stays open above it
|
|
51
|
-
| `counter` | `boolean` | — | `false` | Shows a total-count tag next to the title (requires `collapsible`)
|
|
52
|
-
| `direction` | `"down" \| "right"` | — | `"down"` | On mobile: `down` collapses vertically, `right` slides in from the right as a full-height drawer
|
|
53
|
-
| `instant` | `boolean` | — | `false` | Adds `mx-filters--auto`
|
|
54
|
-
| `sticky` | `boolean` | — | `false` | Adds `mx-filters--sticky` for sticky positioning on desktop
|
|
44
|
+
| Prop | Type | Required | Default | Description |
|
|
45
|
+
| ------------- | ---------------------------- | -------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
46
|
+
| `title` | `string` | yes | `Filters` | Title shown above the form, and used as the collapsed accordion summary on mobile |
|
|
47
|
+
| `action` | `string` | yes | — | The form `action` attribute |
|
|
48
|
+
| `items` | `FilterItem[] \| Collection` | yes | — | Rendered FilterItem strings |
|
|
49
|
+
| `actions` | `Button[] \| Collection` | — | — | Submit/reset buttons rendered inside `.mx-form-actions` |
|
|
50
|
+
| `collapsible` | `boolean` | — | `false` | Wraps the whole component in `<mx-accordionmobile>` so it collapses below `720px` and stays open above it |
|
|
51
|
+
| `counter` | `boolean` | — | `false` | Shows a total-count tag next to the title (requires `collapsible`) |
|
|
52
|
+
| `direction` | `"down" \| "right"` | — | `"down"` | On mobile: `down` collapses vertically, `right` slides in from the right as a full-height drawer |
|
|
53
|
+
| `instant` | `boolean` | — | `false` | Adds `mx-filters--auto` and auto-submits the form as filters change — see [Instant mode](#instant-mode-instant-prop--mx-filters--auto) below |
|
|
54
|
+
| `sticky` | `boolean` | — | `false` | Adds `mx-filters--sticky` for sticky positioning on desktop |
|
|
55
55
|
|
|
56
56
|
**`filter-item.twig` props** (`FilterItem` type from `ids-shape` — extends `FormItem`):
|
|
57
57
|
|
|
@@ -125,7 +125,7 @@ The `<mx-filters>` custom element wraps a `<form>` and enhances it with:
|
|
|
125
125
|
|
|
126
126
|
<div class="mx-form-actions">
|
|
127
127
|
<button class="mx-button mx-button--dark" type="submit">Apply filters</button>
|
|
128
|
-
<button class="mx-button mx-button--light" type="reset">Clear all
|
|
128
|
+
<button class="mx-button mx-button--light" type="reset">Clear all</button>
|
|
129
129
|
</div>
|
|
130
130
|
</form>
|
|
131
131
|
</mx-filters>
|
|
@@ -160,7 +160,7 @@ The `<mx-filters>` custom element wraps a `<form>` and enhances it with:
|
|
|
160
160
|
|
|
161
161
|
<div class="mx-form-actions">
|
|
162
162
|
<button class="mx-button mx-button--dark" type="submit">Apply filters</button>
|
|
163
|
-
<button class="mx-button mx-button--light" type="reset">Clear all
|
|
163
|
+
<button class="mx-button mx-button--light" type="reset">Clear all</button>
|
|
164
164
|
</div>
|
|
165
165
|
</form>
|
|
166
166
|
</details>
|
|
@@ -246,7 +246,7 @@ import { FormLabel, FormCheckbox, FormText, FormDescription } from "@pnx-mixtape
|
|
|
246
246
|
Apply filters
|
|
247
247
|
</button>,
|
|
248
248
|
<button type="reset" className="mx-button mx-button--light">
|
|
249
|
-
Clear all
|
|
249
|
+
Clear all
|
|
250
250
|
</button>,
|
|
251
251
|
]}
|
|
252
252
|
/>
|
|
@@ -258,28 +258,53 @@ Active-filter counts are tracked via `FormData` on the form's `onChange` event.
|
|
|
258
258
|
|
|
259
259
|
`FilterItem` components read their per-item count from `FiltersContext`. The context is provided automatically by `<Filters>`. If you render a `FilterItem` outside of `<Filters>`, counter badges will not appear (the context gracefully returns empty counts).
|
|
260
260
|
|
|
261
|
+
The same context also exposes `pending` and `totalCount`, so you can build your own `actions` (Apply/Clear buttons) that disable themselves while a submit is pending or while no filter is active — the same two conditions the Web Component already disables on via its `count` attribute:
|
|
262
|
+
|
|
263
|
+
```tsx
|
|
264
|
+
import { useFiltersContext } from "@pnx-mixtape/mxds"
|
|
265
|
+
|
|
266
|
+
const FilterActions = () => {
|
|
267
|
+
const { pending, totalCount } = useFiltersContext()
|
|
268
|
+
const disabled = pending || totalCount === 0
|
|
269
|
+
return (
|
|
270
|
+
<>
|
|
271
|
+
<button type="submit" disabled={disabled}>
|
|
272
|
+
Apply filters
|
|
273
|
+
</button>
|
|
274
|
+
<button type="reset" disabled={disabled}>
|
|
275
|
+
Clear all
|
|
276
|
+
</button>
|
|
277
|
+
</>
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
// <Filters actions={[<FilterActions />]} ... />
|
|
281
|
+
```
|
|
282
|
+
|
|
261
283
|
```ts
|
|
262
284
|
import { useFiltersContext } from "@pnx-mixtape/mxds"
|
|
263
|
-
const { counts } = useFiltersContext()
|
|
285
|
+
const { counts, pending, totalCount } = useFiltersContext()
|
|
286
|
+
// counts: Map<fieldName, activeCount>
|
|
287
|
+
// pending: boolean — true while an onSubmit promise is in flight
|
|
288
|
+
// totalCount: number — filter groups with at least one active value, 0 = empty
|
|
264
289
|
```
|
|
265
290
|
|
|
266
291
|
**`<Filters>` props:**
|
|
267
292
|
|
|
268
|
-
| Prop | Type | Default | Description
|
|
269
|
-
| ------------- | ------------------ | ------- |
|
|
270
|
-
| `title` | `string` | — | Title shown above the form / inside the collapsible `<summary>`
|
|
271
|
-
| `action` | `string` | — | `action` on the `<form>`
|
|
272
|
-
| `items` | `ReactNode[]` | — | `FilterItem` components to render inside the form
|
|
273
|
-
| `actions` | `ReactNode[]` | — | Submit/reset buttons rendered inside `.mx-form-actions`
|
|
274
|
-
| `collapsible` | `boolean` | `false` | Wraps title + form in a `<details>` accordion — collapses on mobile
|
|
275
|
-
| `counter` | `boolean` | `false` | Shows a total active-filter count tag next to the title
|
|
276
|
-
| `direction` | `"down"` | — | Layout variant
|
|
277
|
-
| `instant` | `boolean` | `false` | Adds `mx-filters--auto`
|
|
278
|
-
| `sticky` | `boolean` | `false` | Adds `mx-filters--sticky` for sticky desktop positioning
|
|
279
|
-
| `onSubmit` | `FormEventHandler` | — | Form submit handler
|
|
280
|
-
| `onReset` | `FormEventHandler` | — | Form reset handler (also clears internal counts)
|
|
281
|
-
| `className` | `string` | — | Extra class on the outer `<div>`
|
|
282
|
-
| `children` | `ReactNode` | — | Rendered inside the form alongside `items`
|
|
293
|
+
| Prop | Type | Default | Description |
|
|
294
|
+
| ------------- | ------------------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
295
|
+
| `title` | `string` | — | Title shown above the form / inside the collapsible `<summary>` |
|
|
296
|
+
| `action` | `string` | — | `action` on the `<form>` |
|
|
297
|
+
| `items` | `ReactNode[]` | — | `FilterItem` components to render inside the form |
|
|
298
|
+
| `actions` | `ReactNode[]` | — | Submit/reset buttons rendered inside `.mx-form-actions` |
|
|
299
|
+
| `collapsible` | `boolean` | `false` | Wraps title + form in a `<details>` accordion — collapses on mobile |
|
|
300
|
+
| `counter` | `boolean` | `false` | Shows a total active-filter count tag next to the title |
|
|
301
|
+
| `direction` | `"down"` | — | Layout variant for mobile. |
|
|
302
|
+
| `instant` | `boolean` | `false` | Adds `mx-filters--auto` and auto-submits the form as filters change — see [Instant mode](#instant-mode-instant-prop--mx-filters--auto) below |
|
|
303
|
+
| `sticky` | `boolean` | `false` | Adds `mx-filters--sticky` for sticky desktop positioning |
|
|
304
|
+
| `onSubmit` | `FormEventHandler` | — | Form submit handler |
|
|
305
|
+
| `onReset` | `FormEventHandler` | — | Form reset handler (also clears internal counts) |
|
|
306
|
+
| `className` | `string` | — | Extra class on the outer `<div>` |
|
|
307
|
+
| `children` | `ReactNode` | — | Rendered inside the form alongside `items` |
|
|
283
308
|
|
|
284
309
|
**`<FilterItem>` props:**
|
|
285
310
|
|
|
@@ -300,15 +325,55 @@ const { counts } = useFiltersContext() // Map<fieldName, activeCount>
|
|
|
300
325
|
|
|
301
326
|
---
|
|
302
327
|
|
|
328
|
+
## Instant mode (`instant` prop / `mx-filters--auto`)
|
|
329
|
+
|
|
330
|
+
When `instant` is `true`, the form submits itself as filters change instead of waiting for a manual Apply click:
|
|
331
|
+
|
|
332
|
+
- **Immediately** for checkbox/radio/select fields (a discrete choice)
|
|
333
|
+
- **Debounced (~400ms)** for text fields, so a fast typist doesn't trigger a submit per keystroke — checking a box or picking a select option while a debounce is pending cancels it and submits right away with the latest state instead
|
|
334
|
+
- **On reset** — clearing all filters re-submits the (now-empty) form
|
|
335
|
+
|
|
336
|
+
This works identically in React and the Web Component, and both go through the exact same `form.requestSubmit()` a manual Apply click already triggers.
|
|
337
|
+
|
|
338
|
+
**You still need to intercept the form's `submit` event and call `event.preventDefault()`**, exactly as required for manual submission — this is not new, but it matters much more here: forgetting it means a real page navigation on every keystroke once the debounce fires, not just on an occasional button click.
|
|
339
|
+
|
|
340
|
+
```tsx
|
|
341
|
+
// React
|
|
342
|
+
<Filters
|
|
343
|
+
instant
|
|
344
|
+
onSubmit={event => {
|
|
345
|
+
event.preventDefault()
|
|
346
|
+
fetchResults(new FormData(event.currentTarget))
|
|
347
|
+
}}
|
|
348
|
+
/>
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
```js
|
|
352
|
+
// Web Component / Twig — the consumer wires the same interception manually
|
|
353
|
+
document.querySelector("mx-filters form").addEventListener("submit", event => {
|
|
354
|
+
event.preventDefault()
|
|
355
|
+
fetchResults(new FormData(event.target))
|
|
356
|
+
})
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Two more things worth knowing:
|
|
360
|
+
|
|
361
|
+
- `requestSubmit()` runs the browser's native constraint validation, exactly like a real submit-button click — a `required`/`pattern` field that's currently invalid will silently block the auto-submit (and show the native validation bubble) instead of firing `submit`. Avoid strict native validation attributes on instant-mode fields, or validate yourself instead.
|
|
362
|
+
- The debounce delay (400ms) is a fixed constant in both implementations today, not exposed as a prop.
|
|
363
|
+
|
|
364
|
+
Instant mode pairs naturally with the built-in pending state (`onSubmit`'s return value in React; the `pending` property on the Web Component) — auto-submits flow through the same `submit` pipeline, so `aria-busy` and the spinner work unchanged.
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
303
368
|
## Modifiers
|
|
304
369
|
|
|
305
|
-
| Class | Prop value | Visual intent
|
|
306
|
-
| -------------------- | ------------------------------------------ |
|
|
307
|
-
| `mx-filters` | (base) | Default static layout: title + form, always visible
|
|
308
|
-
| `mx-filters--down` | `direction: "down"` + `collapsible: true` | Mobile-only accordion collapse, chevron rotates vertically
|
|
309
|
-
| `mx-filters--right` | `direction: "right"` + `collapsible: true` | Mobile-only off-canvas drawer sliding from the right
|
|
310
|
-
| `mx-filters--auto` | `instant: true` |
|
|
311
|
-
| `mx-filters--sticky` | `sticky: true` | Sticky positioning hook on desktop
|
|
370
|
+
| Class | Prop value | Visual intent |
|
|
371
|
+
| -------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
372
|
+
| `mx-filters` | (base) | Default static layout: title + form, always visible |
|
|
373
|
+
| `mx-filters--down` | `direction: "down"` + `collapsible: true` | Mobile-only accordion collapse, chevron rotates vertically |
|
|
374
|
+
| `mx-filters--right` | `direction: "right"` + `collapsible: true` | Mobile-only off-canvas drawer sliding from the right |
|
|
375
|
+
| `mx-filters--auto` | `instant: true` | Style hook + drives real auto-submit-on-change/reset behaviour — see [Instant mode](#instant-mode-instant-prop--mx-filters--auto) |
|
|
376
|
+
| `mx-filters--sticky` | `sticky: true` | Sticky positioning hook on desktop |
|
|
312
377
|
|
|
313
378
|
FilterItem classes:
|
|
314
379
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
2
|
|
|
3
|
-
exports[`Collapsible 1`] = `"<div class="mx-accordion mx-filter-item" data-open="true"><button type="button" class="mx-accordion__toggle" id="filter-categories-container" aria-expanded="true">Categories<span class="mx-icon mx-icon--chevron-down" aria-hidden="true"></span></button><section aria-labelledby="filter-categories-container" class="mx-accordion__content mx-rich-text mx-vertical-flow"><div class="mx-form-item mx-accordion__content" id="filter-categories"><fieldset class="mx-form-item mx-fieldset" aria-label="Categories" role="group" id="react-aria-_r_1_"><div class="mx-checkbox"><input id="_r_6_" data-react-aria-pressable="true" tabindex="0" type="checkbox" value="news" name="categories"><label for="_r_6_">News</label></div><div class="mx-checkbox"><input id="_r_9_" data-react-aria-pressable="true" tabindex="0" type="checkbox" value="media-releases" name="categories"><label for="_r_9_">Media Releases</label></div><div class="mx-checkbox"><input id="_r_c_" data-react-aria-pressable="true" tabindex="0" type="checkbox" value="publications" name="categories"><label for="_r_c_">Publications</label></div></fieldset></div></section></div>"`;
|
|
3
|
+
exports[`Collapsible 1`] = `"<div class="mx-accordion mx-filter-item" data-open="true"><button type="button" class="mx-accordion__toggle" id="filter-categories-container" aria-expanded="true">Categories<span class="mx-icon mx-icon--chevron-down" aria-hidden="true"></span></button><section aria-labelledby="filter-categories-container" class="mx-accordion__content mx-rich-text mx-vertical-flow"><div class="mx-form-item mx-accordion__content" id="filter-categories-item"><fieldset class="mx-form-item mx-fieldset" aria-label="Categories" role="group" id="react-aria-_r_1_"><div class="mx-checkbox"><input id="_r_6_" data-react-aria-pressable="true" tabindex="0" type="checkbox" value="news" name="categories"><label for="_r_6_">News</label></div><div class="mx-checkbox"><input id="_r_9_" data-react-aria-pressable="true" tabindex="0" type="checkbox" value="media-releases" name="categories"><label for="_r_9_">Media Releases</label></div><div class="mx-checkbox"><input id="_r_c_" data-react-aria-pressable="true" tabindex="0" type="checkbox" value="publications" name="categories"><label for="_r_c_">Publications</label></div></fieldset></div></section></div>"`;
|
|
4
4
|
|
|
5
|
-
exports[`Filter Item Default 1`] = `"<div class="mx-form-item mx-filter-item" id="filter-keywords"><label class="mx-label" for="filter-keywords">Keywords</label><div id="help-filter-keywords" class="mx-description">Search by keywords</div><input class="mx-input__text" id="filter-keywords" aria-describedby="help-filter-keywords" aria-invalid="false" aria-label="Keywords" type="text" value="" name="keywords"></div>"`;
|
|
5
|
+
exports[`Filter Item Default 1`] = `"<div class="mx-form-item mx-filter-item" id="filter-keywords-item"><label class="mx-label" for="filter-keywords">Keywords</label><div id="help-filter-keywords" class="mx-description">Search by keywords</div><input class="mx-input__text" id="filter-keywords" aria-describedby="help-filter-keywords" aria-invalid="false" aria-label="Keywords" type="text" value="" name="keywords"></div>"`;
|
|
6
6
|
|
|
7
|
-
exports[`Shown Items 1`] = `"<div class="mx-form-item mx-filter-item" id="filter-categories"><label class="mx-label" for="filter-categories">Categories</label><fieldset class="mx-form-item mx-fieldset" aria-label="Categories" role="group" id="react-aria-_r_f_"><div class="mx-checkbox"><input id="_r_k_" data-react-aria-pressable="true" tabindex="0" type="checkbox" value="news" name="categories"><label for="_r_k_">News</label></div><div class="mx-checkbox"><input id="_r_n_" data-react-aria-pressable="true" tabindex="0" type="checkbox" value="media-releases" name="categories"><label for="_r_n_">Media Releases</label></div><div class="mx-checkbox"><input id="_r_q_" data-react-aria-pressable="true" tabindex="0" type="checkbox" value="publications" name="categories"><label for="_r_q_">Publications</label></div></fieldset></div>"`;
|
|
7
|
+
exports[`Shown Items 1`] = `"<div class="mx-form-item mx-filter-item" id="filter-categories-item"><label class="mx-label" for="filter-categories">Categories</label><fieldset class="mx-form-item mx-fieldset" aria-label="Categories" role="group" id="react-aria-_r_f_"><div class="mx-checkbox"><input id="_r_k_" data-react-aria-pressable="true" tabindex="0" type="checkbox" value="news" name="categories"><label for="_r_k_">News</label></div><div class="mx-checkbox"><input id="_r_n_" data-react-aria-pressable="true" tabindex="0" type="checkbox" value="media-releases" name="categories"><label for="_r_n_">Media Releases</label></div><div class="mx-checkbox"><input id="_r_q_" data-react-aria-pressable="true" tabindex="0" type="checkbox" value="publications" name="categories"><label for="_r_q_">Publications</label></div></fieldset></div>"`;
|
|
@@ -82,7 +82,7 @@ exports[`Collapsible Counter 1`] = `
|
|
|
82
82
|
|
|
83
83
|
<button class="mx-button mx-button--light" type="reset">
|
|
84
84
|
|
|
85
|
-
<span>Clear all
|
|
85
|
+
<span>Clear all</span>
|
|
86
86
|
|
|
87
87
|
</button>
|
|
88
88
|
|
|
@@ -177,7 +177,7 @@ exports[`Collapsible Right 1`] = `
|
|
|
177
177
|
|
|
178
178
|
<button class="mx-button mx-button--light" type="reset" disabled="">
|
|
179
179
|
|
|
180
|
-
<span>Clear all
|
|
180
|
+
<span>Clear all</span>
|
|
181
181
|
|
|
182
182
|
</button>
|
|
183
183
|
|
|
@@ -257,7 +257,7 @@ exports[`Filters 1`] = `
|
|
|
257
257
|
|
|
258
258
|
<button class="mx-button mx-button--light" type="reset" disabled="">
|
|
259
259
|
|
|
260
|
-
<span>Clear all
|
|
260
|
+
<span>Clear all</span>
|
|
261
261
|
|
|
262
262
|
</button>
|
|
263
263
|
|
|
@@ -329,7 +329,85 @@ exports[`Instant 1`] = `
|
|
|
329
329
|
|
|
330
330
|
<button class="mx-button mx-button--light" type="reset" disabled="">
|
|
331
331
|
|
|
332
|
-
<span>Clear all
|
|
332
|
+
<span>Clear all</span>
|
|
333
|
+
|
|
334
|
+
</button>
|
|
335
|
+
|
|
336
|
+
</div>
|
|
337
|
+
</form>
|
|
338
|
+
|
|
339
|
+
</mx-filters>
|
|
340
|
+
"
|
|
341
|
+
`;
|
|
342
|
+
|
|
343
|
+
exports[`Pending 1`] = `
|
|
344
|
+
"<mx-filters class="mx-filters" count="0">
|
|
345
|
+
|
|
346
|
+
<h2 class="mx-filters__title">
|
|
347
|
+
Filters
|
|
348
|
+
</h2>
|
|
349
|
+
|
|
350
|
+
<form class="mx-filters__form mx-vertical-flow" action="#" aria-busy="false">
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
<div class="mx-form-item mx-filter-item">
|
|
354
|
+
|
|
355
|
+
<label class="mx-label" for="filter-keywords">
|
|
356
|
+
Keywords
|
|
357
|
+
</label>
|
|
358
|
+
|
|
359
|
+
<div class="mx-description" id="unique-0">
|
|
360
|
+
Search by keywords
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
<input class="mx-input__text" id="filter-keywords" name="unique-0" type="text" value="" aria-describedby="unique-0">
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
</div>
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
<fieldset class="mx-form-item mx-fieldset mx-filter-item">
|
|
373
|
+
|
|
374
|
+
<legend class="mx-legend">
|
|
375
|
+
Categories
|
|
376
|
+
</legend>
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
<div class="mx-checkbox">
|
|
381
|
+
<input id="category-news" type="checkbox" name="categories" value="news">
|
|
382
|
+
<label for="category-news">News</label>
|
|
383
|
+
</div>
|
|
384
|
+
|
|
385
|
+
<div class="mx-checkbox">
|
|
386
|
+
<input id="category-media" type="checkbox" name="categories" value="media">
|
|
387
|
+
<label for="category-media">Media Releases</label>
|
|
388
|
+
</div>
|
|
389
|
+
|
|
390
|
+
<div class="mx-checkbox">
|
|
391
|
+
<input id="category-publications" type="checkbox" name="categories" value="publications">
|
|
392
|
+
<label for="category-publications">Publications</label>
|
|
393
|
+
</div>
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
</fieldset>
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
<div class="mx-form-actions">
|
|
401
|
+
|
|
402
|
+
<button class="mx-button mx-button--dark" type="submit" disabled="">
|
|
403
|
+
|
|
404
|
+
<span>Apply filters</span>
|
|
405
|
+
|
|
406
|
+
</button>
|
|
407
|
+
|
|
408
|
+
<button class="mx-button mx-button--light" type="reset" disabled="">
|
|
409
|
+
|
|
410
|
+
<span>Clear all</span>
|
|
333
411
|
|
|
334
412
|
</button>
|
|
335
413
|
|