diff --git a/.storybook/theme.ts b/.storybook/theme.ts index e9e5f90f..f889a8cb 100644 --- a/.storybook/theme.ts +++ b/.storybook/theme.ts @@ -1,6 +1,6 @@ -import { create } from '@storybook/theming/create'; +import { create } from '@storybook/theming/create' export default create({ base: 'light', brandTitle: 'NearForm Quantum' -}); +}) diff --git a/package-lock.json b/package-lock.json index 75753337..318446fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-scroll-area": "^1.0.5", @@ -4217,6 +4218,67 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz", + "integrity": "sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-controllable-state": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-scope": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz", + "integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz", @@ -4265,7 +4327,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", "integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==", - "dev": true, "dependencies": { "@babel/runtime": "^7.13.10" }, @@ -7542,7 +7603,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.3.tgz", "integrity": "sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==", - "dev": true, "dependencies": { "tslib": "^2.0.0" }, @@ -9530,8 +9590,7 @@ "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", - "dev": true + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" }, "node_modules/detect-package-manager": { "version": "2.0.1", @@ -10991,7 +11050,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "dev": true, "engines": { "node": ">=6" } @@ -11783,7 +11841,6 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, "dependencies": { "loose-envify": "^1.0.0" } @@ -16820,7 +16877,6 @@ "version": "2.5.5", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", - "dev": true, "dependencies": { "react-remove-scroll-bar": "^2.3.3", "react-style-singleton": "^2.2.1", @@ -16845,7 +16901,6 @@ "version": "2.3.4", "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz", "integrity": "sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==", - "dev": true, "dependencies": { "react-style-singleton": "^2.2.1", "tslib": "^2.0.0" @@ -16867,7 +16922,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", - "dev": true, "dependencies": { "get-nonce": "^1.0.0", "invariant": "^2.2.4", @@ -18621,8 +18675,7 @@ "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tsup": { "version": "8.0.1", @@ -19424,7 +19477,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz", "integrity": "sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==", - "dev": true, "dependencies": { "tslib": "^2.0.0" }, @@ -19458,7 +19510,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", - "dev": true, "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" diff --git a/package.json b/package.json index 49a5cc60..7e2af0e1 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ }, "dependencies": { "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-scroll-area": "^1.0.5", diff --git a/src/assets/build/close.icon.tsx b/src/assets/build/close.icon.tsx index 1ab58c3f..5113ad53 100644 --- a/src/assets/build/close.icon.tsx +++ b/src/assets/build/close.icon.tsx @@ -12,7 +12,6 @@ export const CloseIcon = (props: any) => ( diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index c8e8f39d..375735f8 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -103,7 +103,7 @@ const Input = React.forwardRef( ) => { const leftSideComponent = leftSideChild ?? convertTypeToComponent.left[`${type}`] - const rightSideComponent = rightSideChild ?? + const rightSideComponent = rightSideChild ?? return (
diff --git a/src/components/Modal/index.tsx b/src/components/Modal/index.tsx new file mode 100644 index 00000000..d92b0d69 --- /dev/null +++ b/src/components/Modal/index.tsx @@ -0,0 +1,135 @@ +'use client' + +import * as React from 'react' +import * as DialogPrimitive from '@radix-ui/react-dialog' +import { CloseIcon } from '@/assets' + +import { cn } from '@/lib/utils' + +const Modal = DialogPrimitive.Root + +const ModalTrigger = DialogPrimitive.Trigger + +const ModalPortal = DialogPrimitive.Portal + +const ModalCloseFooter = DialogPrimitive.Close + +const ModalOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ModalOverlay.displayName = DialogPrimitive.Overlay.displayName + +const ModalContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + +)) +ModalContent.displayName = DialogPrimitive.Content.displayName + +const ModalHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +ModalHeader.displayName = 'ModalHeader' + +const ModalFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +ModalFooter.displayName = 'ModalFooter' + +const ModalTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ModalTitle.displayName = DialogPrimitive.Title.displayName + +const ModalClose = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +
+ +
+ Close +
+)) + +const ModalDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ModalDescription.displayName = DialogPrimitive.Description.displayName + +export { + Modal, + ModalPortal, + ModalOverlay, + ModalClose, + ModalCloseFooter, + ModalTrigger, + ModalContent, + ModalHeader, + ModalFooter, + ModalTitle, + ModalDescription +} diff --git a/src/components/index.ts b/src/components/index.ts index a713d31c..4ee92621 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -8,6 +8,7 @@ export * from './Tooltip' export * from './Input' export * from './Switch' export * from './Chip' +export * from './Modal' export * from './WebsiteFooter' export * from './Select' export * from './Calendar' diff --git a/stories/Modal/Docs.mdx b/stories/Modal/Docs.mdx new file mode 100644 index 00000000..7ef06286 --- /dev/null +++ b/stories/Modal/Docs.mdx @@ -0,0 +1,373 @@ +import { Canvas, Meta } from '@storybook/blocks' + +import * as ModalStories from './Modal.stories' + + + +# Modal + +A modal is a window that can be overlaid on the main window, which when opened, the main window is left partially visible and doesn't allow any user interaction. + +## Default + + + +## Attributes + +The Modal component is built up of several components, the general structure of which is shown below. + +```bash +└── Modal + ├── 1. Modal Trigger + ├── 2. Modal Content +    └── Modal Header +    └── Modal Title +   └── Modal Close + └── Modal Description + └── Modal Footer + └── Modal Close Footer +``` + +### Component Attributes + +#### Modal + +Contains all parts of the modal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefault
defaultOpenboolean-
openboolean-
onOpenChangefunction-
modalbooleantrue
+ +#### Modal Trigger + +The button that opens the modal. + + + + + + + + + + + + + + +
AttributeTypeDefault
asChildbooleanfalse
+ +#### Modal Content + +Contains content to be rendered in the open modal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefault
asChildbooleanfalse
forceMountboolean-
onOpenAutoFocusfunction-
onCloseAutoFocusfunction-
onEscapeKeyDownfunction-
onPointerDownOutsidefunction-
onInteractOutsidefunction-
+ +#### Close/Close Footer + +The button that opens the modal. + + + + + + + + + + + + + + +
AttributeTypeDefault
asChildbooleanfalse
+ +### Title + +An accessible title to be announced when the modal is opened. + + + + + + + + + + + + + + +
AttributeTypeDefault
asChildbooleanfalse
+ +### Description + +An optional accessible description to be announced when the modal is opened. To remove the description entirely, pass `aria-describedby={undefined}` to the Modal Content component. + + + + + + + + + + + + + + +
AttributeTypeDefault
asChildbooleanfalse
+ +To hide the title or description you can wrap it inside a Visual Hidden utility like this ``. More info about this [here](https://www.radix-ui.com/primitives/docs/utilities/visually-hidden). + +You can control the styling by using the Data-attributes in the className. The values are either "open" or "closed". + +See the example below for further clarification. + +## Modal Content + +```tsx +const ModalContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + +)) +ModalContent.displayName = DialogPrimitive.Content.displayName +``` + +## Modal Header + +```tsx +const ModalHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +ModalHeader.displayName = 'ModalHeader' +``` + +## Modal Footer + +```tsx +const ModalFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +ModalFooter.displayName = 'ModalFooter' +``` + +## Modal Title + +```tsx +const ModalTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ModalTitle.displayName = DialogPrimitive.Title.displayName +``` + +## Modal Close + +```tsx +const ModalClose = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +
+ +
+ Close +
+)) +``` + +## Modal Description + +```tsx +const ModalDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ModalDescription.displayName = DialogPrimitive.Description.displayName +``` + +## Example + +```tsx +import { + Modal, + ModalContent, + ModalClose, + ModalCloseFooter, + ModalFooter, + ModalDescription, + ModalHeader, + ModalTitle, + ModalTrigger +} from '@/components/Modal' +import { Button } from '@/components' + +export const ModalDemo = () => { + return ( + + + + + + + Title + + + +
+ Replace this component with your content +
+
+ + + +
+
+ Additional text line +
+
+ Additional text line +
+
+
+
+
+
+ ) +} +``` diff --git a/stories/Modal/Modal.example.tsx b/stories/Modal/Modal.example.tsx new file mode 100644 index 00000000..62d2db9a --- /dev/null +++ b/stories/Modal/Modal.example.tsx @@ -0,0 +1,54 @@ +import { + Modal, + ModalContent, + ModalClose, + ModalCloseFooter, + ModalFooter, + ModalDescription, + ModalHeader, + ModalTitle, + ModalTrigger +} from '@/components/Modal' +import { Button } from '@/components' + +export const ModalDemo = () => { + return ( + + + + + + + Title + + + +
+ Replace this component with your content +
+
+ + + +
+
+ Additional text line +
+
+ Additional text line +
+
+
+
+
+
+ ) +} diff --git a/stories/Modal/Modal.stories.ts b/stories/Modal/Modal.stories.ts new file mode 100644 index 00000000..9edcf9e3 --- /dev/null +++ b/stories/Modal/Modal.stories.ts @@ -0,0 +1,29 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import { ModalDemo } from './Modal.example' + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export +const meta = { + title: 'Components/Modal', + component: ModalDemo, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout + layout: 'centered' + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs + argTypes: { + className: { + controle: 'text', + description: 'Alter the className to change the style' + } + } + // More on argTypes: https://storybook.js.org/docs/react/api/argtypes +} satisfies Meta + +export default meta +type Story = StoryObj + +// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args +export const Default: Story = { + args: {} +} diff --git a/tailwind.config.ts b/tailwind.config.ts index 9cf473dd..9c021a13 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -9,12 +9,12 @@ export default { theme: { colors: colors, boxShadow: { - sm: '0px 1px 2px 0px', - DEFAULT: '0px 1px 2px -1px', - md: '0px 2px 4px -2px', - lg: '0px 4px 6px 0px', - xl: '0px 20px 25px -5px', - '2xl': '0px 25px 50px -12px', + sm: '0px 1px 2px 0px rgba(0, 0, 0, 0.05)', + DEFAULT: '0px 1px 2px -1px rgba(0, 0, 0, 0.05)', + md: '0px 2px 4px -2px rgba(0, 0, 0, 0.05)', + lg: '0px 4px 6px 0px rgba(0, 0, 0, 0.05)', + xl: '0px 20px 25px -5px rgba(0, 0, 0, 0.05)', + '2xl': '0px 25px 50px -12px rgba(0, 0, 0, 0.05)', blue: '0px 0px 0px 4px rgb(118,169,250)', red: '0px 0px 0px 4px rgb(249,128,128)', green: '0px 0px 0px 4px rgb(49,196,141,1)',