Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cypress.config.ts
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

### [0.4.3](https://github.com/themesberg/flowbite-react/compare/v0.4.2...v0.4.3) (2023-04-05)


### Features

* **Toast:** add onClick prop to Toast.Toggle ([#607](https://github.com/themesberg/flowbite-react/issues/607)) ([#666](https://github.com/themesberg/flowbite-react/issues/666)) ([9be39d0](https://github.com/themesberg/flowbite-react/commit/9be39d0f4c2f8da9bdd54003d9a6f2d983d16345))

### [0.4.2](https://github.com/themesberg/flowbite-react/compare/v0.4.1...v0.4.2) (2023-03-08)


Expand Down
11 changes: 11 additions & 0 deletions cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-empty-function */
import { defineConfig } from 'cypress';

export default defineConfig({
fixturesFolder: false,
e2e: {
setupNodeEvents(_on, _config) {},
baseUrl: 'http://localhost:3000',
},
});
5 changes: 0 additions & 5 deletions cypress.json

This file was deleted.

File renamed without changes.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "flowbite-react",
"version": "0.4.2",
"version": "0.4.3",
"description": "Official React components built for Flowbite and Tailwind CSS",
"keywords": [
"design-system",
Expand Down
26 changes: 10 additions & 16 deletions src/docs/pages/ButtonsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { FC } from 'react';
import { HiOutlineArrowRight, HiShoppingCart } from 'react-icons/hi';
import { Button, Spinner } from '../../lib';
import { Button } from '../../lib';
import type { CodeExample } from './DemoPage';
import { DemoPage } from './DemoPage';

Expand Down Expand Up @@ -269,21 +269,15 @@ const ButtonsPage: FC = () => {
title: 'Loader',
code: (
<div className="flex flex-wrap items-center gap-2">
<div>
<Button>
<div className="mr-3">
<Spinner size="sm" light />
</div>
Loading ...
</Button>
</div>
<div>
<Button outline>
<div className="mr-3">
<Spinner size="sm" light />
</div>
Loading ...
</Button>
<div className="flex flex-wrap items-center gap-2">
<div>
<Button isProcessing={true}>Click me!</Button>
</div>
<div>
<Button isProcessing={true} outline>
Click me!
</Button>
</div>
</div>
</div>
),
Expand Down
28 changes: 28 additions & 0 deletions src/lib/components/Accordion/Accordion.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ describe('Components / Accordion', () => {
expect(content()[1]).toBeVisible();
});

it('it should open and close self when `Space is pressed on the same`Accordion.Panel`', async () => {
const user = userEvent.setup();
render(<TestAccordion />);

// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const _ of titles()) {
await user.tab();
}
await user.keyboard('[Space]');
await user.keyboard('[Space]');

expect(content()[0]).not.toBeVisible();
expect(content()[1]).not.toBeVisible();
});
it('should open focused panel without closing others on an `Accordion.Panel` with `alwaysOpen={true}`', async () => {
const user = userEvent.setup();
render(<TestAccordion alwaysOpen />);
Expand Down Expand Up @@ -237,6 +251,20 @@ describe('Components / Accordion', () => {
});
});
});
describe('Click to toggle open', () => {
beforeEach(() => {
render(<TestAccordion />);
});

it('should open and close the accordion when title is clicked', async () => {
const titleElements = titles();

await userEvent.click(titleElements[1]); // open second panel
await userEvent.click(titleElements[1]); // close second panel
expect(content()[0]).not.toBeVisible(); // content should not be visible
expect(content()[1]).not.toBeVisible(); // content should not be visible
});
});
});

