@patternfly/documentation-framework 6.18.7 → 6.19.0

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/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 6.19.0 (2025-08-26)
7
+
8
+
9
+ ### Features
10
+
11
+ * **docs-framework:** add high contrast theme switcher ([#4749](https://github.com/patternfly/patternfly-org/issues/4749)) ([1a24fb2](https://github.com/patternfly/patternfly-org/commit/1a24fb234bbaee2d569c16b4b1a90609fe006ddf))
12
+
13
+
14
+
15
+
16
+
6
17
  ## 6.18.7 (2025-08-19)
7
18
 
8
19
 
@@ -1,4 +1,4 @@
1
- import React, { useContext, useEffect, useState, useCallback } from 'react';
1
+ import React, { useContext, useEffect } from 'react';
2
2
  import { useLocation } from '@reach/router';
3
3
  import {
4
4
  Button,
@@ -6,16 +6,11 @@ import {
6
6
  Flex,
7
7
  CodeBlockCode,
8
8
  debounce,
9
- Icon,
10
9
  Label,
11
10
  Switch,
12
- Select,
13
- SelectOption,
14
- SelectList,
15
- MenuToggle,
16
11
  Tooltip,
17
12
  Stack,
18
- StackItem
13
+ StackItem,
19
14
  } from '@patternfly/react-core';
20
15
  import * as reactCoreModule from '@patternfly/react-core';
21
16
  import * as reactCoreNextModule from '@patternfly/react-core/next';
@@ -24,9 +19,6 @@ import * as reactTableModule from '@patternfly/react-table';
24
19
  import * as reactTableDeprecatedModule from '@patternfly/react-table/deprecated';
25
20
  import { css } from '@patternfly/react-styles';
26
21
  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>;
30
22
  import { ExampleToolbar } from './exampleToolbar.jsx';
31
23
  import { AutoLinkHeader } from '../autoLinkHeader/autoLinkHeader';
32
24
  import {
@@ -35,92 +27,15 @@ import {
35
27
  getReactParams,
36
28
  getExampleClassName,
37
29
  getExampleId,
38
- liveCodeTypes,
30
+ liveCodeTypes
39
31
  } from '../../helpers';
40
32
  import { convertToReactComponent } from '@patternfly/ast-helpers';
41
33
  import missingThumbnail from './missing-thumbnail.jpg';
42
34
  import { RtlContext } from '../../layouts';
43
- import { useTheme } from '../../hooks/useTheme';
35
+ import { ThemeSelector } from '../themeSelector/themeSelector';
44
36
 
45
37
  const errorComponent = (err) => <pre>{err.toString()}</pre>;
46
38
 
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
-
124
39
  class ErrorBoundary extends React.Component {
125
40
  constructor(props) {
126
41
  super(props);
@@ -131,7 +46,7 @@ class ErrorBoundary extends React.Component {
131
46
  errorInfo._suppressLogging = true;
132
47
  this.setState({
133
48
  error: error,
134
- errorInfo: errorInfo,
49
+ errorInfo: errorInfo
135
50
  });
136
51
  }
137
52
 
@@ -183,7 +98,7 @@ export const Example = ({
183
98
  // Content that appears between h3 and code block to explain example
184
99
  children,
185
100
  // Show dark theme switcher on full page examples
186
- hasDarkThemeSwitcher = process.env.hasDarkThemeSwitcher,
101
+ hasThemeSwitcher = process.env.hasThemeSwitcher,
187
102
  // Show dark theme switcher on full page examples
188
103
  hasRTLSwitcher = process.env.hasRTLSwitcher,
189
104
  // Map of relative imports matched to their npm package import path (passed to Codesandbox)
@@ -191,7 +106,7 @@ export const Example = ({
191
106
  // md file location in node_modules, used to resolve relative import paths in examples
192
107
  relPath = '',
193
108
  // absolute url to hosted file
194
- sourceLink = '',
109
+ sourceLink = ''
195
110
  }) => {
196
111
  if (isFullscreenPreview) {
197
112
  isFullscreen = false;
@@ -202,9 +117,7 @@ export const Example = ({
202
117
  useEffect(() => {
203
118
  if (!isFullscreenPreview) return;
204
119
 
205
- document.readyState === 'complete'
206
- ? addPageLoadedClass()
207
- : window.addEventListener('load', addPageLoadedClass);
120
+ document.readyState === 'complete' ? addPageLoadedClass() : window.addEventListener('load', addPageLoadedClass);
208
121
 
209
122
  return () => window.removeEventListener('load', addPageLoadedClass);
210
123
  }, []);
@@ -230,9 +143,7 @@ export const Example = ({
230
143
  ...reactCoreModule,
231
144
  ...reactTableModule,
232
145
  ...(source === 'react-next' ? reactCoreNextModule : {}),
233
- ...(source === 'react-deprecated'
234
- ? { ...reactCoreDeprecatedModule, ...reactTableDeprecatedModule }
235
- : {}),
146
+ ...(source === 'react-deprecated' ? { ...reactCoreDeprecatedModule, ...reactTableDeprecatedModule } : {})
236
147
  };
237
148
 
238
149
  let livePreview = null;
@@ -248,8 +159,7 @@ export const Example = ({
248
159
  );
249
160
  } else {
250
161
  try {
251
- const { code: transformedCode, hasTS } =
252
- convertToReactComponent(editorCode);
162
+ const { code: transformedCode, hasTS } = convertToReactComponent(editorCode);
253
163
  if (hasTS) {
254
164
  lang = 'ts';
255
165
  } else {
@@ -259,11 +169,7 @@ export const Example = ({
259
169
  const componentNames = Object.keys(scope);
260
170
  const componentValues = Object.values(scope);
261
171
 
262
- const getPreviewComponent = new Function(
263
- 'React',
264
- ...componentNames,
265
- transformedCode
266
- );
172
+ const getPreviewComponent = new Function('React', ...componentNames, transformedCode);
267
173
  const PreviewComponent = getPreviewComponent(React, ...componentValues);
268
174
 
269
175
  livePreview = (
@@ -282,13 +188,13 @@ export const Example = ({
282
188
  return (
283
189
  <div id={previewId} className={css(className, 'pf-v6-u-h-100')}>
284
190
  {livePreview}
285
- {(hasDarkThemeSwitcher || hasRTLSwitcher) && (
191
+ {(hasThemeSwitcher || hasRTLSwitcher) && (
286
192
  <Flex
287
193
  direction={{ default: 'column' }}
288
194
  gap={{ default: 'gapMd' }}
289
195
  className="ws-full-page-utils pf-v6-m-dir-ltr"
290
196
  >
291
- {hasDarkThemeSwitcher && <FullScreenThemeSelector />}
197
+ {hasThemeSwitcher && <ThemeSelector id="ws-example-theme-select" />}
292
198
  {hasRTLSwitcher && (
293
199
  <Switch
294
200
  id="ws-example-rtl-switch"
@@ -310,61 +216,49 @@ export const Example = ({
310
216
  const codeBoxParams = getParameters(
311
217
  lang === 'html'
312
218
  ? 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)
322
220
  );
323
221
  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);
328
223
 
329
224
  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;
360
257
 
361
258
  return (
362
259
  <Stack hasGutter>
363
260
  <StackItem>
364
- <AutoLinkHeader
365
- metaText={metaText}
366
- headingLevel="h3"
367
- >
261
+ <AutoLinkHeader metaText={metaText} headingLevel="h3">
368
262
  {title}
369
263
  </AutoLinkHeader>
370
264
  {children}
@@ -378,22 +272,11 @@ export const Example = ({
378
272
  target="_blank"
379
273
  aria-label={`Open fullscreen ${title} example`}
380
274
  >
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`} />
387
276
  </a>
388
277
  </div>
389
278
  ) : (
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')}>
397
280
  {livePreview}
398
281
  </div>
399
282
  )}
@@ -10,3 +10,4 @@ export * from './topNav/topNav';
10
10
  export * from './link/link';
11
11
  export * from './tableOfContents/tableOfContents';
12
12
  export * from './inlineAlert/inlineAlert';
13
+ export * from './themeSelector/themeSelector';
@@ -0,0 +1,148 @@
1
+ import React, { useState } from 'react';
2
+ import {
3
+ Select,
4
+ SelectGroup,
5
+ SelectList,
6
+ SelectOption,
7
+ MenuToggle,
8
+ MenuSearch,
9
+ MenuSearchInput,
10
+ ToggleGroup,
11
+ ToggleGroupItem,
12
+ Icon,
13
+ Divider
14
+ } from '@patternfly/react-core';
15
+ import { useTheme, THEME_TYPES } from '../../hooks/useTheme';
16
+
17
+ const SunIcon = (
18
+ <svg
19
+ xmlns="http://www.w3.org/2000/svg"
20
+ viewBox="0 0 32 32"
21
+ className="pf-v6-svg"
22
+ fill="var(--pf-t--global--icon--color--regular)"
23
+ >
24
+ <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>
25
+ </svg>
26
+ );
27
+ const MoonIcon = (
28
+ <svg
29
+ xmlns="http://www.w3.org/2000/svg"
30
+ viewBox="0 0 32 32"
31
+ className="pf-v6-svg"
32
+ fill="var(--pf-t--global--icon--color--regular)"
33
+ >
34
+ <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>
35
+ </svg>
36
+ );
37
+ const DesktopIcon = (
38
+ <svg
39
+ xmlns="http://www.w3.org/2000/svg"
40
+ viewBox="0 0 32 32"
41
+ className="pf-v6-svg"
42
+ fill="var(--pf-t--global--icon--color--regular)"
43
+ >
44
+ <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>
45
+ </svg>
46
+ );
47
+
48
+ export const ThemeSelector = ({ id }) => {
49
+ const { mode: themeMode, setMode: setThemeMode, modes: colorModes } = useTheme(THEME_TYPES.COLOR);
50
+ const {
51
+ mode: highContrastMode,
52
+ setMode: setHighContrastMode,
53
+ modes: highContrastModes
54
+ } = useTheme(THEME_TYPES.HIGH_CONTRAST);
55
+ const [isThemeSelectOpen, setIsThemeSelectOpen] = useState(false);
56
+
57
+ const handleThemeChange = (_event, selectedMode) => {
58
+ setThemeMode(selectedMode);
59
+ setIsThemeSelectOpen(false);
60
+ };
61
+
62
+ const handleHighContrastThemeSelection = (evt) => {
63
+ setHighContrastMode(evt.currentTarget.id);
64
+ };
65
+
66
+ const getThemeDisplayText = (mode) => {
67
+ switch (mode) {
68
+ case colorModes.LIGHT:
69
+ return 'Light';
70
+ case colorModes.DARK:
71
+ return 'Dark';
72
+ default:
73
+ return 'System';
74
+ }
75
+ };
76
+
77
+ const getThemeIcon = (mode) => {
78
+ switch (mode) {
79
+ case colorModes.LIGHT:
80
+ return SunIcon;
81
+ case colorModes.DARK:
82
+ return MoonIcon;
83
+ default:
84
+ return DesktopIcon;
85
+ }
86
+ };
87
+ return (
88
+ <Select
89
+ id={id}
90
+ isOpen={isThemeSelectOpen}
91
+ selected={themeMode}
92
+ onSelect={handleThemeChange}
93
+ onOpenChange={(isOpen) => setIsThemeSelectOpen(isOpen)}
94
+ toggle={(toggleRef) => (
95
+ <MenuToggle
96
+ ref={toggleRef}
97
+ onClick={() => setIsThemeSelectOpen(!isThemeSelectOpen)}
98
+ isExpanded={isThemeSelectOpen}
99
+ icon={<Icon size="lg">{getThemeIcon(themeMode)}</Icon>}
100
+ aria-label={`Theme selection, current: ${getThemeDisplayText(themeMode)}`}
101
+ />
102
+ )}
103
+ shouldFocusToggleOnSelect
104
+ onOpenChangeKeys={['Escape']}
105
+ >
106
+ <SelectGroup>
107
+ <SelectList aria-label="Light/Dark theme switcher">
108
+ <SelectOption value={colorModes.SYSTEM} icon={DesktopIcon} description="Follow system preference">
109
+ System
110
+ </SelectOption>
111
+ <SelectOption value={colorModes.LIGHT} icon={SunIcon} description="Always use light mode">
112
+ Light
113
+ </SelectOption>
114
+ <SelectOption value={colorModes.DARK} icon={MoonIcon} description="Always use dark mode">
115
+ Dark
116
+ </SelectOption>
117
+ </SelectList>
118
+ </SelectGroup>
119
+ <Divider />
120
+ <SelectGroup label="High Contrast">
121
+ <MenuSearch>
122
+ <MenuSearchInput>
123
+ <ToggleGroup aria-label="High contrast theme switcher">
124
+ <ToggleGroupItem
125
+ text="System"
126
+ buttonId={highContrastModes.SYSTEM}
127
+ isSelected={highContrastMode === highContrastModes.SYSTEM}
128
+ onChange={handleHighContrastThemeSelection}
129
+ />
130
+ <ToggleGroupItem
131
+ text="On"
132
+ buttonId={highContrastModes.ON}
133
+ isSelected={highContrastMode === highContrastModes.ON}
134
+ onChange={handleHighContrastThemeSelection}
135
+ />
136
+ <ToggleGroupItem
137
+ text="Off"
138
+ buttonId={highContrastModes.OFF}
139
+ isSelected={highContrastMode === highContrastModes.OFF}
140
+ onChange={handleHighContrastThemeSelection}
141
+ />
142
+ </ToggleGroup>
143
+ </MenuSearchInput>
144
+ </MenuSearch>
145
+ </SelectGroup>
146
+ </Select>
147
+ );
148
+ };
package/hooks/useTheme.js CHANGED
@@ -1,137 +1,174 @@
1
- import { useState, useEffect, useCallback } from 'react';
1
+ import { useState, useEffect } from 'react';
2
2
 
3
- const THEME_MODES = {
3
+ const COLOR_MODES = {
4
4
  SYSTEM: 'system',
5
5
  LIGHT: 'light',
6
6
  DARK: 'dark'
7
7
  };
8
8
 
9
- const THEME_STORAGE_KEY = 'theme-preference';
10
- const DARK_MODE_CLASS = 'pf-v6-theme-dark';
9
+ const HIGH_CONTRAST_MODES = {
10
+ SYSTEM: 'high-contrast-system',
11
+ ON: 'high-contrast-on',
12
+ OFF: 'high-contrast-off'
13
+ };
11
14
 
12
- /**
13
- * Custom hook for managing theme state with system preference detection
14
- * @returns {Object} Theme state and controls
15
- * @returns {string} themeMode - Current theme mode ('system'|'light'|'dark')
16
- * @returns {Function} setThemeMode - Function to change theme mode
17
- * @returns {string} resolvedTheme - The actual applied theme ('light'|'dark')
18
- * @returns {Object} THEME_MODES - Available theme mode constants
19
- */
20
- export const useTheme = () => {
21
- const getStoredThemeMode = () => {
22
- if (typeof window === 'undefined' || !window.localStorage) {
23
- return null;
15
+ export const THEME_TYPES = {
16
+ COLOR: 'color',
17
+ HIGH_CONTRAST: 'high-contrast'
18
+ };
19
+
20
+ class ThemeManager {
21
+ constructor({ storageKey, modes, defaultMode, cssClass, classEnabledMode, mediaQueryString }) {
22
+ this.storageKey = storageKey;
23
+ this.modes = modes;
24
+ this.defaultMode = defaultMode;
25
+ this.cssClass = cssClass;
26
+ this.classEnabledMode = classEnabledMode;
27
+ this.mediaQueryString = mediaQueryString;
28
+ this.isBrowser = typeof window !== 'undefined' && window.matchMedia && window.localStorage;
29
+ }
30
+
31
+ getMediaQuery() {
32
+ if (!this.isBrowser) {
33
+ return;
24
34
  }
25
- return localStorage.getItem(THEME_STORAGE_KEY);
26
- };
35
+ return window.matchMedia(this.mediaQueryString);
36
+ }
27
37
 
28
- const setStoredThemeMode = (mode) => {
29
- if (typeof window === 'undefined' || !window.localStorage) {
38
+ getStoredValue() {
39
+ if (!this.isBrowser) {
30
40
  return;
31
41
  }
32
- localStorage.setItem(THEME_STORAGE_KEY, mode);
33
- };
42
+ return localStorage.getItem(this.storageKey);
43
+ }
34
44
 
35
- const getResolvedTheme = (mode) => {
36
- // SSR-safe check for window and matchMedia
37
- if (typeof window === 'undefined' || !window.matchMedia) {
38
- return 'light';
45
+ setStoredValue(value) {
46
+ if (!this.isBrowser) {
47
+ return;
39
48
  }
40
-
41
- if (mode === THEME_MODES.SYSTEM) {
42
- try {
43
- return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
44
- } catch (error) {
45
- // Fallback if matchMedia fails
46
- console.warn('matchMedia not supported, defaulting to light theme');
47
- return 'light';
48
- }
49
+ localStorage.setItem(this.storageKey, value);
50
+ }
51
+
52
+ resolve() {
53
+ if (!this.isBrowser) {
54
+ return this.defaultMode;
49
55
  }
50
- return mode;
51
- };
52
56
 
53
- const updateThemeClass = (resolvedTheme) => {
54
- if (typeof window === 'undefined' || !document) {
57
+ if (window.matchMedia(this.mediaQueryString).matches) {
58
+ return this.classEnabledMode;
59
+ }
60
+ return this.defaultMode;
61
+ }
62
+
63
+ addClass() {
64
+ if (!this.isBrowser) {
65
+ return;
66
+ }
67
+ document.querySelector('html').classList.add(this.cssClass);
68
+ }
69
+
70
+ removeClass() {
71
+ if (!this.isBrowser) {
55
72
  return;
56
73
  }
57
-
58
- const htmlElement = document.querySelector('html');
59
- if (!htmlElement) {
74
+ document.querySelector('html').classList.remove(this.cssClass);
75
+ }
76
+
77
+ updateClass(mode) {
78
+ if (!this.isBrowser) {
60
79
  return;
61
80
  }
62
-
63
- if (resolvedTheme === 'dark') {
64
- htmlElement.classList.add(DARK_MODE_CLASS);
81
+
82
+ if (mode === this.modes.SYSTEM) {
83
+ if (this.resolve() === this.classEnabledMode) {
84
+ this.addClass();
85
+ } else {
86
+ this.removeClass();
87
+ }
65
88
  } else {
66
- htmlElement.classList.remove(DARK_MODE_CLASS);
89
+ if (mode === this.classEnabledMode) {
90
+ this.addClass();
91
+ } else {
92
+ this.removeClass();
93
+ }
67
94
  }
68
- };
95
+ }
96
+ }
97
+
98
+ const themeRegistry = new Map();
99
+
100
+ const registerThemeManager = (themeType, manager) => {
101
+ themeRegistry.set(themeType, manager);
102
+ };
103
+
104
+ const colorThemeManager = new ThemeManager({
105
+ storageKey: 'theme-preference',
106
+ modes: COLOR_MODES,
107
+ defaultMode: COLOR_MODES.SYSTEM,
108
+ cssClass: 'pf-v6-theme-dark',
109
+ classEnabledMode: COLOR_MODES.DARK,
110
+ mediaQueryString: '(prefers-color-scheme: dark)'
111
+ });
112
+
113
+ const highContrastThemeManager = new ThemeManager({
114
+ storageKey: 'high-contrast-preference',
115
+ modes: HIGH_CONTRAST_MODES,
116
+ defaultMode: HIGH_CONTRAST_MODES.SYSTEM,
117
+ cssClass: 'pf-v6-theme-high-contrast',
118
+ classEnabledMode: HIGH_CONTRAST_MODES.ON,
119
+ mediaQueryString: '(prefers-contrast: more)'
120
+ });
121
+
122
+ registerThemeManager(THEME_TYPES.COLOR, colorThemeManager);
123
+ registerThemeManager(THEME_TYPES.HIGH_CONTRAST, highContrastThemeManager);
124
+
125
+ /**
126
+ * Unified theme hook that accepts a theme type parameter
127
+ * @param {string} themeType - The type of theme to manage (THEME_TYPES.COLOR, THEME_TYPES.HIGH_CONTRAST, instantiate and register new themes above as needed)
128
+ * @returns {Object} Theme state and controls specific to the theme type
129
+ */
130
+ export const useTheme = (themeType) => {
131
+ if (!themeType) {
132
+ throw new Error('useTheme requires a theme type parameter. Use THEME_TYPES.COLOR or THEME_TYPES.HIGH_CONTRAST');
133
+ }
69
134
 
70
- const [themeMode, setThemeModeState] = useState(() => {
71
- const stored = getStoredThemeMode();
72
- return stored && Object.values(THEME_MODES).includes(stored) ? stored : THEME_MODES.SYSTEM;
73
- });
135
+ const theme = themeRegistry.get(themeType);
74
136
 
75
- const [resolvedTheme, setResolvedTheme] = useState(() => getResolvedTheme(themeMode));
137
+ if (!theme) {
138
+ throw new Error(`Theme manager not found for theme type: ${themeType}`);
139
+ }
76
140
 
77
- const setThemeMode = useCallback((newMode) => {
78
- setThemeModeState(newMode);
79
- setStoredThemeMode(newMode);
80
-
81
- const newResolvedTheme = getResolvedTheme(newMode);
82
- setResolvedTheme(newResolvedTheme);
83
- updateThemeClass(newResolvedTheme);
84
- }, []);
141
+ const [mode, setMode] = useState(theme.getStoredValue());
142
+ const [resolvedTheme, setResolvedTheme] = useState(theme.resolve());
85
143
 
86
- // Listen for system preference changes
87
144
  useEffect(() => {
88
- // Enhanced SSR-safe check
89
- if (typeof window === 'undefined' || !window.matchMedia) {
90
- return;
91
- }
145
+ theme.setStoredValue(mode);
146
+ theme.updateClass(mode);
147
+ }, [theme, mode, resolvedTheme]);
92
148
 
93
- let mediaQuery;
94
- try {
95
- mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
96
- } catch (error) {
97
- console.warn('matchMedia not supported, skipping system theme detection');
98
- return;
99
- }
100
-
101
- const handleSystemThemeChange = (e) => {
102
- if (themeMode === THEME_MODES.SYSTEM) {
103
- const newSystemTheme = e.matches ? 'dark' : 'light';
104
- setResolvedTheme(newSystemTheme);
105
- updateThemeClass(newSystemTheme);
106
- }
107
- };
149
+ const handlePreferenceChange = () => {
150
+ setResolvedTheme(theme.resolve());
151
+ };
152
+ const mediaQuery = theme.getMediaQuery();
108
153
 
109
- // Check if addEventListener is available (some older browsers might not support it)
154
+ useEffect(() => {
110
155
  if (mediaQuery.addEventListener) {
111
- mediaQuery.addEventListener('change', handleSystemThemeChange);
156
+ mediaQuery.addEventListener('change', handlePreferenceChange);
112
157
  return () => {
113
- mediaQuery.removeEventListener('change', handleSystemThemeChange);
158
+ mediaQuery.removeEventListener('change', handlePreferenceChange);
114
159
  };
115
160
  } else if (mediaQuery.addListener) {
116
- // Fallback for older browsers
117
- mediaQuery.addListener(handleSystemThemeChange);
161
+ mediaQuery.addListener(handlePreferenceChange);
118
162
  return () => {
119
- mediaQuery.removeListener(handleSystemThemeChange);
163
+ mediaQuery.removeListener(handlePreferenceChange);
120
164
  };
121
165
  }
122
- }, [themeMode]);
123
-
124
- // Initial theme application
125
- useEffect(() => {
126
- const initialResolvedTheme = getResolvedTheme(themeMode);
127
- setResolvedTheme(initialResolvedTheme);
128
- updateThemeClass(initialResolvedTheme);
129
- }, [themeMode]);
166
+ }, [mediaQuery]);
130
167
 
131
168
  return {
132
- themeMode,
133
- setThemeMode,
169
+ mode,
170
+ setMode,
134
171
  resolvedTheme,
135
- THEME_MODES
172
+ modes: theme.modes
136
173
  };
137
- };
174
+ };
@@ -9,7 +9,6 @@ import {
9
9
  DropdownGroup,
10
10
  DropdownList,
11
11
  Divider,
12
- Icon,
13
12
  Masthead,
14
13
  MastheadToggle,
15
14
  MastheadMain,
@@ -24,20 +23,14 @@ import {
24
23
  SkipToContent,
25
24
  Switch,
26
25
  SearchInput,
27
- Select,
28
- SelectOption,
29
- SelectList,
30
26
  MastheadLogo
31
27
  } from '@patternfly/react-core';
32
28
  import BarsIcon from '@patternfly/react-icons/dist/esm/icons/bars-icon';
33
29
  import GithubIcon from '@patternfly/react-icons/dist/esm/icons/github-icon';
34
- 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>;
35
- 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>;
36
- 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>;
37
- import { SideNav, TopNav, GdprBanner } from '../../components';
30
+ import { SideNav, TopNav, GdprBanner, ThemeSelector } from '../../components';
38
31
  import staticVersions from '../../versions.json';
39
32
  import { Footer } from '@patternfly/documentation-framework/components';
40
- import { useTheme } from '../../hooks/useTheme';
33
+ import { useTheme, THEME_TYPES } from '../../hooks/useTheme';
41
34
 
42
35
  export const RtlContext = createContext(false);
43
36
 
@@ -45,14 +38,11 @@ const HeaderTools = ({
45
38
  versions,
46
39
  hasVersionSwitcher,
47
40
  algolia,
48
- hasDarkThemeSwitcher,
41
+ hasThemeSwitcher,
49
42
  hasRTLSwitcher,
50
43
  topNavItems,
51
44
  isRTL,
52
45
  setIsRTL,
53
- themeMode,
54
- setThemeMode,
55
- THEME_MODES
56
46
  }) => {
57
47
  const latestVersion = versions.Releases.find((version) => version.latest);
58
48
  const previousReleases = Object.values(versions.Releases).filter((version) => !version.hidden && !version.latest);
@@ -60,7 +50,6 @@ const HeaderTools = ({
60
50
  const [isDropdownOpen, setDropdownOpen] = useState(false);
61
51
  const [searchValue, setSearchValue] = React.useState('');
62
52
  const [isSearchExpanded, setIsSearchExpanded] = React.useState(false);
63
- const [isThemeSelectOpen, setIsThemeSelectOpen] = useState(false);
64
53
 
65
54
  const getDropdownItem = (version, isLatest = false) => (
66
55
  <DropdownItem itemId={version.name} key={version.name} to={isLatest ? '/' : `/${version.name}`}>
@@ -76,33 +65,6 @@ const HeaderTools = ({
76
65
  setIsSearchExpanded(!isSearchExpanded);
77
66
  };
78
67
 
79
- const handleThemeChange = (_event, selectedMode) => {
80
- setThemeMode(selectedMode);
81
- setIsThemeSelectOpen(false);
82
- };
83
-
84
- const getThemeDisplayText = (mode) => {
85
- switch (mode) {
86
- case THEME_MODES.LIGHT:
87
- return 'Light';
88
- case THEME_MODES.DARK:
89
- return 'Dark';
90
- default:
91
- return 'System';
92
- }
93
- };
94
-
95
- const getThemeIcon = (mode) => {
96
- switch (mode) {
97
- case THEME_MODES.LIGHT:
98
- return SunIcon;
99
- case THEME_MODES.DARK:
100
- return MoonIcon;
101
- default:
102
- return DesktopIcon;
103
- }
104
- };
105
-
106
68
  useEffect(() => {
107
69
  // reattach algolia to input each time search is expanded
108
70
  if (hasSearch && isSearchExpanded) {
@@ -156,49 +118,9 @@ const HeaderTools = ({
156
118
  <GithubIcon />
157
119
  </Button>
158
120
  </ToolbarItem>
159
- {hasDarkThemeSwitcher && (
121
+ {hasThemeSwitcher && (
160
122
  <ToolbarItem>
161
- <Select
162
- id="ws-theme-select"
163
- isOpen={isThemeSelectOpen}
164
- selected={themeMode}
165
- onSelect={handleThemeChange}
166
- onOpenChange={(isOpen) => setIsThemeSelectOpen(isOpen)}
167
- toggle={(toggleRef) => (
168
- <MenuToggle
169
- ref={toggleRef}
170
- onClick={() => setIsThemeSelectOpen(!isThemeSelectOpen)}
171
- isExpanded={isThemeSelectOpen}
172
- icon={<Icon size="lg">{getThemeIcon(themeMode)}</Icon>}
173
- aria-label={`Theme selection, current: ${getThemeDisplayText(themeMode)}`}
174
- />
175
- )}
176
- shouldFocusToggleOnSelect
177
- >
178
- <SelectList>
179
- <SelectOption
180
- value={THEME_MODES.SYSTEM}
181
- icon={DesktopIcon}
182
- description="Follow system preference"
183
- >
184
- System
185
- </SelectOption>
186
- <SelectOption
187
- value={THEME_MODES.LIGHT}
188
- icon={SunIcon}
189
- description="Always use light mode"
190
- >
191
- Light
192
- </SelectOption>
193
- <SelectOption
194
- value={THEME_MODES.DARK}
195
- icon={MoonIcon}
196
- description="Always use dark mode"
197
- >
198
- Dark
199
- </SelectOption>
200
- </SelectList>
201
- </Select>
123
+ <ThemeSelector id="ws-theme-select" />
202
124
  </ToolbarItem>
203
125
  )}
204
126
  {hasVersionSwitcher && (
@@ -290,7 +212,7 @@ export const SideNavLayout = ({ children, groupedRoutes, navOpen: navOpenProp })
290
212
  const algolia = process.env.algolia;
291
213
  const hasGdprBanner = process.env.hasGdprBanner;
292
214
  const hasVersionSwitcher = process.env.hasVersionSwitcher;
293
- const hasDarkThemeSwitcher = process.env.hasDarkThemeSwitcher;
215
+ const hasThemeSwitcher = process.env.hasThemeSwitcher;
294
216
  const hasRTLSwitcher = process.env.hasRTLSwitcher;
295
217
  const sideNavItems = process.env.sideNavItems;
296
218
  const topNavItems = process.env.topNavItems;
@@ -299,8 +221,8 @@ export const SideNavLayout = ({ children, groupedRoutes, navOpen: navOpenProp })
299
221
 
300
222
  const [versions, setVersions] = useState({ ...staticVersions });
301
223
  const [isRTL, setIsRTL] = useState(false);
302
-
303
- const { themeMode, setThemeMode, resolvedTheme, THEME_MODES } = useTheme();
224
+
225
+ const { resolvedTheme } = useTheme(THEME_TYPES.COLOR);
304
226
 
305
227
  useEffect(() => {
306
228
  if (typeof window === 'undefined') {
@@ -388,14 +310,11 @@ export const SideNavLayout = ({ children, groupedRoutes, navOpen: navOpenProp })
388
310
  versions={versions}
389
311
  algolia={algolia}
390
312
  hasVersionSwitcher={hasVersionSwitcher}
391
- hasDarkThemeSwitcher={hasDarkThemeSwitcher}
313
+ hasThemeSwitcher={hasThemeSwitcher}
392
314
  hasRTLSwitcher={hasRTLSwitcher}
393
315
  topNavItems={topNavItems}
394
316
  isRTL={isRTL}
395
317
  setIsRTL={setIsRTL}
396
- themeMode={themeMode}
397
- setThemeMode={setThemeMode}
398
- THEME_MODES={THEME_MODES}
399
318
  />
400
319
  )}
401
320
  </MastheadContent>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@patternfly/documentation-framework",
3
3
  "description": "A framework to build documentation for PatternFly.",
4
- "version": "6.18.7",
4
+ "version": "6.19.0",
5
5
  "author": "Red Hat",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -12,7 +12,7 @@
12
12
  "@babel/preset-env": "7.27.1",
13
13
  "@babel/preset-react": "^7.24.1",
14
14
  "@mdx-js/util": "1.6.16",
15
- "@patternfly/ast-helpers": "^1.4.0-alpha.265",
15
+ "@patternfly/ast-helpers": "^1.4.0-alpha.266",
16
16
  "@reach/router": "npm:@gatsbyjs/reach-router@1.3.9",
17
17
  "autoprefixer": "10.4.19",
18
18
  "babel-loader": "^9.1.3",
@@ -87,5 +87,5 @@
87
87
  "braces": ">=3.0.3",
88
88
  "ssri": ">=8.0.1"
89
89
  },
90
- "gitHead": "4b5a372f0f4b1cc37f0b9d1ecf24910ed11d5fbc"
90
+ "gitHead": "60954ecaf27cf8c2449a5284f3991bc3059870d7"
91
91
  }
@@ -12,7 +12,7 @@ module.exports = (_env, argv) => {
12
12
  hasFooter = false,
13
13
  hasVersionSwitcher = false,
14
14
  hasDesignGuidelines = false,
15
- hasDarkThemeSwitcher = false,
15
+ hasThemeSwitcher = false,
16
16
  hasRTLSwitcher = false,
17
17
  componentsData = {},
18
18
  sideNavItems = [],
@@ -141,7 +141,7 @@ module.exports = (_env, argv) => {
141
141
  'process.env.hasFooter': JSON.stringify(hasFooter),
142
142
  'process.env.hasVersionSwitcher': JSON.stringify(hasVersionSwitcher),
143
143
  'process.env.hasDesignGuidelines': JSON.stringify(hasDesignGuidelines),
144
- 'process.env.hasDarkThemeSwitcher': JSON.stringify(hasDarkThemeSwitcher),
144
+ 'process.env.hasThemeSwitcher': JSON.stringify(hasThemeSwitcher),
145
145
  'process.env.hasRTLSwitcher': JSON.stringify(hasRTLSwitcher),
146
146
  'process.env.componentsData': JSON.stringify(componentsData),
147
147
  'process.env.sideNavItems': JSON.stringify(sideNavItems),