Skip to content

Commit 1a24fb2

Browse files
feat(docs-framework): add high contrast theme switcher (#4749)
* feat(docs-framework): add high contrast theme switcher * feat(theme): refactor useTheme hook * fix(theme): add missing browser check * feat(theme): componentize theme selector * chore(theme): Rename hasDarkThemeSwitcher to hasThemeSwitcher * fix(theme): a11y improvements * chore(screenshots): update screenshots
1 parent 4b5a372 commit 1a24fb2

File tree

268 files changed

+347
-359
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

268 files changed

+347
-359
lines changed

packages/documentation-framework/components/example/example.js

Lines changed: 50 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
1-
import React, { useContext, useEffect, useState, useCallback } from 'react';
1+
import React, { useContext, useEffect } from 'react';
22
import { useLocation } from '@reach/router';
33
import {
44
Button,
55
CodeBlock,
66
Flex,
77
CodeBlockCode,
88
debounce,
9-
Icon,
109
Label,
1110
Switch,
12-
Select,
13-
SelectOption,
14-
SelectList,
15-
MenuToggle,
1611
Tooltip,
1712
Stack,
18-
StackItem
13+
StackItem,
1914
} from '@patternfly/react-core';
2015
import * as reactCoreModule from '@patternfly/react-core';
2116
import * as reactCoreNextModule from '@patternfly/react-core/next';
@@ -24,9 +19,6 @@ import * as reactTableModule from '@patternfly/react-table';
2419
import * as reactTableDeprecatedModule from '@patternfly/react-table/deprecated';
2520
import { css } from '@patternfly/react-styles';
2621
import { getParameters } from 'codesandbox/lib/api/define';
27-
const SunIcon = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" className="pf-v6-svg" fill="var(--pf-t--global--icon--color--regular)"><path d="M16 25c-4.963 0-9-4.038-9-9s4.037-9 9-9 9 4.038 9 9-4.037 9-9 9Zm0-16c-3.86 0-7 3.14-7 7s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7Zm0-4a1 1 0 0 1-1-1V1a1 1 0 1 1 2 0v3a1 1 0 0 1-1 1Zm0 27a1 1 0 0 1-1-1v-3a1 1 0 1 1 2 0v3a1 1 0 0 1-1 1ZM4 17H1a1 1 0 1 1 0-2h3a1 1 0 1 1 0 2Zm27 0h-3a1 1 0 1 1 0-2h3a1 1 0 1 1 0 2ZM5.394 27.606a1 1 0 0 1-.707-1.707l2.12-2.12a1 1 0 1 1 1.415 1.413L6.1 27.313a.997.997 0 0 1-.707.293ZM24.485 8.515a1 1 0 0 1-.707-1.707L25.9 4.686a1 1 0 1 1 1.415 1.415l-2.122 2.12a.997.997 0 0 1-.707.294Zm-16.97 0a.997.997 0 0 1-.707-.293L4.686 6.1a1 1 0 1 1 1.415-1.415l2.12 2.122a1 1 0 0 1-.706 1.707Zm19.091 19.091a.997.997 0 0 1-.707-.293l-2.12-2.12a1 1 0 1 1 1.413-1.415l2.122 2.121a1 1 0 0 1-.707 1.707Z"></path></svg>;
28-
const MoonIcon = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" className="pf-v6-svg" fill="var(--pf-t--global--icon--color--regular)"><path d="M16.457 30C8.485 30 2 23.515 2 15.543c0-5.93 3.715-11.345 9.243-13.476a.999.999 0 0 1 1.289 1.3 12.185 12.185 0 0 0-.843 4.487c0 6.869 5.588 12.457 12.457 12.457 1.56 0 3.07-.284 4.487-.844a.998.998 0 0 1 1.3 1.29C27.802 26.285 22.387 30 16.456 30ZM9.992 4.904C6.338 7.134 4 11.177 4 15.544 4 22.412 9.588 28 16.457 28c4.367 0 8.41-2.338 10.639-5.992a14.39 14.39 0 0 1-2.95.302c-7.971 0-14.457-6.485-14.457-14.456 0-1.003.102-1.989.303-2.95Z"></path></svg>;
29-
const DesktopIcon = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" className="pf-v6-svg" fill="var(--pf-t--global--icon--color--regular)"><path d="M23.94 16a1 1 0 0 1-.992-.876 6.957 6.957 0 0 0-6.069-6.062 1 1 0 1 1 .242-1.985 8.953 8.953 0 0 1 7.812 7.8A.999.999 0 0 1 23.94 16ZM16 5a1 1 0 0 1-1-1V1a1 1 0 1 1 2 0v3a1 1 0 0 1-1 1Zm0 27a1 1 0 0 1-1-1v-3a1 1 0 1 1 2 0v3a1 1 0 0 1-1 1ZM4 17H1a1 1 0 1 1 0-2h3a1 1 0 1 1 0 2Zm27 0h-3a1 1 0 1 1 0-2h3a1 1 0 1 1 0 2ZM5.394 27.606a1 1 0 0 1-.707-1.707l2.12-2.12a1 1 0 1 1 1.415 1.413L6.1 27.313a.997.997 0 0 1-.707.293ZM24.485 8.515a1 1 0 0 1-.707-1.707L25.9 4.686a1 1 0 1 1 1.415 1.415l-2.122 2.12a.997.997 0 0 1-.707.294Zm-16.97 0a.997.997 0 0 1-.707-.293L4.686 6.1a1 1 0 1 1 1.415-1.415l2.12 2.122a1 1 0 0 1-.706 1.707Zm19.091 19.091a.997.997 0 0 1-.707-.293l-2.12-2.12a1 1 0 1 1 1.413-1.415l2.122 2.121a1 1 0 0 1-.707 1.707ZM16 24.875c-4.894 0-8.875-3.981-8.875-8.875a8.879 8.879 0 0 1 5.227-8.088.876.876 0 0 1 1.153 1.163 6.945 6.945 0 0 0-.63 2.925A7.133 7.133 0 0 0 20 19.125a6.948 6.948 0 0 0 2.925-.63.876.876 0 0 1 1.163 1.154A8.88 8.88 0 0 1 16 24.875Zm-4.785-14.153A7.135 7.135 0 0 0 8.875 16 7.133 7.133 0 0 0 16 23.125a7.13 7.13 0 0 0 5.278-2.34c-.419.06-.845.09-1.278.09-4.894 0-8.875-3.981-8.875-8.875 0-.433.03-.86.09-1.278Z"></path></svg>;
3022
import { ExampleToolbar } from './exampleToolbar.jsx';
3123
import { AutoLinkHeader } from '../autoLinkHeader/autoLinkHeader';
3224
import {
@@ -35,92 +27,15 @@ import {
3527
getReactParams,
3628
getExampleClassName,
3729
getExampleId,
38-
liveCodeTypes,
30+
liveCodeTypes
3931
} from '../../helpers';
4032
import { convertToReactComponent } from '@patternfly/ast-helpers';
4133
import missingThumbnail from './missing-thumbnail.jpg';
4234
import { RtlContext } from '../../layouts';
43-
import { useTheme } from '../../hooks/useTheme';
35+
import { ThemeSelector } from '../themeSelector/themeSelector';
4436

4537
const errorComponent = (err) => <pre>{err.toString()}</pre>;
4638

47-
// Full-screen theme selector component using shared theme hook
48-
const FullScreenThemeSelector = () => {
49-
const { themeMode, setThemeMode, THEME_MODES } = useTheme();
50-
const [isThemeSelectOpen, setIsThemeSelectOpen] = useState(false);
51-
52-
const handleThemeChange = (_event, selectedMode) => {
53-
setThemeMode(selectedMode);
54-
setIsThemeSelectOpen(false);
55-
};
56-
57-
const getThemeDisplayText = (mode) => {
58-
switch (mode) {
59-
case THEME_MODES.LIGHT:
60-
return 'Light';
61-
case THEME_MODES.DARK:
62-
return 'Dark';
63-
default:
64-
return 'System';
65-
}
66-
};
67-
68-
const getThemeIcon = (mode) => {
69-
switch (mode) {
70-
case THEME_MODES.LIGHT:
71-
return SunIcon;
72-
case THEME_MODES.DARK:
73-
return MoonIcon;
74-
default:
75-
return DesktopIcon;
76-
}
77-
};
78-
79-
return (
80-
<Select
81-
id="ws-example-theme-select"
82-
isOpen={isThemeSelectOpen}
83-
selected={themeMode}
84-
onSelect={handleThemeChange}
85-
onOpenChange={(isOpen) => setIsThemeSelectOpen(isOpen)}
86-
toggle={(toggleRef) => (
87-
<MenuToggle
88-
ref={toggleRef}
89-
onClick={() => setIsThemeSelectOpen(!isThemeSelectOpen)}
90-
isExpanded={isThemeSelectOpen}
91-
icon={<Icon size="lg">{getThemeIcon(themeMode)}</Icon>}
92-
aria-label={`Theme selection, current: ${getThemeDisplayText(themeMode)}`}
93-
/>
94-
)}
95-
shouldFocusToggleOnSelect
96-
>
97-
<SelectList>
98-
<SelectOption
99-
value={THEME_MODES.SYSTEM}
100-
icon={DesktopIcon}
101-
description="Follow system preference"
102-
>
103-
System
104-
</SelectOption>
105-
<SelectOption
106-
value={THEME_MODES.LIGHT}
107-
icon={SunIcon}
108-
description="Always use light mode"
109-
>
110-
Light
111-
</SelectOption>
112-
<SelectOption
113-
value={THEME_MODES.DARK}
114-
icon={MoonIcon}
115-
description="Always use dark mode"
116-
>
117-
Dark
118-
</SelectOption>
119-
</SelectList>
120-
</Select>
121-
);
122-
};
123-
12439
class ErrorBoundary extends React.Component {
12540
constructor(props) {
12641
super(props);
@@ -131,7 +46,7 @@ class ErrorBoundary extends React.Component {
13146
errorInfo._suppressLogging = true;
13247
this.setState({
13348
error: error,
134-
errorInfo: errorInfo,
49+
errorInfo: errorInfo
13550
});
13651
}
13752

@@ -183,15 +98,15 @@ export const Example = ({
18398
// Content that appears between h3 and code block to explain example
18499
children,
185100
// Show dark theme switcher on full page examples
186-
hasDarkThemeSwitcher = process.env.hasDarkThemeSwitcher,
101+
hasThemeSwitcher = process.env.hasThemeSwitcher,
187102
// Show dark theme switcher on full page examples
188103
hasRTLSwitcher = process.env.hasRTLSwitcher,
189104
// Map of relative imports matched to their npm package import path (passed to Codesandbox)
190105
relativeImports,
191106
// md file location in node_modules, used to resolve relative import paths in examples
192107
relPath = '',
193108
// absolute url to hosted file
194-
sourceLink = '',
109+
sourceLink = ''
195110
}) => {
196111
if (isFullscreenPreview) {
197112
isFullscreen = false;
@@ -202,9 +117,7 @@ export const Example = ({
202117
useEffect(() => {
203118
if (!isFullscreenPreview) return;
204119

205-
document.readyState === 'complete'
206-
? addPageLoadedClass()
207-
: window.addEventListener('load', addPageLoadedClass);
120+
document.readyState === 'complete' ? addPageLoadedClass() : window.addEventListener('load', addPageLoadedClass);
208121

209122
return () => window.removeEventListener('load', addPageLoadedClass);
210123
}, []);
@@ -230,9 +143,7 @@ export const Example = ({
230143
...reactCoreModule,
231144
...reactTableModule,
232145
...(source === 'react-next' ? reactCoreNextModule : {}),
233-
...(source === 'react-deprecated'
234-
? { ...reactCoreDeprecatedModule, ...reactTableDeprecatedModule }
235-
: {}),
146+
...(source === 'react-deprecated' ? { ...reactCoreDeprecatedModule, ...reactTableDeprecatedModule } : {})
236147
};
237148

238149
let livePreview = null;
@@ -248,8 +159,7 @@ export const Example = ({
248159
);
249160
} else {
250161
try {
251-
const { code: transformedCode, hasTS } =
252-
convertToReactComponent(editorCode);
162+
const { code: transformedCode, hasTS } = convertToReactComponent(editorCode);
253163
if (hasTS) {
254164
lang = 'ts';
255165
} else {
@@ -259,11 +169,7 @@ export const Example = ({
259169
const componentNames = Object.keys(scope);
260170
const componentValues = Object.values(scope);
261171

262-
const getPreviewComponent = new Function(
263-
'React',
264-
...componentNames,
265-
transformedCode
266-
);
172+
const getPreviewComponent = new Function('React', ...componentNames, transformedCode);
267173
const PreviewComponent = getPreviewComponent(React, ...componentValues);
268174

269175
livePreview = (
@@ -282,13 +188,13 @@ export const Example = ({
282188
return (
283189
<div id={previewId} className={css(className, 'pf-v6-u-h-100')}>
284190
{livePreview}
285-
{(hasDarkThemeSwitcher || hasRTLSwitcher) && (
191+
{(hasThemeSwitcher || hasRTLSwitcher) && (
286192
<Flex
287193
direction={{ default: 'column' }}
288194
gap={{ default: 'gapMd' }}
289195
className="ws-full-page-utils pf-v6-m-dir-ltr"
290196
>
291-
{hasDarkThemeSwitcher && <FullScreenThemeSelector />}
197+
{hasThemeSwitcher && <ThemeSelector id="ws-example-theme-select" />}
292198
{hasRTLSwitcher && (
293199
<Switch
294200
id="ws-example-rtl-switch"
@@ -310,61 +216,49 @@ export const Example = ({
310216
const codeBoxParams = getParameters(
311217
lang === 'html'
312218
? getStaticParams(title, editorCode)
313-
: getReactParams(
314-
title,
315-
editorCode,
316-
scope,
317-
lang,
318-
relativeImports,
319-
relPath,
320-
sourceLink
321-
)
219+
: getReactParams(title, editorCode, scope, lang, relativeImports, relPath, sourceLink)
322220
);
323221
const fullscreenLink =
324-
loc.pathname.replace(/\/$/, '') +
325-
(loc.pathname.endsWith(source) ? '' : `/${source}`) +
326-
'/' +
327-
slugger(title);
222+
loc.pathname.replace(/\/$/, '') + (loc.pathname.endsWith(source) ? '' : `/${source}`) + '/' + slugger(title);
328223

329224
const hasMetaText = isBeta || isDemo || isDeprecated || false;
330-
const tooltips = (<React.Fragment>
331-
{isBeta && (
332-
<Tooltip content="This beta component is currently under review and is still open for further evolution.">
333-
<Button variant="plain" hasNoPadding>
334-
<Label isCompact color="blue">
335-
Beta
336-
</Label>
337-
</Button>
338-
</Tooltip>
339-
)}
340-
{isDemo && (
341-
<Tooltip content="Demos show how multiple components can be used in a single design.">
342-
<Button variant="plain" hasNoPadding>
343-
<Label isCompact color="purple">
344-
Demo
345-
</Label>
346-
</Button>
347-
</Tooltip>
348-
)}
349-
{isDeprecated && (
350-
<Tooltip content="Deprecated components are available for use but are no longer being maintained or enhanced.">
351-
<Button variant="plain" hasNoPadding>
352-
<Label isCompact color="grey">
353-
Deprecated
354-
</Label>
355-
</Button>
356-
</Tooltip>
357-
)}
358-
</React.Fragment>);
359-
const metaText = hasMetaText && tooltips
225+
const tooltips = (
226+
<React.Fragment>
227+
{isBeta && (
228+
<Tooltip content="This beta component is currently under review and is still open for further evolution.">
229+
<Button variant="plain" hasNoPadding>
230+
<Label isCompact color="blue">
231+
Beta
232+
</Label>
233+
</Button>
234+
</Tooltip>
235+
)}
236+
{isDemo && (
237+
<Tooltip content="Demos show how multiple components can be used in a single design.">
238+
<Button variant="plain" hasNoPadding>
239+
<Label isCompact color="purple">
240+
Demo
241+
</Label>
242+
</Button>
243+
</Tooltip>
244+
)}
245+
{isDeprecated && (
246+
<Tooltip content="Deprecated components are available for use but are no longer being maintained or enhanced.">
247+
<Button variant="plain" hasNoPadding>
248+
<Label isCompact color="grey">
249+
Deprecated
250+
</Label>
251+
</Button>
252+
</Tooltip>
253+
)}
254+
</React.Fragment>
255+
);
256+
const metaText = hasMetaText && tooltips;
360257

361258
return (
362259
<Stack hasGutter>
363260
<StackItem>
364-
<AutoLinkHeader
365-
metaText={metaText}
366-
headingLevel="h3"
367-
>
261+
<AutoLinkHeader metaText={metaText} headingLevel="h3">
368262
{title}
369263
</AutoLinkHeader>
370264
{children}
@@ -378,22 +272,11 @@ export const Example = ({
378272
target="_blank"
379273
aria-label={`Open fullscreen ${title} example`}
380274
>
381-
<img
382-
src={thumbnail.src}
383-
width={thumbnail.width}
384-
height={thumbnail.height}
385-
alt={`${title} screenshot`}
386-
/>
275+
<img src={thumbnail.src} width={thumbnail.width} height={thumbnail.height} alt={`${title} screenshot`} />
387276
</a>
388277
</div>
389278
) : (
390-
<div
391-
id={previewId}
392-
className={css(
393-
className,
394-
isRTL && 'pf-v6-m-dir-rtl'
395-
)}
396-
>
279+
<div id={previewId} className={css(className, isRTL && 'pf-v6-m-dir-rtl')}>
397280
{livePreview}
398281
</div>
399282
)}

packages/documentation-framework/components/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export * from './topNav/topNav';
1010
export * from './link/link';
1111
export * from './tableOfContents/tableOfContents';
1212
export * from './inlineAlert/inlineAlert';
13+
export * from './themeSelector/themeSelector';

0 commit comments

Comments
 (0)