const TestAccordion: FC<Omit<AccordionProps, 'children'>> = (props) => (
Expand Down
9 changes: 8 additions & 1 deletion src/lib/components/Accordion/Accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,17 @@ const AccordionComponent: FC<AccordionProps> = ({
...props
}) => {
const [isOpen, setOpen] = useState(collapseAll ? -1 : 0);

const panels = useMemo(
() =>
Children.map(children, (child, i) =>
cloneElement(child, { alwaysOpen, arrowIcon, flush, isOpen: isOpen === i, setOpen: () => setOpen(i) }),
cloneElement(child, {
alwaysOpen,
arrowIcon,
flush,
isOpen: isOpen === i,
setOpen: () => setOpen(isOpen === i ? -1 : i),
}),
),
[alwaysOpen, arrowIcon, children, flush, isOpen],
);
Expand Down
18 changes: 15 additions & 3 deletions src/lib/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
FlowbiteSizes,
} from '../Flowbite/FlowbiteTheme';
import { useTheme } from '../Flowbite/ThemeContext';
import { Spinner } from '../Spinner';
import type { PositionInButtonGroup } from './ButtonGroup';
import ButtonGroup from './ButtonGroup';

Expand All @@ -18,6 +19,8 @@ export interface FlowbiteButtonTheme {
fullSized: string;
color: FlowbiteColors;
disabled: string;
isProcessing: string;
spinnerSlot: string;
gradient: ButtonGradientColors;
gradientDuoTone: ButtonGradientDuoToneColors;
inner: FlowbiteButtonInnerTheme;
Expand Down Expand Up @@ -66,6 +69,9 @@ export interface ButtonProps extends Omit<ComponentProps<'button'>, 'color' | 'r
gradientMonochrome?: keyof ButtonGradientColors;
href?: string;
target?: string;
isProcessing?: boolean;
processingLabel?: string;
processingSpinner?: ReactNode;
label?: ReactNode;
outline?: boolean;
pill?: boolean;
Expand All @@ -82,6 +88,9 @@ const ButtonComponent = forwardRef<HTMLButtonElement | HTMLAnchorElement, Button
color = 'info',
disabled = false,
fullSized,
isProcessing = false,
processingLabel = 'Loading...',
processingSpinner: SpinnerComponent = <Spinner />,
gradientDuoTone,
gradientMonochrome,
href,
Expand Down Expand Up @@ -129,13 +138,16 @@ const ButtonComponent = forwardRef<HTMLButtonElement | HTMLAnchorElement, Button
theme.outline.pill[outline && pill ? 'on' : 'off'],
theme.size[size],
outline && !theme.outline.color[color] && theme.inner.outline,
isProcessing && theme.isProcessing,
)}
>
<>
{typeof children !== 'undefined' && children}
{typeof label !== 'undefined' && (
{isProcessing && <span className={theme.spinnerSlot}>{SpinnerComponent}</span>}
{typeof children !== 'undefined' ? (
children
) : (
<span data-testid="flowbite-button-label" className={theme.label}>
{label}
{isProcessing ? processingLabel : label}
</span>
)}
</>
Expand Down
39 changes: 25 additions & 14 deletions src/lib/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import classNames from 'classnames';
import type { ComponentProps, FC, MouseEvent, PropsWithChildren } from 'react';
import { useEffect, useRef } from 'react';
import { useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import type { DeepPartial } from '..';
import { mergeDeep } from '../../helpers/mergeDeep';
Expand Down Expand Up @@ -70,23 +70,14 @@ const ModalComponent: FC<ModalProps> = ({
}) => {
const theme = mergeDeep(useTheme().theme.modal, customTheme);

const [mounted, setMounted] = useState(false);

// Declare a ref to store a reference to a div element.
const containerRef = useRef<HTMLDivElement | null>(null);

// If the current value of the ref is falsy (e.g. null), set it to a new div
// element.
if (!containerRef.current) {
containerRef.current = document.createElement('div');
}

// If the current value of the ref is not already a child of the root element,
// append it or replace its parent.
if (containerRef.current.parentNode !== root && windowExists()) {
root = root || document.body;
root.appendChild(containerRef.current);
}

useEffect(() => {
setMounted(true);

return () => {
const container = containerRef.current;

Expand All @@ -105,6 +96,26 @@ const ModalComponent: FC<ModalProps> = ({
}
});

if (!mounted) {
return null;
}

// If the current value of the ref is falsy (e.g. null), set it to a new div
// element.
if (!containerRef.current) {
containerRef.current = document.createElement('div');
}

// If the current value of the ref is not already a child of the root element,
// append it or replace its parent.
if (containerRef.current.parentNode !== root && windowExists()) {
root ||= document.body;
root.appendChild(containerRef.current);

// Prevent scrolling of the root element when the modal is shown
root.style.overflow = show ? 'hidden' : 'auto';
}

const handleOnClick = (e: MouseEvent<HTMLDivElement>) => {
if (dismissible && e.target === e.currentTarget && onClose) {
onClose();
Expand Down
7 changes: 4 additions & 3 deletions src/lib/components/Progress/Progress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,17 @@ export const Progress: FC<ProgressProps> = ({
<>
<div id={id} aria-label={textLabel} aria-valuenow={progress} role="progressbar" {...props}>
{((textLabel && labelText && textLabelPosition === 'outside') ||
(progress && labelProgress && progressLabelPosition === 'outside')) && (
(progress > 0 && labelProgress && progressLabelPosition === 'outside')) && (
<div className={theme.label} data-testid="flowbite-progress-outer-label-container">
{textLabel && labelText && textLabelPosition === 'outside' && (
<span data-testid="flowbite-progress-outer-text-label">{textLabel}</span>
)}
{progress && labelProgress && progressLabelPosition === 'outside' && (
{labelProgress && progressLabelPosition === 'outside' && (
<span data-testid="flowbite-progress-outer-progress-label">{progress}%</span>
)}
</div>
)}

<div className={classNames(theme.base, theme.size[size], className)}>
<div
style={{ width: `${progress}%` }}
Expand All @@ -72,7 +73,7 @@ export const Progress: FC<ProgressProps> = ({
{textLabel && labelText && textLabelPosition === 'inside' && (
<span data-testid="flowbite-progress-inner-text-label">{textLabel}</span>
)}
{progress && labelProgress && progressLabelPosition === 'inside' && (
{progress > 0 && labelProgress && progressLabelPosition === 'inside' && (
<span data-testid="flowbite-progress-inner-progress-label">{progress}%</span>
)}
</div>
Expand Down
8 changes: 5 additions & 3 deletions src/lib/components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import classNames from 'classnames';
import type { ComponentProps, FC, PropsWithChildren } from 'react';
import type { ComponentProps, ElementType, FC, PropsWithChildren } from 'react';
import type { DeepPartial } from '..';
import { mergeDeep } from '../../helpers/mergeDeep';
import type { FlowbiteBoolean } from '../Flowbite/FlowbiteTheme';
Expand Down Expand Up @@ -31,13 +31,15 @@ export interface FlowbiteSidebarTheme {
}

export interface SidebarProps extends PropsWithChildren, ComponentProps<'div'> {
as?: ElementType;
collapseBehavior?: 'collapse' | 'hide';
collapsed?: boolean;
theme?: DeepPartial<FlowbiteSidebarTheme>;
}

const SidebarComponent: FC<SidebarProps> = ({
children,
as: Component = 'nav',
collapseBehavior = 'collapse',
collapsed: isCollapsed = false,
theme: customTheme = {},
Expand All @@ -48,14 +50,14 @@ const SidebarComponent: FC<SidebarProps> = ({

return (
<SidebarContext.Provider value={{ isCollapsed }}>
<aside
<Component
aria-label="Sidebar"
hidden={isCollapsed && collapseBehavior === 'hide'}
className={classNames(theme.root.base, theme.root.collapsed[isCollapsed ? 'on' : 'off'], className)}
{...props}
>
<div className={theme.root.inner}>{children}</div>
</aside>
</Component>
</SidebarContext.Provider>
);
};
Expand Down
6 changes: 4 additions & 2 deletions src/lib/components/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import type { ComponentProps, FC, PropsWithChildren } from 'react';
import type { DeepPartial } from '..';
import { mergeDeep } from '../../helpers/mergeDeep';
import { useTheme } from '../Flowbite';
import type { FlowbiteTableBodyTheme } from './TableBody';
import { TableBody } from './TableBody';
import type { FlowbiteTableCellTheme } from './TableCell';
import { TableCell } from './TableCell';
import type { TableContextType } from './TableContext';
import { TableContext } from './TableContext';
Expand All @@ -16,13 +16,14 @@ import { TableRow } from './TableRow';

export interface FlowbiteTableTheme {
root: FlowbiteTableRootTheme;
cell: FlowbiteTableCellTheme;
head: FlowbiteTableHeadTheme;
row: FlowbiteTableRowTheme;
body: FlowbiteTableBodyTheme;
}

export interface FlowbiteTableRootTheme {
base: string;
shadow: string;
wrapper: string;
}

Expand All @@ -43,6 +44,7 @@ const TableComponent: FC<TableProps> = ({
return (
<div data-testid="table-element" className={classNames(theme.root.wrapper)}>
<TableContext.Provider value={{ striped, hoverable }}>
<div className={classNames(theme.root.shadow, className)}></div>
<table className={classNames(theme.root.base, className)} {...props}>
{children}
</table>
Expand Down
24 changes: 21 additions & 3 deletions src/lib/components/Table/TableBody.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
import classNames from 'classnames';
import type { ComponentProps, FC, PropsWithChildren } from 'react';
import type { DeepPartial } from '..';
import { mergeDeep } from '../../helpers/mergeDeep';
import { useTheme } from '../Flowbite';
import type { FlowbiteTableCellTheme } from './TableCell';

export type TableBodyProps = PropsWithChildren<ComponentProps<'tbody'>>;
export interface FlowbiteTableBodyTheme {
base: string;
cell: FlowbiteTableCellTheme;
}

export const TableBody: FC<TableBodyProps> = ({ children, ...props }) => {
return <tbody {...props}>{children}</tbody>;
export interface TableBodyProps extends PropsWithChildren, ComponentProps<'tbody'> {
theme?: DeepPartial<FlowbiteTableCellTheme>;
}

export const TableBody: FC<TableBodyProps> = ({ children, className, theme: customTheme = {}, ...props }) => {
const theme = mergeDeep(useTheme().theme.table.body, customTheme);

return (
<tbody className={classNames(theme.base, className)} {...props}>
{children}
</tbody>
);
};
Loading