Skip to content

Commit cac6148

Browse files
SutuSebastianSebastian Sutu
andauthored
fix: dropdown theme - granular control (#1066)
Co-authored-by: Sebastian Sutu <sebastian.sutu@sensidev.com>
1 parent 4c0a188 commit cac6148

5 files changed

Lines changed: 63 additions & 32 deletions

File tree

src/components/Dropdown/Dropdown.tsx

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import type { ExtendedRefs, useInteractions } from '@floating-ui/react';
3+
import type { ExtendedRefs } from '@floating-ui/react';
44
import { FloatingFocusManager, FloatingList, useListNavigation, useTypeahead } from '@floating-ui/react';
55
import type {
66
ComponentProps,
@@ -14,22 +14,19 @@ import type {
1414
RefCallback,
1515
SetStateAction,
1616
} from 'react';
17-
import { cloneElement, createContext, useCallback, useEffect, useMemo, useRef, useState } from 'react';
17+
import { cloneElement, useCallback, useEffect, useMemo, useRef, useState } from 'react';
1818
import { HiOutlineChevronDown, HiOutlineChevronLeft, HiOutlineChevronRight, HiOutlineChevronUp } from 'react-icons/hi';
1919
import { twMerge } from 'tailwind-merge';
2020
import { mergeDeep } from '../../helpers/merge-deep';
2121
import { useBaseFLoating, useFloatingInteractions } from '../../helpers/use-floating';
2222
import { getTheme } from '../../theme-store';
2323
import type { DeepPartial } from '../../types';
24-
import type { ButtonProps } from '../Button';
25-
import { Button } from '../Button';
24+
import { Button, type ButtonProps } from '../Button';
2625
import type { FloatingProps, FlowbiteFloatingTheme } from '../Floating';
27-
import type { FlowbiteDropdownDividerTheme } from './DropdownDivider';
28-
import { DropdownDivider } from './DropdownDivider';
29-
import type { FlowbiteDropdownHeaderTheme } from './DropdownHeader';
30-
import { DropdownHeader } from './DropdownHeader';
31-
import type { FlowbiteDropdownItemTheme } from './DropdownItem';
32-
import { DropdownItem } from './DropdownItem';
26+
import { DropdownContext } from './DropdownContext';
27+
import { DropdownDivider, type FlowbiteDropdownDividerTheme } from './DropdownDivider';
28+
import { DropdownHeader, type FlowbiteDropdownHeaderTheme } from './DropdownHeader';
29+
import { DropdownItem, type FlowbiteDropdownItemTheme } from './DropdownItem';
3330

3431
export interface FlowbiteDropdownFloatingTheme
3532
extends FlowbiteFloatingTheme,
@@ -117,15 +114,6 @@ const Trigger = ({
117114
);
118115
};
119116

120-
interface DropdownContextValue {
121-
activeIndex: number | null;
122-
dismissOnClick?: boolean;
123-
getItemProps: ReturnType<typeof useInteractions>['getItemProps'];
124-
handleSelect: (index: number | null) => void;
125-
}
126-
127-
export const DropdownContext = createContext<DropdownContextValue>({} as DropdownContextValue);
128-
129117
const DropdownComponent: FC<DropdownProps> = ({
130118
children,
131119
className,
@@ -219,6 +207,7 @@ const DropdownComponent: FC<DropdownProps> = ({
219207
</Trigger>
220208
<DropdownContext.Provider
221209
value={{
210+
theme,
222211
activeIndex,
223212
dismissOnClick,
224213
getItemProps,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use client';
2+
3+
import type { useInteractions } from '@floating-ui/react';
4+
import { createContext, useContext } from 'react';
5+
import type { DeepPartial } from '../../types';
6+
import type { FlowbiteDropdownTheme } from './Dropdown';
7+
8+
type DropdownContext = {
9+
theme?: DeepPartial<FlowbiteDropdownTheme>;
10+
activeIndex: number | null;
11+
dismissOnClick?: boolean;
12+
getItemProps: ReturnType<typeof useInteractions>['getItemProps'];
13+
handleSelect: (index: number | null) => void;
14+
};
15+
16+
export const DropdownContext = createContext<DropdownContext | undefined>(undefined);
17+
18+
export function useDropdownContext(): DropdownContext {
19+
const context = useContext(DropdownContext);
20+
21+
if (!context) {
22+
throw new Error('useDropdownContext should be used within the DropdownContext provider!');
23+
}
24+
25+
return context;
26+
}
Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1+
'use client';
2+
13
import type { ComponentProps, FC } from 'react';
24
import { twMerge } from 'tailwind-merge';
3-
import { getTheme } from '../../theme-store';
5+
import type { DeepPartial } from '../../types';
6+
import { useDropdownContext } from './DropdownContext';
47

58
export interface FlowbiteDropdownDividerTheme {
69
divider: string;
710
}
811

9-
export const DropdownDivider: FC<ComponentProps<'div'>> = ({ className, ...props }) => {
10-
const theme = getTheme().dropdown.floating.divider;
12+
export type DropdownDividerProps = {
13+
theme?: DeepPartial<FlowbiteDropdownDividerTheme>;
14+
} & ComponentProps<'div'>;
15+
16+
export const DropdownDivider: FC<DropdownDividerProps> = ({ className, theme: customTheme = {}, ...props }) => {
17+
const { theme: dropdownTheme } = useDropdownContext();
18+
19+
const theme = customTheme.divider ?? dropdownTheme?.floating?.divider;
1120

1221
return <div className={twMerge(theme, className)} {...props} />;
1322
};

src/components/Dropdown/DropdownHeader.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
1+
'use client';
2+
13
import type { ComponentProps, FC, PropsWithChildren } from 'react';
24
import { twMerge } from 'tailwind-merge';
3-
import { getTheme } from '../../theme-store';
5+
import type { DeepPartial } from '../../types';
6+
import { useDropdownContext } from './DropdownContext';
47
import { DropdownDivider } from './DropdownDivider';
58

69
export interface FlowbiteDropdownHeaderTheme {
710
header: string;
811
}
912

10-
export const DropdownHeader: FC<PropsWithChildren & ComponentProps<'div'>> = ({ children, className, ...props }) => {
11-
const theme = getTheme().dropdown.floating.header;
13+
export type DropdownHeaderProps = {
14+
theme?: DeepPartial<FlowbiteDropdownHeaderTheme>;
15+
} & PropsWithChildren &
16+
ComponentProps<'div'>;
17+
18+
export const DropdownHeader: FC<DropdownHeaderProps> = ({ children, className, theme: customTheme = {}, ...props }) => {
19+
const { theme: dropdownTheme } = useDropdownContext();
20+
21+
const theme = customTheme.header ?? dropdownTheme?.floating?.header;
1222

1323
return (
1424
<>

src/components/Dropdown/DropdownItem.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,11 @@
22

33
import { useListItem } from '@floating-ui/react';
44
import type { ComponentProps, ComponentPropsWithoutRef, ElementType, FC, RefCallback } from 'react';
5-
import { useContext } from 'react';
65
import { twMerge } from 'tailwind-merge';
76
import { mergeDeep } from '../../helpers/merge-deep';
8-
import { getTheme } from '../../theme-store';
97
import type { DeepPartial } from '../../types';
10-
import type { ButtonBaseProps } from '../Button/ButtonBase';
11-
import { ButtonBase } from '../Button/ButtonBase';
12-
import { DropdownContext } from './Dropdown';
8+
import { ButtonBase, type ButtonBaseProps } from '../Button/ButtonBase';
9+
import { useDropdownContext } from './DropdownContext';
1310

1411
export interface FlowbiteDropdownItemTheme {
1512
container: string;
@@ -34,9 +31,9 @@ export const DropdownItem = <T extends ElementType = 'button'>({
3431
...props
3532
}: DropdownItemProps<T>) => {
3633
const { ref, index } = useListItem({ label: typeof children === 'string' ? children : undefined });
37-
const { activeIndex, dismissOnClick, getItemProps, handleSelect } = useContext(DropdownContext);
34+
const { theme: dropdownTheme, activeIndex, dismissOnClick, getItemProps, handleSelect } = useDropdownContext();
3835
const isActive = activeIndex === index;
39-
const theme = mergeDeep(getTheme().dropdown.floating.item, customTheme);
36+
const theme = mergeDeep(dropdownTheme?.floating?.item ?? {}, customTheme);
4037

4138
const theirProps = props as ButtonBaseProps<T>;
4239

0 commit comments

Comments
 (0)