@patternfly/documentation-framework 6.35.1 → 6.36.1
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 +22 -0
- package/components/themeSelector/themeSelector.js +107 -94
- package/hooks/useTheme.js +139 -38
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
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.36.1 (2026-02-19)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **theme switch:** change unified theme class name ([#4934](https://github.com/patternfly/patternfly-org/issues/4934)) ([1caa470](https://github.com/patternfly/patternfly-org/commit/1caa470c123ecaacb36cb732eae844616717fc9b))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# 6.36.0 (2026-02-16)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* **theme-switcher:** enhance theme switcher for theme development/testing ([#4929](https://github.com/patternfly/patternfly-org/issues/4929)) ([cf5a959](https://github.com/patternfly/patternfly-org/commit/cf5a95937b6a534f76e39cb4b2a19511842fbf2b))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
6
28
|
## 6.35.1 (2026-02-11)
|
|
7
29
|
|
|
8
30
|
|
|
@@ -2,8 +2,6 @@ import React, { useState } from 'react';
|
|
|
2
2
|
import {
|
|
3
3
|
Select,
|
|
4
4
|
SelectGroup,
|
|
5
|
-
SelectList,
|
|
6
|
-
SelectOption,
|
|
7
5
|
MenuToggle,
|
|
8
6
|
MenuSearch,
|
|
9
7
|
MenuSearchInput,
|
|
@@ -11,12 +9,8 @@ import {
|
|
|
11
9
|
ToggleGroupItem,
|
|
12
10
|
Icon,
|
|
13
11
|
Divider,
|
|
14
|
-
Spinner
|
|
15
|
-
Label,
|
|
16
|
-
Popover,
|
|
17
|
-
Button
|
|
12
|
+
Spinner
|
|
18
13
|
} from '@patternfly/react-core';
|
|
19
|
-
import { HelpIcon, ExternalLinkAltIcon } from '@patternfly/react-icons';
|
|
20
14
|
import { useTheme, THEME_TYPES } from '../../hooks/useTheme';
|
|
21
15
|
|
|
22
16
|
const SunIcon = (
|
|
@@ -50,6 +44,14 @@ const DesktopIcon = (
|
|
|
50
44
|
</svg>
|
|
51
45
|
);
|
|
52
46
|
|
|
47
|
+
const ThemeVariantGroupLabel = () => {
|
|
48
|
+
return (
|
|
49
|
+
<div className="pf-v6-c-menu__group-title" id="theme-selector-variant-title">
|
|
50
|
+
Theme
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
53
55
|
const ColorSchemeGroupLabel = () => {
|
|
54
56
|
return (
|
|
55
57
|
<div className="pf-v6-c-menu__group-title" id="theme-selector-color-scheme-title">
|
|
@@ -58,60 +60,34 @@ const ColorSchemeGroupLabel = () => {
|
|
|
58
60
|
);
|
|
59
61
|
};
|
|
60
62
|
|
|
61
|
-
const
|
|
62
|
-
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
|
63
|
-
|
|
63
|
+
const ContrastModeGroupLabel = () => {
|
|
64
64
|
return (
|
|
65
|
-
<div className="pf-v6-c-menu__group-title">
|
|
66
|
-
|
|
67
|
-
<Popover
|
|
68
|
-
onClick={(e) => e.stopPropagation()}
|
|
69
|
-
headerContent={'Under development'}
|
|
70
|
-
headerComponent="h1"
|
|
71
|
-
bodyContent={
|
|
72
|
-
'We are still working to add high contrast support across all PatternFly components and extensions. This beta allows you to preview our progress.'
|
|
73
|
-
}
|
|
74
|
-
footerContent={
|
|
75
|
-
<Button
|
|
76
|
-
icon={<ExternalLinkAltIcon />}
|
|
77
|
-
component="a"
|
|
78
|
-
isInline
|
|
79
|
-
variant="link"
|
|
80
|
-
href="/design-foundations/theming"
|
|
81
|
-
target="_blank"
|
|
82
|
-
>
|
|
83
|
-
Learn more
|
|
84
|
-
</Button>
|
|
85
|
-
}
|
|
86
|
-
aria-label="More info about high contrast"
|
|
87
|
-
appendTo={() => document.body}
|
|
88
|
-
>
|
|
89
|
-
<Button variant="plain" hasNoPadding icon={<HelpIcon />} aria-label="High contrast help" />
|
|
90
|
-
</Popover>{' '}
|
|
91
|
-
|
|
92
|
-
<Label color="blue" isCompact>
|
|
93
|
-
Beta
|
|
94
|
-
</Label>
|
|
65
|
+
<div className="pf-v6-c-menu__group-title" id="theme-selector-contrast-title">
|
|
66
|
+
Contrast mode
|
|
95
67
|
</div>
|
|
96
68
|
);
|
|
97
69
|
};
|
|
98
70
|
|
|
99
71
|
export const ThemeSelector = ({ id }) => {
|
|
72
|
+
const { mode: themeVariant, setMode: setThemeVariant, modes: themeVariantModes } = useTheme(THEME_TYPES.THEME_VARIANT);
|
|
100
73
|
const { mode: themeMode, setMode: setThemeMode, modes: colorModes } = useTheme(THEME_TYPES.COLOR);
|
|
101
74
|
const {
|
|
102
|
-
mode:
|
|
103
|
-
setMode:
|
|
104
|
-
modes:
|
|
105
|
-
} = useTheme(THEME_TYPES.
|
|
75
|
+
mode: contrastMode,
|
|
76
|
+
setMode: setContrastMode,
|
|
77
|
+
modes: contrastModes
|
|
78
|
+
} = useTheme(THEME_TYPES.CONTRAST);
|
|
106
79
|
const [isThemeSelectOpen, setIsThemeSelectOpen] = useState(false);
|
|
107
80
|
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
setIsThemeSelectOpen(false);
|
|
81
|
+
const handleThemeVariantChange = (evt) => {
|
|
82
|
+
setThemeVariant(evt.currentTarget.id);
|
|
111
83
|
};
|
|
112
84
|
|
|
113
|
-
const
|
|
114
|
-
|
|
85
|
+
const handleThemeChange = (evt) => {
|
|
86
|
+
setThemeMode(evt.currentTarget.id);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const handleContrastModeChange = (evt) => {
|
|
90
|
+
setContrastMode(evt.currentTarget.id);
|
|
115
91
|
};
|
|
116
92
|
|
|
117
93
|
const getThemeDisplayText = (mode) => {
|
|
@@ -126,6 +102,9 @@ export const ThemeSelector = ({ id }) => {
|
|
|
126
102
|
};
|
|
127
103
|
|
|
128
104
|
const getThemeIcon = (mode) => {
|
|
105
|
+
if (!colorModes) {
|
|
106
|
+
return <Spinner size="sm" />;
|
|
107
|
+
}
|
|
129
108
|
switch (mode) {
|
|
130
109
|
case colorModes.LIGHT:
|
|
131
110
|
return SunIcon;
|
|
@@ -134,7 +113,7 @@ export const ThemeSelector = ({ id }) => {
|
|
|
134
113
|
case colorModes.SYSTEM:
|
|
135
114
|
return DesktopIcon;
|
|
136
115
|
default:
|
|
137
|
-
return
|
|
116
|
+
return DesktopIcon; // Default to system icon
|
|
138
117
|
}
|
|
139
118
|
};
|
|
140
119
|
|
|
@@ -142,8 +121,6 @@ export const ThemeSelector = ({ id }) => {
|
|
|
142
121
|
<Select
|
|
143
122
|
id={id}
|
|
144
123
|
isOpen={isThemeSelectOpen}
|
|
145
|
-
selected={themeMode}
|
|
146
|
-
onSelect={handleThemeChange}
|
|
147
124
|
onOpenChange={(isOpen) => setIsThemeSelectOpen(isOpen)}
|
|
148
125
|
toggle={(toggleRef) => (
|
|
149
126
|
<MenuToggle
|
|
@@ -162,50 +139,86 @@ export const ThemeSelector = ({ id }) => {
|
|
|
162
139
|
preventOverflow: true
|
|
163
140
|
}}
|
|
164
141
|
>
|
|
142
|
+
<SelectGroup label={ThemeVariantGroupLabel}>
|
|
143
|
+
<MenuSearch>
|
|
144
|
+
<MenuSearchInput>
|
|
145
|
+
<ToggleGroup aria-labelledby="theme-selector-variant-title">
|
|
146
|
+
<ToggleGroupItem
|
|
147
|
+
text="Default"
|
|
148
|
+
buttonId={themeVariantModes.DEFAULT}
|
|
149
|
+
isSelected={themeVariant === themeVariantModes.DEFAULT}
|
|
150
|
+
onChange={handleThemeVariantChange}
|
|
151
|
+
/>
|
|
152
|
+
<ToggleGroupItem
|
|
153
|
+
text="Unified"
|
|
154
|
+
buttonId={themeVariantModes.UNIFIED}
|
|
155
|
+
isSelected={themeVariant === themeVariantModes.UNIFIED}
|
|
156
|
+
onChange={handleThemeVariantChange}
|
|
157
|
+
/>
|
|
158
|
+
</ToggleGroup>
|
|
159
|
+
</MenuSearchInput>
|
|
160
|
+
</MenuSearch>
|
|
161
|
+
</SelectGroup>
|
|
162
|
+
<Divider />
|
|
165
163
|
<SelectGroup label={ColorSchemeGroupLabel}>
|
|
166
|
-
<
|
|
167
|
-
<
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
164
|
+
<MenuSearch>
|
|
165
|
+
<MenuSearchInput>
|
|
166
|
+
<ToggleGroup aria-labelledby="theme-selector-color-scheme-title">
|
|
167
|
+
<ToggleGroupItem
|
|
168
|
+
text="System"
|
|
169
|
+
buttonId={colorModes.SYSTEM}
|
|
170
|
+
isSelected={themeMode === colorModes.SYSTEM}
|
|
171
|
+
onChange={handleThemeChange}
|
|
172
|
+
/>
|
|
173
|
+
<ToggleGroupItem
|
|
174
|
+
text="Light"
|
|
175
|
+
buttonId={colorModes.LIGHT}
|
|
176
|
+
isSelected={themeMode === colorModes.LIGHT}
|
|
177
|
+
onChange={handleThemeChange}
|
|
178
|
+
/>
|
|
179
|
+
<ToggleGroupItem
|
|
180
|
+
text="Dark"
|
|
181
|
+
buttonId={colorModes.DARK}
|
|
182
|
+
isSelected={themeMode === colorModes.DARK}
|
|
183
|
+
onChange={handleThemeChange}
|
|
184
|
+
/>
|
|
185
|
+
</ToggleGroup>
|
|
186
|
+
</MenuSearchInput>
|
|
187
|
+
</MenuSearch>
|
|
188
|
+
</SelectGroup>
|
|
189
|
+
<Divider />
|
|
190
|
+
<SelectGroup label={ContrastModeGroupLabel}>
|
|
191
|
+
<MenuSearch>
|
|
192
|
+
<MenuSearchInput>
|
|
193
|
+
<ToggleGroup aria-labelledby="theme-selector-contrast-title">
|
|
194
|
+
<ToggleGroupItem
|
|
195
|
+
text="System"
|
|
196
|
+
buttonId={contrastModes.SYSTEM}
|
|
197
|
+
isSelected={contrastMode === contrastModes.SYSTEM}
|
|
198
|
+
onChange={handleContrastModeChange}
|
|
199
|
+
/>
|
|
200
|
+
<ToggleGroupItem
|
|
201
|
+
text="Default"
|
|
202
|
+
buttonId={contrastModes.DEFAULT}
|
|
203
|
+
isSelected={contrastMode === contrastModes.DEFAULT}
|
|
204
|
+
onChange={handleContrastModeChange}
|
|
205
|
+
/>
|
|
206
|
+
<ToggleGroupItem
|
|
207
|
+
text="High contrast"
|
|
208
|
+
buttonId={contrastModes.HIGH_CONTRAST}
|
|
209
|
+
isSelected={contrastMode === contrastModes.HIGH_CONTRAST}
|
|
210
|
+
onChange={handleContrastModeChange}
|
|
211
|
+
/>
|
|
212
|
+
<ToggleGroupItem
|
|
213
|
+
text="Glass"
|
|
214
|
+
buttonId={contrastModes.GLASS}
|
|
215
|
+
isSelected={contrastMode === contrastModes.GLASS}
|
|
216
|
+
onChange={handleContrastModeChange}
|
|
217
|
+
/>
|
|
218
|
+
</ToggleGroup>
|
|
219
|
+
</MenuSearchInput>
|
|
220
|
+
</MenuSearch>
|
|
177
221
|
</SelectGroup>
|
|
178
|
-
{process.env.hasHighContrastSwitcher && (
|
|
179
|
-
<>
|
|
180
|
-
<Divider />
|
|
181
|
-
<SelectGroup label={HighContrastGroupLabel}>
|
|
182
|
-
<MenuSearch>
|
|
183
|
-
<MenuSearchInput>
|
|
184
|
-
<ToggleGroup aria-label="High contrast theme switcher">
|
|
185
|
-
<ToggleGroupItem
|
|
186
|
-
text="System"
|
|
187
|
-
buttonId={highContrastModes.SYSTEM}
|
|
188
|
-
isSelected={highContrastMode === highContrastModes.SYSTEM}
|
|
189
|
-
onChange={handleHighContrastThemeSelection}
|
|
190
|
-
/>
|
|
191
|
-
<ToggleGroupItem
|
|
192
|
-
text="On"
|
|
193
|
-
buttonId={highContrastModes.ON}
|
|
194
|
-
isSelected={highContrastMode === highContrastModes.ON}
|
|
195
|
-
onChange={handleHighContrastThemeSelection}
|
|
196
|
-
/>
|
|
197
|
-
<ToggleGroupItem
|
|
198
|
-
text="Off"
|
|
199
|
-
buttonId={highContrastModes.OFF}
|
|
200
|
-
isSelected={highContrastMode === highContrastModes.OFF}
|
|
201
|
-
onChange={handleHighContrastThemeSelection}
|
|
202
|
-
/>
|
|
203
|
-
</ToggleGroup>
|
|
204
|
-
</MenuSearchInput>
|
|
205
|
-
</MenuSearch>
|
|
206
|
-
</SelectGroup>
|
|
207
|
-
</>
|
|
208
|
-
)}
|
|
209
222
|
</Select>
|
|
210
223
|
);
|
|
211
224
|
};
|
package/hooks/useTheme.js
CHANGED
|
@@ -6,15 +6,22 @@ const COLOR_MODES = {
|
|
|
6
6
|
DARK: 'dark'
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
SYSTEM: '
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
const CONTRAST_MODES = {
|
|
10
|
+
SYSTEM: 'contrast-system',
|
|
11
|
+
DEFAULT: 'contrast-default',
|
|
12
|
+
HIGH_CONTRAST: 'contrast-high',
|
|
13
|
+
GLASS: 'contrast-glass'
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const THEME_VARIANT_MODES = {
|
|
17
|
+
DEFAULT: 'theme-default',
|
|
18
|
+
UNIFIED: 'theme-redhat'
|
|
13
19
|
};
|
|
14
20
|
|
|
15
21
|
export const THEME_TYPES = {
|
|
16
22
|
COLOR: 'color',
|
|
17
|
-
|
|
23
|
+
CONTRAST: 'contrast',
|
|
24
|
+
THEME_VARIANT: 'theme-variant'
|
|
18
25
|
};
|
|
19
26
|
|
|
20
27
|
class ThemeManager {
|
|
@@ -61,37 +68,105 @@ class ThemeManager {
|
|
|
61
68
|
return this.defaultMode;
|
|
62
69
|
}
|
|
63
70
|
|
|
64
|
-
|
|
71
|
+
getHtmlElement() {
|
|
65
72
|
if (!this.isBrowser) {
|
|
66
|
-
return;
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return document.querySelector('html');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
addClass() {
|
|
79
|
+
const htmlElement = this.getHtmlElement();
|
|
80
|
+
if (htmlElement && !htmlElement.classList.contains(this.cssClass)) {
|
|
81
|
+
htmlElement.classList.add(this.cssClass);
|
|
67
82
|
}
|
|
68
|
-
document.querySelector('html').classList.add(this.cssClass);
|
|
69
83
|
}
|
|
70
84
|
|
|
71
85
|
removeClass() {
|
|
86
|
+
const htmlElement = this.getHtmlElement();
|
|
87
|
+
if (htmlElement && htmlElement.classList.contains(this.cssClass)) {
|
|
88
|
+
htmlElement.classList.remove(this.cssClass);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
updateClass() {
|
|
72
93
|
if (!this.isBrowser) {
|
|
73
94
|
return;
|
|
74
95
|
}
|
|
75
|
-
|
|
96
|
+
|
|
97
|
+
// ALWAYS read from localStorage to ensure we have the correct mode for THIS theme
|
|
98
|
+
const storedMode = this.getStoredValue();
|
|
99
|
+
|
|
100
|
+
// Validate that the stored mode is valid for this theme
|
|
101
|
+
const validModes = Object.values(this.modes);
|
|
102
|
+
if (!validModes.includes(storedMode)) {
|
|
103
|
+
console.error(`[${this.storageKey}] Invalid stored mode "${storedMode}". Valid modes:`, validModes);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const shouldHaveClass = storedMode === this.modes.SYSTEM
|
|
108
|
+
? this.resolve() === this.classEnabledMode
|
|
109
|
+
: storedMode === this.classEnabledMode;
|
|
110
|
+
|
|
111
|
+
if (shouldHaveClass) {
|
|
112
|
+
this.addClass();
|
|
113
|
+
} else {
|
|
114
|
+
this.removeClass();
|
|
115
|
+
}
|
|
76
116
|
}
|
|
117
|
+
}
|
|
77
118
|
|
|
78
|
-
|
|
119
|
+
class ContrastThemeManager extends ThemeManager {
|
|
120
|
+
constructor({ storageKey, modes, defaultMode, mediaQueryString }) {
|
|
121
|
+
super({
|
|
122
|
+
storageKey,
|
|
123
|
+
modes,
|
|
124
|
+
defaultMode,
|
|
125
|
+
cssClass: 'pf-v6-theme-high-contrast',
|
|
126
|
+
classEnabledMode: modes.HIGH_CONTRAST,
|
|
127
|
+
mediaQueryString
|
|
128
|
+
});
|
|
129
|
+
this.glassClass = 'pf-v6-theme-glass';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
updateClass() {
|
|
79
133
|
if (!this.isBrowser) {
|
|
80
134
|
return;
|
|
81
135
|
}
|
|
82
136
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
137
|
+
const htmlElement = this.getHtmlElement();
|
|
138
|
+
if (!htmlElement) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ALWAYS read from localStorage to ensure we have the correct mode for THIS theme
|
|
143
|
+
const storedMode = this.getStoredValue();
|
|
144
|
+
|
|
145
|
+
// Determine which class should be applied based on stored mode
|
|
146
|
+
let shouldHaveHighContrast = false;
|
|
147
|
+
let shouldHaveGlass = false;
|
|
148
|
+
|
|
149
|
+
if (storedMode === this.modes.SYSTEM) {
|
|
150
|
+
shouldHaveHighContrast = window.matchMedia(this.mediaQueryString).matches;
|
|
151
|
+
} else if (storedMode === this.modes.HIGH_CONTRAST) {
|
|
152
|
+
shouldHaveHighContrast = true;
|
|
153
|
+
} else if (storedMode === this.modes.GLASS) {
|
|
154
|
+
shouldHaveGlass = true;
|
|
155
|
+
}
|
|
156
|
+
// DEFAULT mode: both false
|
|
157
|
+
|
|
158
|
+
// Apply high contrast class
|
|
159
|
+
if (shouldHaveHighContrast && !htmlElement.classList.contains(this.cssClass)) {
|
|
160
|
+
htmlElement.classList.add(this.cssClass);
|
|
161
|
+
} else if (!shouldHaveHighContrast && htmlElement.classList.contains(this.cssClass)) {
|
|
162
|
+
htmlElement.classList.remove(this.cssClass);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Apply glass class
|
|
166
|
+
if (shouldHaveGlass && !htmlElement.classList.contains(this.glassClass)) {
|
|
167
|
+
htmlElement.classList.add(this.glassClass);
|
|
168
|
+
} else if (!shouldHaveGlass && htmlElement.classList.contains(this.glassClass)) {
|
|
169
|
+
htmlElement.classList.remove(this.glassClass);
|
|
95
170
|
}
|
|
96
171
|
}
|
|
97
172
|
}
|
|
@@ -111,26 +186,34 @@ const colorThemeManager = new ThemeManager({
|
|
|
111
186
|
mediaQueryString: '(prefers-color-scheme: dark)'
|
|
112
187
|
});
|
|
113
188
|
|
|
114
|
-
const
|
|
115
|
-
storageKey: '
|
|
116
|
-
modes:
|
|
117
|
-
defaultMode:
|
|
118
|
-
cssClass: 'pf-v6-theme-
|
|
119
|
-
classEnabledMode:
|
|
189
|
+
const themeVariantManager = new ThemeManager({
|
|
190
|
+
storageKey: 'theme-variant-preference',
|
|
191
|
+
modes: THEME_VARIANT_MODES,
|
|
192
|
+
defaultMode: THEME_VARIANT_MODES.DEFAULT,
|
|
193
|
+
cssClass: 'pf-v6-theme-redhat',
|
|
194
|
+
classEnabledMode: THEME_VARIANT_MODES.UNIFIED,
|
|
195
|
+
mediaQueryString: '(prefers-color-scheme: dark)' // Not used for variant, but required
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const contrastThemeManager = new ContrastThemeManager({
|
|
199
|
+
storageKey: 'contrast-preference',
|
|
200
|
+
modes: CONTRAST_MODES,
|
|
201
|
+
defaultMode: CONTRAST_MODES.SYSTEM,
|
|
120
202
|
mediaQueryString: '(prefers-contrast: more)'
|
|
121
203
|
});
|
|
122
204
|
|
|
123
205
|
registerThemeManager(THEME_TYPES.COLOR, colorThemeManager);
|
|
124
|
-
registerThemeManager(THEME_TYPES.
|
|
206
|
+
registerThemeManager(THEME_TYPES.THEME_VARIANT, themeVariantManager);
|
|
207
|
+
registerThemeManager(THEME_TYPES.CONTRAST, contrastThemeManager);
|
|
125
208
|
|
|
126
209
|
/**
|
|
127
210
|
* Unified theme hook that accepts a theme type parameter
|
|
128
|
-
* @param {string} themeType - The type of theme to manage (THEME_TYPES.COLOR, THEME_TYPES.
|
|
211
|
+
* @param {string} themeType - The type of theme to manage (THEME_TYPES.COLOR, THEME_TYPES.CONTRAST, THEME_TYPES.THEME_VARIANT)
|
|
129
212
|
* @returns {Object} Theme state and controls specific to the theme type
|
|
130
213
|
*/
|
|
131
214
|
export const useTheme = (themeType) => {
|
|
132
215
|
if (!themeType) {
|
|
133
|
-
throw new Error('useTheme requires a theme type parameter. Use THEME_TYPES.COLOR or THEME_TYPES.
|
|
216
|
+
throw new Error('useTheme requires a theme type parameter. Use THEME_TYPES.COLOR, THEME_TYPES.CONTRAST, or THEME_TYPES.THEME_VARIANT');
|
|
134
217
|
}
|
|
135
218
|
|
|
136
219
|
const theme = themeRegistry.get(themeType);
|
|
@@ -143,16 +226,34 @@ export const useTheme = (themeType) => {
|
|
|
143
226
|
const [resolvedTheme, setResolvedTheme] = useState(theme.resolve());
|
|
144
227
|
|
|
145
228
|
useEffect(() => {
|
|
229
|
+
// Verify mode is valid for this theme
|
|
230
|
+
const validModes = Object.values(theme.modes);
|
|
231
|
+
if (!validModes.includes(mode)) {
|
|
232
|
+
console.error(`Invalid mode "${mode}" for theme ${theme.storageKey}. Valid modes:`, validModes);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
146
236
|
theme.setStoredValue(mode);
|
|
147
|
-
theme.updateClass(
|
|
148
|
-
}, [theme, mode
|
|
237
|
+
theme.updateClass();
|
|
238
|
+
}, [theme, mode]);
|
|
149
239
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
// Only update class when system preference changes AND mode is SYSTEM
|
|
242
|
+
if (mode === theme.modes.SYSTEM) {
|
|
243
|
+
theme.updateClass();
|
|
244
|
+
}
|
|
245
|
+
}, [theme, mode, resolvedTheme]);
|
|
154
246
|
|
|
155
247
|
useEffect(() => {
|
|
248
|
+
const handlePreferenceChange = () => {
|
|
249
|
+
setResolvedTheme(theme.resolve());
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const mediaQuery = theme.getMediaQuery();
|
|
253
|
+
|
|
254
|
+
if (!mediaQuery) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
156
257
|
if (mediaQuery.addEventListener) {
|
|
157
258
|
mediaQuery.addEventListener('change', handlePreferenceChange);
|
|
158
259
|
return () => {
|
|
@@ -164,7 +265,7 @@ export const useTheme = (themeType) => {
|
|
|
164
265
|
mediaQuery.removeListener(handlePreferenceChange);
|
|
165
266
|
};
|
|
166
267
|
}
|
|
167
|
-
}, [
|
|
268
|
+
}, []);
|
|
168
269
|
|
|
169
270
|
return {
|
|
170
271
|
mode,
|
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.
|
|
4
|
+
"version": "6.36.1",
|
|
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.
|
|
15
|
+
"@patternfly/ast-helpers": "^1.4.0-alpha.343",
|
|
16
16
|
"@reach/router": "npm:@gatsbyjs/reach-router@1.3.9",
|
|
17
17
|
"@rspack/core": "^1.5.6",
|
|
18
18
|
"@rspack/dev-server": "^1.1.4",
|
|
@@ -92,5 +92,5 @@
|
|
|
92
92
|
"http-cache-semantics": ">=4.1.1",
|
|
93
93
|
"nanoid": "3.3.8"
|
|
94
94
|
},
|
|
95
|
-
"gitHead": "
|
|
95
|
+
"gitHead": "c51df5b7c4db53d8270141f4e0a1dc8f39cd0ba1"
|
|
96
96
|
}
|