Skip to content

Commit

Permalink
fix(component): use 1st letter of text in collapsed Sidebar.Item with…
Browse files Browse the repository at this point in the history
… no icon (#338)

* fix(component): use 1st letter of text in collapsed `Sidebar.Item` with no `icon`

resolve #81

* feat(theme): add `sidebar.item.collapsed.noIcon` to theme

Classes placed here are applied when you create `Sidebar.Item` with an `icon` and collapse the
`Sidebar`.

* test(helper): update `mergeDeep` tests to more closely reflect actual usage

We only use `mergeDeep` in one scenario currently: combine the default `<Flowbite theme>` with
custom overrides provided in `<Flowbite theme={{ theme }}>`. The default theme contains all keys. We
only need to test one scenario: given one object with all the keys, and a second object with any of
those keys, use the second (overriding) object's values.

* refactor(component): explicitly type `mergeDeep` for `FlowbiteTheme`
  • Loading branch information
tulup-conner authored Aug 21, 2022
1 parent a9a9a87 commit 103d173
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 92 deletions.
3 changes: 2 additions & 1 deletion src/lib/components/Flowbite/FlowbiteTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import type { TabStyleItem, TabStyles } from '../Tab';

export type CustomFlowbiteTheme = DeepPartial<FlowbiteTheme>;

export interface FlowbiteTheme {
export interface FlowbiteTheme extends Record<string, unknown> {
accordion: {
base: string;
content: {
Expand Down Expand Up @@ -411,6 +411,7 @@ export interface FlowbiteTheme {
base: string;
collapsed: {
insideCollapse: string;
noIcon: string;
};
content: {
base: string;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/Flowbite/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const Flowbite: FC<FlowbiteProps> = ({ children, theme = {} }) => {
const { theme: customTheme = {}, dark, usePreferences = true } = theme;
const [mode, setMode, toggleMode] = useThemeMode(usePreferences);

const mergedTheme = mergeDeep(defaultTheme, customTheme) as unknown as FlowbiteTheme;
const mergedTheme = mergeDeep<FlowbiteTheme>(defaultTheme, customTheme);

useEffect(() => {
if (dark) {
Expand Down
21 changes: 13 additions & 8 deletions src/lib/components/Sidebar/SidebarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,21 @@ const SidebarItem: FC<SidebarItemProps> = ({
const { isInsideCollapse } = useSidebarItemContext();
const theme = useTheme().theme.sidebar.item;

const Wrapper: FC<PropsWithChildren<unknown>> = ({ children: Component }) => (
const ListItem: FC<PropsWithChildren> = ({ children: wrapperChildren }) => (
<li>
{isCollapsed ? (
<Tooltip content={<Children>{children}</Children>} placement="right">
{Component}
<Tooltip content={<TooltipContent>{children}</TooltipContent>} placement="right">
{wrapperChildren}
</Tooltip>
) : (
Component
wrapperChildren
)}
</li>
);

const Children: FC<PropsWithChildren<unknown>> = ({ children }) => (
const TooltipContent: FC<PropsWithChildren> = ({ children }) => <Children>{children}</Children>;

const Children: FC<PropsWithChildren> = ({ children }) => (
<span
className={classNames(theme.content.base)}
data-testid="flowbite-sidebar-item-content"
Expand All @@ -62,7 +64,7 @@ const SidebarItem: FC<SidebarItemProps> = ({
);

return (
<Wrapper>
<ListItem>
<Component
aria-labelledby={`flowbite-sidebar-item-${id}`}
className={classNames(
Expand All @@ -79,14 +81,17 @@ const SidebarItem: FC<SidebarItemProps> = ({
data-testid="flowbite-sidebar-item-icon"
/>
)}
{isCollapsed && !Icon && (
<span className={theme.collapsed.noIcon}>{(children as string).charAt(0).toLocaleUpperCase() ?? '?'}</span>
)}
{!isCollapsed && <Children>{children}</Children>}
{label && !isCollapsed && (
{!isCollapsed && label && (
<Badge color={labelColor} data-testid="flowbite-sidebar-label" hidden={isCollapsed}>
{label}
</Badge>
)}
</Component>
</Wrapper>
</ListItem>
);
};

Expand Down
105 changes: 29 additions & 76 deletions src/lib/helpers/mergeDeep.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,83 +2,36 @@ import { describe, expect, it } from 'vitest';
import { mergeDeep } from './mergeDeep';

describe.concurrent('Helper / mergeDeep (Deeply merge two objects)', () => {
describe.concurrent('given two objects', () => {
it('should combine unique shallow properties', () => {
const combineMe = {
a: 'a',
b: 'b',
c: 'c',
};
const withThis = {
d: 'd',
e: 'e',
};
it('should use the overriding value given an identical key in both inputs', () => {
const defaultTheme = {
base: 'base',
content: {
base: 'content',
},
flush: {
off: 'no-flush',
on: 'flush',
},
};
const overrides = {
content: {
base: 'new-content',
},
flush: {
off: 'new-no-flush',
on: 'new-flush',
},
};

expect(mergeDeep(combineMe, withThis)).toEqual({
a: 'a',
b: 'b',
c: 'c',
d: 'd',
e: 'e',
});
});

describe.concurrent('with identical key', () => {
it("should use overriding object's value", () => {
const combineMe = {
base: 'base',
content: {
base: 'content',
},
flush: {
off: 'no-flush',
on: 'flush',
},
};
const withThis = {
base: 'new-base',
content: {
base: 'new-content',
},
flush: {
off: 'new-no-flush',
on: 'new-flush',
},
};

expect(mergeDeep(combineMe, withThis)).toEqual({
base: 'new-base',
content: {
base: 'new-content',
},
flush: {
off: 'new-no-flush',
on: 'new-flush',
},
});
});

describe.concurrent('that is an object', () => {
it('should combine keys from both objects', () => {
const combineMe = {
content: {
base: 'base',
},
};
const withThis = {
content: {
primary: 'primary',
},
};

expect(mergeDeep(combineMe, withThis)).toEqual({
content: {
base: 'base',
primary: 'primary',
},
});
});
});
expect(mergeDeep(defaultTheme, overrides)).toEqual({
base: 'base',
content: {
base: 'new-content',
},
flush: {
off: 'new-no-flush',
on: 'new-flush',
},
});
});
});
7 changes: 3 additions & 4 deletions src/lib/helpers/mergeDeep.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// source: https://stackoverflow.com/questions/27936772/how-to-deep-merge-instead-of-shallow-merge

import { DeepPartial } from '../components';

/**
* Simple object check.
* @param item
Expand All @@ -14,10 +16,7 @@ export function isObject(item: unknown) {
* @param target
* @param ...sources
*/
export function mergeDeep(
target: Record<string, unknown>,
...sources: Record<string, unknown>[]
): Record<string, unknown> {
export function mergeDeep<T extends Record<string, unknown>>(target: T, ...sources: DeepPartial<T>[]): T {
if (!sources.length) return target;
const source = sources.shift();

Expand Down
9 changes: 7 additions & 2 deletions src/lib/theme/default.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export default {
import type { FlowbiteTheme } from '../components';

const theme: FlowbiteTheme = {
accordion: {
base: 'divide-y divide-gray-200 border-gray-200 dark:divide-gray-700 dark:border-gray-700',
content: {
Expand Down Expand Up @@ -697,10 +699,11 @@ export default {
},
},
item: {
base: 'flex items-center rounded-lg p-2 text-base font-normal text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700',
base: 'flex items-center justify-center rounded-lg p-2 text-base font-normal text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700',
active: 'bg-gray-100 dark:bg-gray-700',
collapsed: {
insideCollapse: 'group w-full pl-8 transition duration-75',
noIcon: 'font-bold',
},
content: {
base: 'px-3 flex-1 whitespace-nowrap',
Expand Down Expand Up @@ -866,3 +869,5 @@ export default {
},
},
};

export default theme;

0 comments on commit 103d173

Please sign in to comment.