From 3ed455186fe15efadea0f21efaaca87a9292c5fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20L=C3=BCders?= Date: Mon, 6 Jun 2022 00:02:19 +0200 Subject: [PATCH] feat(component): theme support to modal (#197) * feat(component): theme support to modal * doc(component): update button color modal * refactor(component): clean up interfaces --- src/docs/pages/ModalPage.tsx | 170 ++++++++++--------- src/lib/components/Avatar/index.tsx | 2 +- src/lib/components/Button/index.tsx | 2 +- src/lib/components/Flowbite/FlowbiteTheme.ts | 39 +++++ src/lib/components/Modal/Modal.spec.tsx | 22 +-- src/lib/components/Modal/Modal.stories.tsx | 116 +++++++------ src/lib/components/Modal/ModalBody.tsx | 24 ++- src/lib/components/Modal/ModalFooter.tsx | 23 ++- src/lib/components/Modal/ModalHeader.tsx | 23 ++- src/lib/components/Modal/index.tsx | 67 +++----- src/lib/components/Spinner/index.tsx | 2 +- src/lib/theme/default.ts | 56 ++++++ 12 files changed, 313 insertions(+), 233 deletions(-) diff --git a/src/docs/pages/ModalPage.tsx b/src/docs/pages/ModalPage.tsx index a0a56b1e1..eb3d4d207 100644 --- a/src/docs/pages/ModalPage.tsx +++ b/src/docs/pages/ModalPage.tsx @@ -1,10 +1,8 @@ import type { FC } from 'react'; import { useState } from 'react'; import { HiOutlineExclamationCircle } from 'react-icons/hi'; - import type { CodeExample } from './DemoPage'; import { DemoPage } from './DemoPage'; -import type { ModalSize, ModalPlacement } from '../../lib'; import { Button, Checkbox, Label, Modal, Select, TextInput } from '../../lib'; const ModalPage: FC = () => { @@ -20,16 +18,18 @@ const ModalPage: FC = () => { setOpenModal(undefined)}> Terms of Service - -

- With less than a month to go before the European Union enacts new consumer privacy laws for its - citizens, companies around the world are updating their terms of service agreements to comply. -

-

- The European Union’s General Data Protection Regulation (G.D.P.R.) goes into effect on May 25 and is - meant to ensure a common set of data rights in the European Union. It requires organizations to notify - users as soon as possible of high-risk data breaches that could personally affect them. -

+ +
+

+ With less than a month to go before the European Union enacts new consumer privacy laws for its + citizens, companies around the world are updating their terms of service agreements to comply. +

+

+ The European Union’s General Data Protection Regulation (G.D.P.R.) goes into effect on May 25 and is + meant to ensure a common set of data rights in the European Union. It requires organizations to notify + users as soon as possible of high-risk data breaches that could personally affect them. +

+
@@ -48,18 +48,20 @@ const ModalPage: FC = () => { setOpenModal(undefined)}> - - -

- Are you sure you want to delete this product? -

-
- - + +
+ +

+ Are you sure you want to delete this product? +

+
+ + +
@@ -73,40 +75,42 @@ const ModalPage: FC = () => { setOpenModal(undefined)}> - -

Sign in to our platform

-
- - -
-
- - -
-
-
- - + +
+

Sign in to our platform

+
+ + +
+
+ + +
+
+
+ + +
+ + Lost Password? + +
+ +
+ Not registered?{' '} + + Create account +
- - Lost Password? - -
- -
- Not registered?{' '} - - Create account -
@@ -132,18 +136,20 @@ const ModalPage: FC = () => {
- setOpenModal(undefined)}> + setOpenModal(undefined)}> Small modal - -

- With less than a month to go before the European Union enacts new consumer privacy laws for its - citizens, companies around the world are updating their terms of service agreements to comply. -

-

- The European Union’s General Data Protection Regulation (G.D.P.R.) goes into effect on May 25 and is - meant to ensure a common set of data rights in the European Union. It requires organizations to notify - users as soon as possible of high-risk data breaches that could personally affect them. -

+ +
+

+ With less than a month to go before the European Union enacts new consumer privacy laws for its + citizens, companies around the world are updating their terms of service agreements to comply. +

