Skip to content

Commit 8be4e2e

Browse files
zhujejgbernalp
andauthored
[FEATURE] UI Customization. Add Menu Component (#419)
* [FEATURE] UI Customization. Add Menu Component Signed-off-by: Jenny Zhu <jenny.a.zhu@gmail.com> * [IGNORE] add MenuLinkItem Signed-off-by: Jenny Zhu <jenny.a.zhu@gmail.com> * [IGNORE] add MenuCheckboxItem Signed-off-by: Jenny Zhu <jenny.a.zhu@gmail.com> * [IGNORE] add Menu stories Signed-off-by: Jenny Zhu <jenny.a.zhu@gmail.com> * [IGNORE] address Menu review: viewport max-height, selected focus highlight, aria-current Signed-off-by: Jenny Zhu <jenny.a.zhu@gmail.com> * [IGNORE] address checklist review: item display props, story tokens Signed-off-by: Jenny Zhu <jenny.a.zhu@gmail.com> * [IGNORE] add opacity fallback to disabled menu items Signed-off-by: Jenny Zhu <jenny.a.zhu@gmail.com> * [IGNORE] reuse checkbox style, improve dark mode colors Signed-off-by: Gabriel Bernal <gbernal@redhat.com> --------- Signed-off-by: Jenny Zhu <jenny.a.zhu@gmail.com> Signed-off-by: Gabriel Bernal <gbernal@redhat.com> Co-authored-by: Gabriel Bernal <gbernal@redhat.com>
1 parent f9cdec9 commit 8be4e2e

24 files changed

Lines changed: 3147 additions & 7 deletions

‎components/src/next/contexts/ComponentsContext.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type { ChipProps } from '../primitives/Chip';
2323
import type { DividerProps } from '../primitives/Divider';
2424
import type { GridItemProps, GridProps } from '../primitives/Grid';
2525
import type { IconButtonProps } from '../primitives/IconButton';
26+
import type { MenuCheckboxItemProps, MenuItemProps, MenuLinkItemProps, MenuProps } from '../primitives/Menu';
2627
import type { SpinnerProps } from '../primitives/Spinner';
2728
import type { StackProps } from '../primitives/Stack';
2829
import type { SwitchProps } from '../primitives/Switch';
@@ -67,6 +68,10 @@ export interface PersesComponents {
6768
Stack: RefComponent<StackProps, HTMLDivElement>;
6869
Switch: RefComponent<SwitchProps, HTMLElement>;
6970
Tooltip: RefComponent<TooltipProps, HTMLDivElement>;
71+
Menu: RefComponent<MenuProps, HTMLDivElement>;
72+
MenuItem: RefComponent<MenuItemProps, HTMLDivElement>;
73+
MenuLinkItem: RefComponent<MenuLinkItemProps, HTMLAnchorElement>;
74+
MenuCheckboxItem: RefComponent<MenuCheckboxItemProps, HTMLDivElement>;
7075
Table: RefComponent<TableProps, HTMLTableElement>;
7176
TableHead: RefComponent<TableHeadProps, HTMLTableSectionElement>;
7277
TableBody: RefComponent<TableBodyProps, HTMLTableSectionElement>;
@@ -129,6 +134,10 @@ export interface ComponentsProviderProps {
129134
* Spinner: MySpinner,
130135
* Stack: MyStack,
131136
* Tooltip: MyTooltip,
137+
* Menu: MyMenu,
138+
* MenuItem: MyMenuItem,
139+
* MenuLinkItem: MyMenuLinkItem,
140+
* MenuCheckboxItem: MyMenuCheckboxItem,
132141
* Table: MyTable,
133142
* TableHead: MyTableHead,
134143
* TableBody: MyTableBody,

‎components/src/next/contexts/ThemeModeProvider.tsx‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,23 @@
1111
// See the License for the specific language governing permissions and
1212
// limitations under the License.
1313

14+
import { createContext } from 'react';
1415
import type { ReactElement, ReactNode } from 'react';
1516

1617
export type ThemeMode = 'dark' | 'light';
1718

19+
/** @internal Carries the nearest explicit mode across React portals. */
20+
export const ThemeModeContext = createContext<ThemeMode | undefined>(undefined);
21+
1822
export interface ThemeModeProviderProps {
1923
mode: ThemeMode;
2024
children?: ReactNode;
2125
}
2226

2327
export function ThemeModeProvider({ mode, children }: ThemeModeProviderProps): ReactElement {
24-
return <div data-perses-mode={mode}>{children}</div>;
28+
return (
29+
<ThemeModeContext.Provider value={mode}>
30+
<div data-perses-mode={mode}>{children}</div>
31+
</ThemeModeContext.Provider>
32+
);
2533
}

‎components/src/next/primitives/Checkbox/Checkbox.tsx‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// limitations under the License.
1313

1414
import { Checkbox as BaseCheckbox } from '@base-ui/react/checkbox';
15-
import clsx from 'clsx';
15+
import { clsx } from 'clsx';
1616
import type { CSSProperties, HTMLAttributes, ReactNode, Ref } from 'react';
1717
import { forwardRef, useCallback } from 'react';
1818

@@ -28,11 +28,14 @@ import {
2828
spacingProps,
2929
} from '../system';
3030
import type { ColorVariant, Size, Status } from '../types';
31+
import { CheckboxMark, CheckboxVisual } from './CheckboxVisual';
3132

33+
// oxlint-disable-next-line import/no-unassigned-import -- This import intentionally registers shared layout styles.
3234
import '../system/layout.css';
35+
// oxlint-disable-next-line import/no-unassigned-import -- This import intentionally registers shared radius styles.
3336
import '../system/radius.css';
37+
// oxlint-disable-next-line import/no-unassigned-import -- This import intentionally registers shared spacing styles.
3438
import '../system/spacing.css';
35-
import './checkbox.css';
3639

3740
export type CheckboxLabelPosition = 'start' | 'end';
3841
export type CheckboxVariant = 'subtle' | 'solid' | 'outline';
@@ -162,6 +165,7 @@ export const Checkbox = forwardRef<HTMLElement, CheckboxProps>(function Checkbox
162165
const control = (
163166
<BaseCheckbox.Root
164167
{...domProps}
168+
render={<CheckboxVisual />}
165169
ref={ref}
166170
id={id}
167171
checked={checked}
@@ -177,7 +181,7 @@ export const Checkbox = forwardRef<HTMLElement, CheckboxProps>(function Checkbox
177181
uncheckedValue={uncheckedValue}
178182
form={form}
179183
inputRef={inputRef}
180-
className={clsx('ps-Checkbox', radiusClassName, !hasLabel && callerClassName, slotProps?.control?.className)}
184+
className={clsx(radiusClassName, !hasLabel && callerClassName, slotProps?.control?.className)}
181185
style={controlStyle}
182186
data-invalid={invalid ? '' : undefined}
183187
data-size={size}
@@ -188,7 +192,7 @@ export const Checkbox = forwardRef<HTMLElement, CheckboxProps>(function Checkbox
188192
className={clsx('ps-Checkbox__indicator', slotProps?.indicator?.className)}
189193
style={slotProps?.indicator?.style}
190194
>
191-
<span className="ps-Checkbox__mark" aria-hidden="true" />
195+
<CheckboxMark />
192196
</BaseCheckbox.Indicator>
193197
</BaseCheckbox.Root>
194198
);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright The Perses Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
import { clsx } from 'clsx';
15+
import { forwardRef } from 'react';
16+
import type { HTMLAttributes, ReactElement } from 'react';
17+
18+
// oxlint-disable-next-line import/no-unassigned-import -- Shared by the standalone control and decorative menu indicator.
19+
import './checkbox.css';
20+
21+
interface CheckboxVisualProps extends HTMLAttributes<HTMLSpanElement> {
22+
decorative?: boolean;
23+
}
24+
25+
// Private presentation shared by controls that own their own semantics, focus, and checked state.
26+
export const CheckboxVisual = forwardRef<HTMLSpanElement, CheckboxVisualProps>(function CheckboxVisual(
27+
{ decorative = false, className, ...props },
28+
ref,
29+
) {
30+
return (
31+
<span
32+
{...props}
33+
ref={ref}
34+
className={clsx('ps-Checkbox', className)}
35+
data-decorative={decorative ? '' : undefined}
36+
/>
37+
);
38+
});
39+
40+
export function CheckboxMark(): ReactElement {
41+
return <span className="ps-Checkbox__mark" aria-hidden="true" />;
42+
}

‎components/src/next/primitives/Checkbox/checkbox.css‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@
165165
cursor: default;
166166
}
167167

168+
/* Decorative indicators inherit disabled opacity from their interactive owner. */
169+
:where(.ps-Checkbox[data-decorative]) {
170+
opacity: 1;
171+
cursor: inherit;
172+
}
173+
168174
:where(.ps-Checkbox[data-invalid]:not([data-checked]):not([data-indeterminate])) {
169175
border-color: var(--perses-status-border-error);
170176
}

0 commit comments

Comments
 (0)