+

+ The European Union’s General Data Protection Regulation (G.D.P.R.) goes into effect on May 25 and is + meant to ensure a common set of data rights in the European Union. It requires organizations to notify + users as soon as possible of high-risk data breaches that could personally affect them. +

+
@@ -173,22 +179,20 @@ const ModalPage: FC = () => {
- setOpenModal(undefined)} - > + setOpenModal(undefined)}> Small modal - -

- With less than a month to go before the European Union enacts new consumer privacy laws for its - citizens, companies around the world are updating their terms of service agreements to comply. -

-

- The European Union’s General Data Protection Regulation (G.D.P.R.) goes into effect on May 25 and is - meant to ensure a common set of data rights in the European Union. It requires organizations to notify - users as soon as possible of high-risk data breaches that could personally affect them. -

+ +
+

+ With less than a month to go before the European Union enacts new consumer privacy laws for its + citizens, companies around the world are updating their terms of service agreements to comply. +

+

+ The European Union’s General Data Protection Regulation (G.D.P.R.) goes into effect on May 25 and is + meant to ensure a common set of data rights in the European Union. It requires organizations to notify + users as soon as possible of high-risk data breaches that could personally affect them. +

+
diff --git a/src/lib/components/Avatar/index.tsx b/src/lib/components/Avatar/index.tsx index e83c4e18f..8a8ea8442 100644 --- a/src/lib/components/Avatar/index.tsx +++ b/src/lib/components/Avatar/index.tsx @@ -17,7 +17,7 @@ export interface AvatarProps extends PropsWithChildren> { statusPosition?: keyof FlowbitePositions; } -export interface AvatarSizes extends FlowbiteSizes { +export interface AvatarSizes extends Pick { [key: string]: string; } diff --git a/src/lib/components/Button/index.tsx b/src/lib/components/Button/index.tsx index 6b2f57f63..dcb86fe69 100644 --- a/src/lib/components/Button/index.tsx +++ b/src/lib/components/Button/index.tsx @@ -39,7 +39,7 @@ export interface ButtonOutlineColors extends Pick { [key: string]: string; } -export interface ButtonSizes extends FlowbiteSizes { +export interface ButtonSizes extends Pick { [key: string]: string; } diff --git a/src/lib/components/Flowbite/FlowbiteTheme.ts b/src/lib/components/Flowbite/FlowbiteTheme.ts index 6b45caa05..a7d410bfe 100644 --- a/src/lib/components/Flowbite/FlowbiteTheme.ts +++ b/src/lib/components/Flowbite/FlowbiteTheme.ts @@ -7,6 +7,7 @@ import type { ButtonGradientColors, ButtonGradientDuoToneColors } from '../Butto import type { DeepPartial } from '../../helpers/deep-partial'; import type { PositionInButtonGroup } from '../Button/ButtonGroup'; import type { StarSizes } from '../Rating'; +import type { ModalPositions, ModalSizes } from '../Modal'; export type CustomFlowbiteTheme = DeepPartial; @@ -142,6 +143,33 @@ export interface FlowbiteTheme { snap: string; }; }; + modal: { + base: string; + show: FlowbiteBoolean; + content: { + base: string; + inner: string; + }; + body: { + base: string; + popup: string; + }; + header: { + base: string; + popup: string; + title: string; + close: { + base: string; + icon: string; + }; + }; + footer: { + base: string; + popup: string; + }; + sizes: ModalSizes; + positions: ModalPositions; + }; rating: { base: string; star: { @@ -257,8 +285,13 @@ export type FlowbiteHeadingLevel = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; export interface FlowbitePositions { 'bottom-left': string; 'bottom-right': string; + 'bottom-center': string; 'top-left': string; + 'top-center': string; 'top-right': string; + 'center-left': string; + center: string; + 'center-right': string; } export interface FlowbiteSizes { @@ -267,4 +300,10 @@ export interface FlowbiteSizes { md: string; lg: string; xl: string; + '2xl': string; + '3xl': string; + '4xl': string; + '5xl': string; + '6xl': string; + '7xl': string; } diff --git a/src/lib/components/Modal/Modal.spec.tsx b/src/lib/components/Modal/Modal.spec.tsx index 80ca37bc0..1a962d748 100644 --- a/src/lib/components/Modal/Modal.spec.tsx +++ b/src/lib/components/Modal/Modal.spec.tsx @@ -50,16 +50,18 @@ const TestModal = ({ root }: Pick): JSX.Element => { setOpen(false)}> Terms of Service - -

- With less than a month to go before the European Union enacts new consumer privacy laws for its citizens, - companies around the world are updating their terms of service agreements to comply. -

-

- The European Union’s General Data Protection Regulation (G.D.P.R.) goes into effect on May 25 and is meant - to ensure a common set of data rights in the European Union. It requires organizations to notify users as - soon as possible of high-risk data breaches that could personally affect them. -

+ +
+

+ With less than a month to go before the European Union enacts new consumer privacy laws for its citizens, + companies around the world are updating their terms of service agreements to comply. +

+

+ The European Union’s General Data Protection Regulation (G.D.P.R.) goes into effect on May 25 and is meant + to ensure a common set of data rights in the European Union. It requires organizations to notify users as + soon as possible of high-risk data breaches that could personally affect them. +

+
diff --git a/src/lib/components/Modal/Modal.stories.tsx b/src/lib/components/Modal/Modal.stories.tsx index 70101a9ac..1a94eceed 100644 --- a/src/lib/components/Modal/Modal.stories.tsx +++ b/src/lib/components/Modal/Modal.stories.tsx @@ -31,16 +31,18 @@ Default.args = { children: ( <> Terms of Service - -

- With less than a month to go before the European Union enacts new consumer privacy laws for its citizens, - companies around the world are updating their terms of service agreements to comply. -

-

- The European Union’s General Data Protection Regulation (G.D.P.R.) goes into effect on May 25 and is meant to - ensure a common set of data rights in the European Union. It requires organizations to notify users as soon as - possible of high-risk data breaches that could personally affect them. -

+ +
+

+ With less than a month to go before the European Union enacts new consumer privacy laws for its citizens, + companies around the world are updating their terms of service agreements to comply. +

+

+ The European Union’s General Data Protection Regulation (G.D.P.R.) goes into effect on May 25 and is meant + to ensure a common set of data rights in the European Union. It requires organizations to notify users as + soon as possible of high-risk data breaches that could personally affect them. +

+
@@ -56,18 +58,20 @@ export const PopUp = Template.bind({}); PopUp.storyName = 'Pop-up modal'; PopUp.args = { children: ( - - -

- Are you sure you want to delete this product? -

-
- - + +
+ +

+ Are you sure you want to delete this product? +

+
+ + +
), @@ -79,40 +83,42 @@ FormElements.args = { children: ( <> - -

Sign in to our platform

-
- - -
-
- - -
-
-
- - + +
+

Sign in to our platform

+
+ + +
+
+ + +
+
+
+ + +
+ + Lost Password? + +
+ +
+ Not registered?{' '} + + Create account +
- - Lost Password? - -
- -
- Not registered?{' '} - - Create account -
diff --git a/src/lib/components/Modal/ModalBody.tsx b/src/lib/components/Modal/ModalBody.tsx index 732568efa..5a42c79a8 100644 --- a/src/lib/components/Modal/ModalBody.tsx +++ b/src/lib/components/Modal/ModalBody.tsx @@ -1,24 +1,22 @@ -import type { FC, PropsWithChildren } from 'react'; +import type { ComponentProps, FC, PropsWithChildren } from 'react'; import classNames from 'classnames'; - import { useModalContext } from './ModalContext'; +import { excludeClassName } from '../../helpers/exclude'; +import { useTheme } from '../Flowbite/ThemeContext'; -export type ModalBodyProps = PropsWithChildren<{ - className?: string; -}>; +export type ModalBodyProps = PropsWithChildren, 'className'>>; -export const ModalBody: FC = ({ children, className }) => { +export const ModalBody: FC = ({ children, ...props }) => { const { popup } = useModalContext(); + const theme = useTheme().theme.modal.body; + const theirProps = excludeClassName(props); return (
{children}
diff --git a/src/lib/components/Modal/ModalFooter.tsx b/src/lib/components/Modal/ModalFooter.tsx index e7706cb41..8a1b151ed 100644 --- a/src/lib/components/Modal/ModalFooter.tsx +++ b/src/lib/components/Modal/ModalFooter.tsx @@ -1,24 +1,23 @@ -import type { FC, PropsWithChildren } from 'react'; +import type { ComponentProps, FC, PropsWithChildren } from 'react'; import classNames from 'classnames'; import { useModalContext } from './ModalContext'; +import { useTheme } from '../Flowbite/ThemeContext'; +import { excludeClassName } from '../../helpers/exclude'; -export type ModalFooterProps = PropsWithChildren<{ - className?: string; -}>; +export type ModalFooterProps = PropsWithChildren, 'className'>>; -export const ModalFooter: FC = ({ children, className }) => { +export const ModalFooter: FC = ({ children, ...props }) => { const { popup } = useModalContext(); + const theme = useTheme().theme.modal.footer; + const theirProps = excludeClassName(props); return (
{children}
diff --git a/src/lib/components/Modal/ModalHeader.tsx b/src/lib/components/Modal/ModalHeader.tsx index d21eda912..ae475e6e1 100644 --- a/src/lib/components/Modal/ModalHeader.tsx +++ b/src/lib/components/Modal/ModalHeader.tsx @@ -1,30 +1,27 @@ import type { ComponentProps, FC, PropsWithChildren } from 'react'; import classNames from 'classnames'; import { HiOutlineX } from 'react-icons/hi'; - import { useModalContext } from './ModalContext'; import { excludeClassName } from '../../helpers/exclude'; +import { useTheme } from '../Flowbite/ThemeContext'; -export const ModalHeader: FC>> = ({ children, ...props }): JSX.Element => { - const theirProps = excludeClassName(props); +export type ModalHeaderProps = PropsWithChildren, 'className'>>; +export const ModalHeader: FC = ({ children, ...props }): JSX.Element => { const { popup, onClose } = useModalContext(); + const theme = useTheme().theme.modal.header; + const theirProps = excludeClassName(props); return (
-

{children}

-
); diff --git a/src/lib/components/Modal/index.tsx b/src/lib/components/Modal/index.tsx index 806198d73..cffbde63c 100644 --- a/src/lib/components/Modal/index.tsx +++ b/src/lib/components/Modal/index.tsx @@ -1,50 +1,32 @@ -import type { FC, PropsWithChildren } from 'react'; +import type { ComponentProps, FC, PropsWithChildren } from 'react'; import { useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import classNames from 'classnames'; - import { ModalHeader } from './ModalHeader'; import { ModalBody } from './ModalBody'; import { ModalFooter } from './ModalFooter'; import { ModalContext } from './ModalContext'; import windowExists from '../../helpers/window-exists'; +import { useTheme } from '../Flowbite/ThemeContext'; +import { excludeClassName } from '../../helpers/exclude'; +import type { FlowbitePositions, FlowbiteSizes } from '../Flowbite/FlowbiteTheme'; + +export interface ModalPositions extends FlowbitePositions { + [key: string]: string; +} -export type ModalSize = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | '6xl' | '7xl'; -export type ModalPlacement = `${'top' | 'bottom'}-${'left' | 'center' | 'right'}` | `center${'' | '-left' | '-right'}`; +export interface ModalSizes extends Omit { + [key: string]: string; +} -export type ModalProps = PropsWithChildren<{ +export interface ModalProps extends PropsWithChildren, 'className'>> { onClose?: () => void; - placement?: ModalPlacement; + position?: keyof ModalPositions; popup?: boolean; root?: HTMLElement; show?: boolean; - size?: ModalSize; -}>; - -const sizeClasses: Record = { - sm: 'max-w-sm', - md: 'max-w-md', - lg: 'max-w-lg', - xl: 'max-w-xl', - '2xl': 'max-w-2xl', - '3xl': 'max-w-3xl', - '4xl': 'max-w-4xl', - '5xl': 'max-w-5xl', - '6xl': 'max-w-6xl', - '7xl': 'max-w-7xl', -}; - -const placementClasses: Record = { - 'top-left': 'items-start justify-start', - 'top-center': 'items-start justify-center', - 'top-right': 'items-start justify-end', - 'center-left': 'items-center justify-start', - center: 'items-center justify-center', - 'center-right': 'items-center justify-end', - 'bottom-right': 'items-end justify-end', - 'bottom-center': 'items-end justify-center', - 'bottom-left': 'items-end justify-start', -}; + size?: keyof ModalSizes; +} const ModalComponent: FC = ({ children, @@ -52,10 +34,13 @@ const ModalComponent: FC = ({ show, popup, size = '2xl', - placement = 'center', + position = 'center', onClose, + ...props }): JSX.Element | null => { const [container] = useState(windowExists() ? document.createElement('div') : undefined); + const theme = useTheme().theme.modal; + const theirProps = excludeClassName(props); useEffect(() => { if (!container || !root || !show) { @@ -74,18 +59,12 @@ const ModalComponent: FC = ({
-
-
{children}
+
+
{children}
, diff --git a/src/lib/components/Spinner/index.tsx b/src/lib/components/Spinner/index.tsx index 262f51d5c..c61b5e972 100644 --- a/src/lib/components/Spinner/index.tsx +++ b/src/lib/components/Spinner/index.tsx @@ -15,7 +15,7 @@ export interface SpinnerColors [key: string]: string; } -export interface SpinnerSizes extends FlowbiteSizes { +export interface SpinnerSizes extends Pick { [key: string]: string; } diff --git a/src/lib/theme/default.ts b/src/lib/theme/default.ts index e0d1be3f2..cdc9fad9b 100644 --- a/src/lib/theme/default.ts +++ b/src/lib/theme/default.ts @@ -79,9 +79,14 @@ export default { }, statusPosition: { 'bottom-left': '-bottom-1 -left-1', + 'bottom-center': '-botton-1 center', 'bottom-right': '-bottom-1 -right-1', 'top-left': '-top-1 -left-1', + 'top-center': '-top-1 center', 'top-right': '-top-1 -right-1', + 'center-right': 'center -right-1', + center: 'center center', + 'center-left': 'center -left-1', }, }, badge: { @@ -254,6 +259,57 @@ export default { snap: 'snap-x', }, }, + modal: { + base: 'fixed top-0 right-0 left-0 z-50 h-modal overflow-y-auto overflow-x-hidden md:inset-0 md:h-full', + show: { + on: 'flex bg-gray-900 bg-opacity-50 dark:bg-opacity-80', + off: 'hidden', + }, + content: { + base: 'relative h-full w-full p-4 md:h-auto', + inner: 'relative rounded-lg bg-white shadow dark:bg-gray-700', + }, + body: { + base: 'p-6', + popup: 'pt-0', + }, + header: { + base: 'flex items-start justify-between rounded-t dark:border-gray-600 border-b p-5', + popup: '!p-2 !border-b-0', + title: 'text-xl font-medium text-gray-900 dark:text-white', + close: { + base: 'ml-auto inline-flex items-center rounded-lg bg-transparent p-1.5 text-sm text-gray-400 hover:bg-gray-200 hover:text-gray-900 dark:hover:bg-gray-600 dark:hover:text-white', + icon: 'h-5 w-5', + }, + }, + footer: { + base: 'flex items-center space-x-2 rounded-b border-gray-200 p-6 dark:border-gray-600', + popup: 'border-t', + }, + sizes: { + sm: 'max-w-sm', + md: 'max-w-md', + lg: 'max-w-lg', + xl: 'max-w-xl', + '2xl': 'max-w-2xl', + '3xl': 'max-w-3xl', + '4xl': 'max-w-4xl', + '5xl': 'max-w-5xl', + '6xl': 'max-w-6xl', + '7xl': 'max-w-7xl', + }, + positions: { + 'top-left': 'items-start justify-start', + 'top-center': 'items-start justify-center', + 'top-right': 'items-start justify-end', + 'center-left': 'items-center justify-start', + center: 'items-center justify-center', + 'center-right': 'items-center justify-end', + 'bottom-right': 'items-end justify-end', + 'bottom-center': 'items-end justify-center', + 'bottom-left': 'items-end justify-start', + }, + }, rating: { base: 'flex items-center', star: {