From 77043a582db26bfa56fecc6fae36fa8a714651fc Mon Sep 17 00:00:00 2001 From: Eelco Wiersma Date: Sat, 9 Nov 2024 11:02:45 +0000 Subject: [PATCH] feat: update modals --- packages/saas-ui-assets/package.json | 4 +- packages/saas-ui-auth-provider/package.json | 2 +- packages/saas-ui-auth0/package.json | 4 +- packages/saas-ui-clerk/package.json | 4 +- packages/saas-ui-command-bar/package.json | 4 +- packages/saas-ui-core/package.json | 18 +- .../error-boundary/error-boundary.stories.tsx | 25 +- .../src/error-boundary/error-provider.tsx | 22 ++ .../saas-ui-core/src/error-boundary/index.ts | 2 + packages/saas-ui-data-table/package.json | 4 +- packages/saas-ui-date-picker/package.json | 4 +- packages/saas-ui-file-upload/package.json | 4 +- packages/saas-ui-forms/ajv/package.json | 2 +- packages/saas-ui-forms/package.json | 4 +- packages/saas-ui-forms/yup/package.json | 2 +- packages/saas-ui-forms/zod/package.json | 2 +- packages/saas-ui-hooks/package.json | 4 +- packages/saas-ui-hotkeys/package.json | 4 +- packages/saas-ui-modals-provider/README.md | 76 ++++ packages/saas-ui-modals-provider/index.ts | 1 + packages/saas-ui-modals-provider/package.json | 60 +++ .../src/create-modals.tsx | 10 +- packages/saas-ui-modals-provider/src/index.ts | 20 + .../src/provider.tsx | 94 ++--- packages/saas-ui-modals-provider/src/types.ts | 83 ++++ .../stories/form.bac.tsx | 357 ++++++++++++++++++ .../stories/modals.stories.tsx | 272 +++++++++++++ .../tests/use-modals.test.tsx | 224 +++++++++++ .../saas-ui-modals-provider/tsconfig.json | 15 + .../saas-ui-modals-provider/tsup.config.ts | 7 + packages/saas-ui-modals/package.json | 17 +- .../src/{dialog.tsx => alert-dialog.tsx} | 112 +++--- packages/saas-ui-modals/src/default-modals.ts | 6 +- packages/saas-ui-modals/src/drawer.tsx | 77 ++-- packages/saas-ui-modals/src/index.ts | 42 +-- packages/saas-ui-modals/src/modal.tsx | 66 ++-- packages/saas-ui-modals/src/types.ts | 79 ++-- .../saas-ui-modals/stories/modals.stories.tsx | 115 +++--- packages/saas-ui-nprogress/package.json | 4 +- packages/saas-ui-palette/package.json | 4 +- packages/saas-ui-react/package.json | 4 +- .../src/components/dialog/dialog.tsx | 5 +- .../src/components/dialog/index.ts | 1 + .../src/components/dialog/namespace.ts | 5 + .../src/components/drawer/drawer.tsx | 5 +- .../src/components/drawer/namespace.ts | 6 + packages/saas-ui-react/src/index.ts | 3 + .../src/provider/index.ts | 0 .../src/provider/sui-provider.tsx | 11 +- .../src/provider/use-link.test.tsx | 0 .../src/provider/use-link.tsx | 0 packages/saas-ui-supabase/package.json | 4 +- packages/saas-ui-theme-glass/package.json | 4 +- packages/saas-ui-theme/package.json | 4 +- packages/saas-ui-use-hotkeys/package.json | 4 +- yarn.lock | 20 +- 56 files changed, 1513 insertions(+), 419 deletions(-) create mode 100644 packages/saas-ui-core/src/error-boundary/error-provider.tsx create mode 100644 packages/saas-ui-modals-provider/README.md create mode 100644 packages/saas-ui-modals-provider/index.ts create mode 100644 packages/saas-ui-modals-provider/package.json rename packages/{saas-ui-modals => saas-ui-modals-provider}/src/create-modals.tsx (71%) create mode 100644 packages/saas-ui-modals-provider/src/index.ts rename packages/{saas-ui-modals => saas-ui-modals-provider}/src/provider.tsx (80%) create mode 100644 packages/saas-ui-modals-provider/src/types.ts create mode 100644 packages/saas-ui-modals-provider/stories/form.bac.tsx create mode 100644 packages/saas-ui-modals-provider/stories/modals.stories.tsx create mode 100644 packages/saas-ui-modals-provider/tests/use-modals.test.tsx create mode 100644 packages/saas-ui-modals-provider/tsconfig.json create mode 100644 packages/saas-ui-modals-provider/tsup.config.ts rename packages/saas-ui-modals/src/{dialog.tsx => alert-dialog.tsx} (52%) rename packages/{saas-ui-core => saas-ui-react}/src/provider/index.ts (100%) rename packages/{saas-ui-core => saas-ui-react}/src/provider/sui-provider.tsx (86%) rename packages/{saas-ui-core => saas-ui-react}/src/provider/use-link.test.tsx (100%) rename packages/{saas-ui-core => saas-ui-react}/src/provider/use-link.tsx (100%) diff --git a/packages/saas-ui-assets/package.json b/packages/saas-ui-assets/package.json index 88cf685ea..2157f1325 100644 --- a/packages/saas-ui-assets/package.json +++ b/packages/saas-ui-assets/package.json @@ -31,12 +31,12 @@ "publishConfig": { "access": "public" }, - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "homepage": "https://saas-ui.dev/", "repository": { "type": "git", - "url": "https://github.com/saas-js/saas-ui", + "url": "git+https://github.com/saas-js/saas-ui.git", "directory": "packages/saas-ui-auth" }, "keywords": [ diff --git a/packages/saas-ui-auth-provider/package.json b/packages/saas-ui-auth-provider/package.json index 47edf1de0..5bb023c39 100644 --- a/packages/saas-ui-auth-provider/package.json +++ b/packages/saas-ui-auth-provider/package.json @@ -31,7 +31,7 @@ "publishConfig": { "access": "public" }, - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "homepage": "https://saas-ui.dev/", "repository": { diff --git a/packages/saas-ui-auth0/package.json b/packages/saas-ui-auth0/package.json index f8c84badc..5359abe6f 100644 --- a/packages/saas-ui-auth0/package.json +++ b/packages/saas-ui-auth0/package.json @@ -32,12 +32,12 @@ "publishConfig": { "access": "public" }, - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "homepage": "https://saas-ui.dev/", "repository": { "type": "git", - "url": "https://github.com/saas-js/saas-ui", + "url": "git+https://github.com/saas-js/saas-ui.git", "directory": "packages/saas-ui-auth0" }, "keywords": [ diff --git a/packages/saas-ui-clerk/package.json b/packages/saas-ui-clerk/package.json index d3d3ac653..f5086636c 100644 --- a/packages/saas-ui-clerk/package.json +++ b/packages/saas-ui-clerk/package.json @@ -31,12 +31,12 @@ "publishConfig": { "access": "public" }, - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "homepage": "https://saas-ui.dev/", "repository": { "type": "git", - "url": "https://github.com/saas-js/saas-ui", + "url": "git+https://github.com/saas-js/saas-ui.git", "directory": "packages/saas-ui-clerk" }, "keywords": [ diff --git a/packages/saas-ui-command-bar/package.json b/packages/saas-ui-command-bar/package.json index 5bd28e89a..7057a3ff6 100644 --- a/packages/saas-ui-command-bar/package.json +++ b/packages/saas-ui-command-bar/package.json @@ -48,12 +48,12 @@ "publishConfig": { "access": "public" }, - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "homepage": "https://saas-ui.dev/", "repository": { "type": "git", - "url": "https://github.com/saas-js/saas-ui", + "url": "git+https://github.com/saas-js/saas-ui.git", "directory": "packages/saas-ui-command-bar" }, "keywords": [ diff --git a/packages/saas-ui-core/package.json b/packages/saas-ui-core/package.json index 5046b8713..b52ab4388 100644 --- a/packages/saas-ui-core/package.json +++ b/packages/saas-ui-core/package.json @@ -1,7 +1,7 @@ { "name": "@saas-ui/core", "version": "2.5.5", - "description": "Saas UI - The React component library for startups.", + "description": "Unstyled primitives for building React applications.", "type": "module", "exports": { ".": { @@ -60,24 +60,22 @@ "publishConfig": { "access": "public" }, - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "homepage": "https://saas-ui.dev/", "repository": { "type": "git", - "url": "https://github.com/saas-js/saas-ui", - "directory": "packages/saas-ui-react" + "url": "git+https://github.com/saas-js/saas-ui.git", + "directory": "packages/saas-ui-core" }, "keywords": [ "react", "ui", - "chakra-ui", "design-system", "react-components", "uikit", "accessible", "components", - "emotion", "library" ], "storybook": { @@ -86,24 +84,16 @@ }, "dependencies": { "@ark-ui/react": "^4.2.0", - "@react-aria/interactions": "^3.22.4", "@react-aria/utils": "^3.25.3", - "@saas-ui/theme": "workspace:*", "@zag-js/dom-event": "^0.77.0", "@zag-js/dom-utils": "^0.2.4" }, "peerDependencies": { - "@chakra-ui/react": ">=3.0.0-next.13", - "@emotion/react": "^11", - "@emotion/styled": "^11", "react": ">=18", "react-dom": ">=18" }, "devDependencies": { - "@chakra-ui/react": "^3.0.2", - "@react-types/shared": "^3.25.0", "jotai": "^2.10.1", - "react-icons": "^5.3.0", "tsup": "^8.3.5" } } diff --git a/packages/saas-ui-core/src/error-boundary/error-boundary.stories.tsx b/packages/saas-ui-core/src/error-boundary/error-boundary.stories.tsx index 5037ea54c..c50834c93 100644 --- a/packages/saas-ui-core/src/error-boundary/error-boundary.stories.tsx +++ b/packages/saas-ui-core/src/error-boundary/error-boundary.stories.tsx @@ -1,11 +1,8 @@ import * as React from 'react' -import { Button, EmptyState } from '@chakra-ui/react' import { Meta } from '@storybook/react' -import { system } from '../preset.ts' -import { SuiProvider } from '../provider/sui-provider.tsx' -import { ErrorBoundary } from './' +import { ErrorBoundary, ErrorProvider } from './' export default { title: 'Utilities/ErrorBoundary', @@ -13,12 +10,9 @@ export default { decorators: [ (Story) => { return ( - console.log('ERROR', err)} - > + console.log('ERROR', err)}> - + ) }, ], @@ -41,15 +35,10 @@ export const CustomFallback = () => { return ( - Whoops, this was not expected - - Something terribly went wrong, but it's not your fault. - - - - - +
+

Whoops, this was unexpected

+

Something terribly went wrong, but it's not your fault.

+
} > diff --git a/packages/saas-ui-core/src/error-boundary/error-provider.tsx b/packages/saas-ui-core/src/error-boundary/error-provider.tsx new file mode 100644 index 000000000..c2b46da7a --- /dev/null +++ b/packages/saas-ui-core/src/error-boundary/error-provider.tsx @@ -0,0 +1,22 @@ +import React from 'react' + +export interface ErrorProviderProps { + children: React.ReactNode + onError?: (error: Error, errorInfo: React.ErrorInfo) => void +} + +export interface ErrorContextValue { + onError?: (error: Error, errorInfo: React.ErrorInfo) => void +} + +const context = React.createContext({}) + +export const ErrorProvider = (props: ErrorProviderProps) => { + const { children, onError } = props + + const value = React.useMemo(() => ({ onError }), [onError]) + + return {children} +} + +export const useErrorContext = () => React.useContext(context) diff --git a/packages/saas-ui-core/src/error-boundary/index.ts b/packages/saas-ui-core/src/error-boundary/index.ts index 2f0883e43..891e69dba 100644 --- a/packages/saas-ui-core/src/error-boundary/index.ts +++ b/packages/saas-ui-core/src/error-boundary/index.ts @@ -1,2 +1,4 @@ export { ErrorBoundary } from './error-boundary' export type { ErrorBoundaryProps, ErrorBoundaryState } from './error-boundary' +export { ErrorProvider, useErrorContext } from './error-provider' +export type { ErrorProviderProps } from './error-provider' diff --git a/packages/saas-ui-data-table/package.json b/packages/saas-ui-data-table/package.json index 9d8852da3..73f2ac0d2 100644 --- a/packages/saas-ui-data-table/package.json +++ b/packages/saas-ui-data-table/package.json @@ -31,12 +31,12 @@ "publishConfig": { "access": "public" }, - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "homepage": "https://saas-ui.dev/", "repository": { "type": "git", - "url": "https://github.com/saas-js/saas-ui", + "url": "git+https://github.com/saas-js/saas-ui.git", "directory": "packages/saas-ui-data-table" }, "keywords": [ diff --git a/packages/saas-ui-date-picker/package.json b/packages/saas-ui-date-picker/package.json index 802851984..948b7245d 100644 --- a/packages/saas-ui-date-picker/package.json +++ b/packages/saas-ui-date-picker/package.json @@ -31,12 +31,12 @@ "publishConfig": { "access": "public" }, - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "homepage": "https://saas-ui.dev/", "repository": { "type": "git", - "url": "https://github.com/saas-js/saas-ui", + "url": "git+https://github.com/saas-js/saas-ui.git", "directory": "packages/saas-ui-date-picker" }, "keywords": [ diff --git a/packages/saas-ui-file-upload/package.json b/packages/saas-ui-file-upload/package.json index 8d05e876a..65fa8b016 100644 --- a/packages/saas-ui-file-upload/package.json +++ b/packages/saas-ui-file-upload/package.json @@ -31,12 +31,12 @@ "publishConfig": { "access": "public" }, - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "homepage": "https://saas-ui.dev/", "repository": { "type": "git", - "url": "https://github.com/saas-js/saas-ui", + "url": "git+https://github.com/saas-js/saas-ui.git", "directory": "packages/saas-ui-dropzone" }, "keywords": [ diff --git a/packages/saas-ui-forms/ajv/package.json b/packages/saas-ui-forms/ajv/package.json index 896dcb251..5649910fb 100644 --- a/packages/saas-ui-forms/ajv/package.json +++ b/packages/saas-ui-forms/ajv/package.json @@ -16,7 +16,7 @@ "main": "../dist/ajv/index.js", "module": "../dist/ajv/index.mjs", "types": "../dist/ajv/index.d.ts", - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "peerDependencies": { "@chakra-ui/utils": "^2.0.14", diff --git a/packages/saas-ui-forms/package.json b/packages/saas-ui-forms/package.json index 8b3aa90df..02abfeaa8 100644 --- a/packages/saas-ui-forms/package.json +++ b/packages/saas-ui-forms/package.json @@ -73,12 +73,12 @@ "publishConfig": { "access": "public" }, - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "homepage": "https://saas-ui.dev/", "repository": { "type": "git", - "url": "https://github.com/saas-js/saas-ui", + "url": "git+https://github.com/saas-js/saas-ui.git", "directory": "packages/saas-ui-forms" }, "keywords": [ diff --git a/packages/saas-ui-forms/yup/package.json b/packages/saas-ui-forms/yup/package.json index 8b7748893..9d6c80573 100644 --- a/packages/saas-ui-forms/yup/package.json +++ b/packages/saas-ui-forms/yup/package.json @@ -16,7 +16,7 @@ "main": "../dist/yup/index.js", "module": "../dist/yup/index.mjs", "types": "../dist/yup/index.d.ts", - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "peerDependencies": { "@hookform/resolvers": "^3.0.0", diff --git a/packages/saas-ui-forms/zod/package.json b/packages/saas-ui-forms/zod/package.json index 4b85831e6..67dca0db4 100644 --- a/packages/saas-ui-forms/zod/package.json +++ b/packages/saas-ui-forms/zod/package.json @@ -16,7 +16,7 @@ "main": "../dist/zod/index.js", "module": "../dist/zod/index.mjs", "types": "../dist/zod/index.d.ts", - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "peerDependencies": { "@saas-ui/modals": "*", diff --git a/packages/saas-ui-hooks/package.json b/packages/saas-ui-hooks/package.json index 7dda257f4..b049e7cf2 100644 --- a/packages/saas-ui-hooks/package.json +++ b/packages/saas-ui-hooks/package.json @@ -31,12 +31,12 @@ "publishConfig": { "access": "public" }, - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "homepage": "https://saas-ui.dev/", "repository": { "type": "git", - "url": "https://github.com/saas-js/saas-ui", + "url": "git+https://github.com/saas-js/saas-ui.git", "directory": "packages/saas-ui-system" }, "keywords": [ diff --git a/packages/saas-ui-hotkeys/package.json b/packages/saas-ui-hotkeys/package.json index d591e50b0..5959cc6c1 100644 --- a/packages/saas-ui-hotkeys/package.json +++ b/packages/saas-ui-hotkeys/package.json @@ -31,12 +31,12 @@ "publishConfig": { "access": "public" }, - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "homepage": "https://saas-ui.dev/", "repository": { "type": "git", - "url": "https://github.com/saas-js/saas-ui", + "url": "git+https://github.com/saas-js/saas-ui.git", "directory": "packages/hotkeys" }, "keywords": [ diff --git a/packages/saas-ui-modals-provider/README.md b/packages/saas-ui-modals-provider/README.md new file mode 100644 index 000000000..427e720d8 --- /dev/null +++ b/packages/saas-ui-modals-provider/README.md @@ -0,0 +1,76 @@ +# @saas-ui/modals-provider + +UI library agnostic React modals manager. + +## Installation + +```sh +$ yarn add @saas-ui/modals-provider + +#or + +$ npm i @saas-ui/modals-provider --save +``` + +## Usage + +### Add the ModalProvider to your app. + +```ts +export const {ModalsProvider, useModals} = createModals({ + modals: { + alert: AlertDialog, + confirm: ConfirmDialog, + } +}) + +export default App() { + return ( + + {children} + + ) +} +``` + +### Open a modal + +```ts +import { useModals } from './modals-provider' + +function MyComponent() { + const modals = useModals() + + modals.open(MyModal, { + title: 'My modal', + }) +} +``` + +### Open a confirm dialog + +```ts +import { useModals } from './modals-provider' + +function MyComponent() { + const modals = useModals() + + modals.confirm({ + title: 'Delete user', + body: 'Are you sure you want to delete this user?' + onConfirm: () => //delete user + }) +} +``` + +## Docs + +https://www.saas-ui.dev/docs/overlay/modals-manager + +## Source + +https://github.com/saas-js/saas-ui/tree/next/packages/saas-ui-modals-provider + +## License + +MIT - Appulse Software diff --git a/packages/saas-ui-modals-provider/index.ts b/packages/saas-ui-modals-provider/index.ts new file mode 100644 index 000000000..6f39cd49b --- /dev/null +++ b/packages/saas-ui-modals-provider/index.ts @@ -0,0 +1 @@ +export * from './src' diff --git a/packages/saas-ui-modals-provider/package.json b/packages/saas-ui-modals-provider/package.json new file mode 100644 index 000000000..0b587bd39 --- /dev/null +++ b/packages/saas-ui-modals-provider/package.json @@ -0,0 +1,60 @@ +{ + "name": "@saas-ui/modals-provider", + "version": "0.0.1", + "description": "A UI library agnostic modal manager for React", + "source": "src/index.ts", + "exports": { + ".": { + "sui": "./src/index.ts", + "types": "./dist/index.d.ts", + "require": "./dist/index.js", + "import": "./dist/index.mjs" + } + }, + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "scripts": { + "clean": "rimraf dist", + "build": "tsup --config tsup.config.ts", + "lint": "eslint src --ext .ts,.tsx,.js,.jsx --config ../../.eslintrc.js", + "lint:staged": "lint-staged --allow-empty --config ../../lint-staged.config.js", + "typecheck": "tsc --noEmit" + }, + "files": [ + "dist", + "src" + ], + "sideEffects": false, + "publishConfig": { + "access": "public" + }, + "author": "Eelco Wiersma ", + "license": "MIT", + "homepage": "https://saas-ui.dev/", + "repository": { + "type": "git", + "url": "git+https://github.com/saas-js/saas-ui.git", + "directory": "packages/saas-ui-modals-provider" + }, + "keywords": [ + "react", + "ui", + "design-system", + "react-components", + "uikit", + "accessible", + "components", + "library", + "modals", + "dialogs" + ], + "storybook": { + "title": "Saas UI", + "url": "https://storybook.saas-ui.dev" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } +} diff --git a/packages/saas-ui-modals/src/create-modals.tsx b/packages/saas-ui-modals-provider/src/create-modals.tsx similarity index 71% rename from packages/saas-ui-modals/src/create-modals.tsx rename to packages/saas-ui-modals-provider/src/create-modals.tsx index de9572109..45cb0e050 100644 --- a/packages/saas-ui-modals/src/create-modals.tsx +++ b/packages/saas-ui-modals-provider/src/create-modals.tsx @@ -1,4 +1,3 @@ -import { defaultModals } from './default-modals' import { ModalsContextValue, ModalsProvider, @@ -7,18 +6,15 @@ import { } from './provider' export interface CreateModalsOptions< - TModalDefs extends Record> + TModalDefs extends Record>, > { modals: TModalDefs } export const createModals = >>( - options: CreateModalsOptions + options: CreateModalsOptions, ) => { - const modals = { - ...defaultModals, - ...options.modals, - } + const modals = options.modals const Provider = (props: Omit) => { return } diff --git a/packages/saas-ui-modals-provider/src/index.ts b/packages/saas-ui-modals-provider/src/index.ts new file mode 100644 index 000000000..cabf0d9b8 --- /dev/null +++ b/packages/saas-ui-modals-provider/src/index.ts @@ -0,0 +1,20 @@ +export { + ModalsContext, + ModalsProvider, + useModals, + useModalsContext, +} from './provider' +export type { + ConfirmDialogOptions, + AlertDialogOptions, + ModalOptions, + ModalConfig, + ModalId, + ModalScopes, + ModalsContextValue, + ModalsProviderProps, + OpenOptions, +} from './provider' + +export { createModals } from './create-modals' +export type { CreateModalsOptions } from './create-modals' diff --git a/packages/saas-ui-modals/src/provider.tsx b/packages/saas-ui-modals-provider/src/provider.tsx similarity index 80% rename from packages/saas-ui-modals/src/provider.tsx rename to packages/saas-ui-modals-provider/src/provider.tsx index 799b000d1..011713b71 100644 --- a/packages/saas-ui-modals/src/provider.tsx +++ b/packages/saas-ui-modals-provider/src/provider.tsx @@ -1,12 +1,10 @@ import * as React from 'react' -import { defaultModals } from './default-modals' -import { ConfirmDialogProps } from './dialog' -import { DrawerProps } from './drawer' -import { BaseModalProps } from './modal' - export interface ModalsContextValue< - TModals extends Record> = Record>, + TModals extends Record> = Record< + string, + React.ComponentType + >, TTypes extends Extract = Extract< keyof TModals, string @@ -14,7 +12,7 @@ export interface ModalsContextValue< > { open: >( componentOrOptions: T extends { - component: infer TComponent extends React.FC + component: infer TComponent extends React.ComponentType } ? WithModalOptions> : T extends { @@ -26,47 +24,52 @@ export interface ModalsContextValue< ? WithModalOptions> : never, ) => ModalId - drawer: (options: DrawerOptions) => ModalId - alert: (options: ConfirmDialogOptions) => ModalId - confirm: (options: ConfirmDialogOptions) => ModalId + alert: (options: T) => ModalId + confirm: (options: T) => ModalId close: (id: ModalId) => void closeAll: () => void } export const ModalsContext = React.createContext> > | null>(null) export interface ModalsProviderProps< - TModals extends Record> = Record>, + TModals extends Record> = Record< + string, + React.FC + >, > { children: React.ReactNode modals?: TModals + render?: (props: ModalsContextValue) => React.ReactNode } export type ModalId = string | number type WithModalOptions = Omit & ModalOptions -interface ModalOptions - extends Omit< - BaseModalProps, - 'open' | 'onOpenChange' | 'placement' | 'children' - > { +export interface ModalOptions { + title?: string + children?: React.ReactNode + open?: boolean + onOpenChange?: (details: { open: boolean }) => void onClose?: (args: { force?: boolean }) => Promise | void [key: string]: any } -export interface DrawerOptions - extends Omit, - Omit< - DrawerProps, - 'open' | 'onOpenChange' | 'children' | 'title' | 'size' - > {} +export interface AlertDialogOptions extends ModalOptions { + onConfirm?: () => Promise | void + confirmProps?: React.HTMLProps +} -export interface ConfirmDialogOptions - extends ModalOptions, - Omit {} +export interface ConfirmDialogOptions extends ModalOptions { + leastDestructiveFocus?: 'cancel' | 'confirm' + onConfirm?: () => Promise | void + onCancel?: () => Promise | void + confirmProps?: React.HTMLProps + cancelProps?: React.HTMLProps +} export interface OpenOptions extends ModalOptions { type?: TModalTypes @@ -105,7 +108,7 @@ export interface ModalConfig< * Render a custom modal component. * This will ignore the `type` param. */ - component?: React.FC + component?: React.ComponentType /** * Whether the modal is open or not. * This is used internally to keep track of the modal state. @@ -131,10 +134,7 @@ export function ModalsProvider({ children, modals }: ModalsProviderProps) { }) const getModalComponent = React.useMemo(() => { - const _modals: Record> = { - ...defaultModals, - ...modals, - } + const _modals: Record> = modals || {} return (type = 'modal') => { const component = _modals[type] || _modals.modal @@ -200,29 +200,12 @@ export function ModalsProvider({ children, modals }: ModalsProviderProps) { [_instances], ) - const drawer = React.useCallback( - (options: DrawerOptions) => { - return open({ - ...options, - type: 'drawer', - }) - }, - [open], - ) - const alert = React.useCallback( (options: ConfirmDialogOptions) => { return open({ ...options, - scope: 'alert', type: 'alert', - cancelProps: { - display: 'none', - }, - confirmProps: { - label: 'OK', - }, - leastDestructiveFocus: 'confirm', + scope: 'alert', }) }, [open], @@ -230,10 +213,10 @@ export function ModalsProvider({ children, modals }: ModalsProviderProps) { const confirm = React.useCallback( (options: ConfirmDialogOptions) => { - return open({ + return open({ ...options, - scope: 'alert', type: 'confirm', + scope: 'alert', }) }, [open], @@ -305,13 +288,12 @@ export function ModalsProvider({ children, modals }: ModalsProviderProps) { const context = React.useMemo( () => ({ open, - drawer, alert, confirm, close, closeAll, }), - [open, drawer, alert, confirm, close, closeAll], + [open, alert, confirm, close, closeAll], ) const content = React.useMemo( @@ -319,13 +301,13 @@ export function ModalsProvider({ children, modals }: ModalsProviderProps) { Object.entries(activeModals).map(([scope, config]) => { const Component = config.component || getModalComponent(config.type) - const { title, body, children, ...props } = config.props || {} + const { title, children, ...props } = config.props || {} return ( @@ -335,7 +317,7 @@ export function ModalsProvider({ children, modals }: ModalsProviderProps) { /> ) }), - [activeModals], + [activeModals, getModalComponent], ) return ( diff --git a/packages/saas-ui-modals-provider/src/types.ts b/packages/saas-ui-modals-provider/src/types.ts new file mode 100644 index 000000000..d38729a71 --- /dev/null +++ b/packages/saas-ui-modals-provider/src/types.ts @@ -0,0 +1,83 @@ +import { FormProps, FormType } from '@saas-ui/forms' +import { ModalId } from './provider' +import { AnyObjectSchema, YupFormType } from '@saas-ui/forms/yup' +import { FormDialogFieldOverrides } from './form' +import { WithFields } from '@saas-ui/forms' +import { FieldValues } from '@saas-ui/forms' +import type { z } from 'zod' +import type { InferType } from 'yup' +import { ZodFormType } from '@saas-ui/forms/zod' + +export type FormDialogHandler = T extends YupFormType< + infer FieldDefs, + infer ExtraProps, + infer ExtraOverrides, + 'yup' +> + ? YupHandler + : T extends ZodFormType< + infer FieldDefs, + infer ExtraProps, + infer ExtraOverrides, + 'zod' + > + ? ZodHandler + : T extends FormType + ? FormHandler + : never + +export type ZodHandler< + FieldDefs, + ExtraProps = object, + ExtraOverrides = object, + Type extends 'zod' = 'zod' +> = < + TSchema extends z.AnyZodObject = z.AnyZodObject, + TFieldValues extends z.infer = z.infer, + TContext extends object = object +>( + props: WithFields< + FormProps, + FieldDefs, + ExtraOverrides + > & { + ref?: React.ForwardedRef + } & ExtraProps +) => ModalId + +export type FormHandler< + FieldDefs, + ExtraProps = object, + ExtraOverrides = object +> = < + TSchema = unknown, + TFieldValues extends FieldValues = FieldValues, + TContext extends object = object +>( + props: WithFields< + FormProps, + FieldDefs, + ExtraOverrides + > & { + ref?: React.ForwardedRef + } & ExtraProps +) => ModalId + +export type YupHandler< + FieldDefs, + ExtraProps = object, + ExtraOverrides = object, + Type extends 'yup' = 'yup' +> = < + TSchema extends AnyObjectSchema = AnyObjectSchema, + TFieldValues extends InferType = InferType, // placeholder + TContext extends object = object +>( + props: WithFields< + FormProps, + FieldDefs, + ExtraOverrides + > & { + ref?: React.ForwardedRef + } & ExtraProps +) => ModalId diff --git a/packages/saas-ui-modals-provider/stories/form.bac.tsx b/packages/saas-ui-modals-provider/stories/form.bac.tsx new file mode 100644 index 000000000..eed711ae7 --- /dev/null +++ b/packages/saas-ui-modals-provider/stories/form.bac.tsx @@ -0,0 +1,357 @@ +import * as React from 'react' +import { + Stack, + Button, + Container, + useDisclosure, + ModalFooter, +} from '@chakra-ui/react' + +import { Form, FormLayout, SubmitButton, createField } from '@saas-ui/forms' +import { createZodForm, Form as DefaultZodForm } from '@saas-ui/forms/zod' +import { createYupForm, Form as DefaultYupForm } from '@saas-ui/forms/yup' + +import { FormDialog } from '../src/form' + +import { createZodFormDialog } from '../src/zod' + +import { createYupFormDialog } from '../src/yup' + +import * as yup from 'yup' +import * as zod from 'zod' + +const CustomField = createField((props: { customFieldProps: string }) => ( +
custom
+)) + +const ZodForm = createZodForm({ + fields: { + custom: CustomField, + }, +}) + +const ZodFormDialog = createZodFormDialog(ZodForm) + +const YupForm = createYupForm({ + fields: { + custom: CustomField, + }, +}) + +const YupFormDialog = createYupFormDialog(YupForm) + +export default { + title: 'Components/Overlay/FormDialog', + decorators: [ + (Story: any) => ( + + + + ), + ], +} + +const onSubmit = ({ onClose }: any) => { + return (data: any) => { + return new Promise((resolve) => { + console.log(data) + setTimeout(() => { + onClose() + resolve(true) + }, 2000) + }) + } +} + +export const Basic = () => { + const disclosure = useDisclosure() + + return ( + + + + + {({ Field }) => ( + + + + + )} + + + ) +} + +export const FocusFirstInput = () => { + const disclosure = useDisclosure() + + const initialRef = React.useRef(null) + + return ( + + + + + {({ Field }) => ( + + + + + )} + + + ) +} + +export const CustomFooter = () => { + const disclosure = useDisclosure() + + const initialRef = React.useRef(null) + + const footer = ( + + Save post + + ) + + return ( + + + + + {({ Field }) => ( + + + + + )} + + + ) +} + +const zodSchema = zod.object({ + title: zod.string().nonempty('Title is required'), +}) + +export const ZodSchema = () => { + const disclosure = useDisclosure() + + const initialRef = React.useRef(null) + + return ( + + + + { + return onSubmit(disclosure)({ title }) + }} + initialFocusRef={initialRef} + /> + + ) +} + +export const ZodSchemaWithFields = () => { + const disclosure = useDisclosure() + + const initialRef = React.useRef(null) + + return ( + + + + { + return onSubmit(disclosure)({ title }) + }} + initialFocusRef={initialRef} + > + {({ Field }) => ( + + + + )} + + + ) +} + +const yupSchema = yup.object({ + title: yup.string().required('Title is required'), + description: yup.string(), +}) + +export const YupSchema = () => { + const disclosure = useDisclosure() + + const initialRef = React.useRef(null) + + return ( + + + + { + return onSubmit(disclosure)({ title }) + }} + initialFocusRef={initialRef} + /> + + ) +} + +export const YupSchemaWithFields = () => { + const disclosure = useDisclosure() + + const initialRef = React.useRef(null) + + return ( + + + + { + return onSubmit(disclosure)({ title }) + }} + initialFocusRef={initialRef} + > + {({ Field }) => ( + + + + )} + + + ) +} diff --git a/packages/saas-ui-modals-provider/stories/modals.stories.tsx b/packages/saas-ui-modals-provider/stories/modals.stories.tsx new file mode 100644 index 000000000..0bc3fe761 --- /dev/null +++ b/packages/saas-ui-modals-provider/stories/modals.stories.tsx @@ -0,0 +1,272 @@ +import * as React from 'react' + +import { Button, Container, Stack } from '@chakra-ui/react' + +import { createModals } from '../src' +import { BaseModalProps, Modal } from '../src/modal' + +interface CustomModalProps extends Omit { + customProp: 'test' + children?: React.ReactNode +} + +const CustomModal: React.FC = ({ + title = 'Custom modal', + footer = 'Custom footer', + children = 'Modal body', + ...props +}) => ( + + {children} + +) + +const { ModalsProvider: ModalsProvider, useModals } = createModals({ + modals: { + custom: CustomModal, + }, +}) + +export default { + title: 'Modals/Modals Manager', + decorators: [ + (Story: any) => ( + + + + + + ), + ], +} + +export const Basic = () => { + const modals = useModals() + + return ( + + , + }) + }} + > + Open modal + + , + }) + }} + > + Open xl modal + + + + + + + ), + }) + } + > + Open drawer + + + ), + }) + } + > + Open drawer + + + + ) +} + +export const Custom = () => { + const modals = useModals() + + return ( + + ) +} + +export const CustomAsComponent = () => { + const modals = useModals() + + return ( + + ) +} + +export const OnClose = () => { + const modals = useModals() + + return ( + + ) +} + +export const Multiple = () => { + const modals = useModals() + + const next = () => { + const id = modals.open({ + title: 'Modal step 2', + body: 'Step 2', + footer: ( + <> + + + + ), + }) + } + + return ( + + + ), + }) + } + > + Open modal + + ) +} + +export const AsyncConfirmDialog = () => { + const modals = useModals() + + return ( + + ) +} diff --git a/packages/saas-ui-modals-provider/tests/use-modals.test.tsx b/packages/saas-ui-modals-provider/tests/use-modals.test.tsx new file mode 100644 index 000000000..4417bedfe --- /dev/null +++ b/packages/saas-ui-modals-provider/tests/use-modals.test.tsx @@ -0,0 +1,224 @@ +import * as React from 'react' + +import { Field, FormLayout } from '@saas-ui/forms' +import { render, waitFor } from '@saas-ui/test-utils' +import { vi } from 'vitest' + +import { ModalsProvider, useModals } from '../src' + +const renderModal = (ui: React.ReactNode) => { + return render({ui}) +} + +test('should open a basic modal', async () => { + const title = 'Modal title' + + const TestComponent = () => { + const modals = useModals() + return + } + + const { findByText, findByRole, user } = renderModal() + + const button = await findByText('Open') + await user.click(button) + + const dialog = await findByRole('dialog') + + waitFor(() => expect(dialog).toBeInTheDocument()) +}) + +test('should close all modals', async () => { + const title = 'Modal title' + + const TestComponent = () => { + const modals = useModals() + return ( + <> + + + + ) + } + + const { findByText, findByRole, user } = renderModal() + + const button = await findByText('Modal') + await user.click(button) + + const dialog = await findByRole('dialog') + + await waitFor(() => expect(dialog).toBeInTheDocument()) + + const close = await findByText('Close all') + await user.click(close) + + await waitFor(() => expect(dialog).not.toBeVisible()) +}) + +test('should open a drawer', async () => { + const title = 'Drawer title' + + const TestComponent = () => { + const modals = useModals() + return + } + + const { findByText, findByRole, user } = renderModal() + + const button = await findByText('Open') + await user.click(button) + + const dialog = await findByRole('dialog') + + await waitFor(() => expect(dialog).toBeInTheDocument()) +}) + +test('should open an alert', async () => { + const title = 'Alert' + + const TestComponent = () => { + const modals = useModals() + return + } + + const { findByText, findByRole, user } = renderModal() + + const button = await findByText('Open') + await user.click(button) + + const dialog = await findByRole('alertdialog') + + await waitFor(() => expect(dialog).toBeInTheDocument()) +}) + +test('should open an confirm dialog', async () => { + const title = 'Alert' + + const TestComponent = () => { + const modals = useModals() + return + } + + const { findByText, findByRole, user } = renderModal() + + const button = await findByText('Alert') + await user.click(button) + + const dialog = await findByRole('alertdialog') + + expect(dialog).toBeInTheDocument() +}) + +test('should have confirm and cancel buttons', async () => { + const title = 'Alert' + + const onConfirm = vi.fn() + + const TestComponent = () => { + const modals = useModals() + return ( + + ) + } + + const { findByText, findByRole, user } = renderModal() + + const button = await findByText('Open') + await user.click(button) + + const dialog = await findByRole('alertdialog') + + const cancel = await findByText('Cancel') + + const confirm = await findByText('Confirm') + + expect(cancel).toBeInTheDocument() + expect(confirm).toBeInTheDocument() + + await user.click(confirm) + + expect(onConfirm).toBeCalled() +}) + +// test('should open a menu dialog', async () => { +// const title = 'Menu' + +// const TestComponent = () => { +// const modals = useModals() +// return ( +// +// ) +// } + +// const { findByText, findByRole, findAllByText, user } = renderModal( +// +// ) + +// const button = await findByText('Menu') +// await user.click(button) + +// const menu = await findByRole('dialog') + +// expect(menu).toBeVisible() + +// const items = await findAllByText('Item') + +// expect(items).toHaveLength(2) +// }) + +test('should open a form dialog', async () => { + const title = 'Form' + + const onSubmit = vi.fn() + + const TestComponent = () => { + const modals = useModals() + return ( + + ) + } + + const { findByText, findByRole, findByLabelText, user } = renderModal( + , + ) + + const button = await findByText('Open') + await user.click(button) + + const form = await findByRole('dialog') + + await waitFor(() => expect(form).toBeInTheDocument()) + + const field = await findByLabelText('Title') + + expect(field).toBeInTheDocument() +}) diff --git a/packages/saas-ui-modals-provider/tsconfig.json b/packages/saas-ui-modals-provider/tsconfig.json new file mode 100644 index 000000000..0c3ec4619 --- /dev/null +++ b/packages/saas-ui-modals-provider/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "." + }, + "include": ["src"], + "exclude": [ + "src/tests", + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.test.ts", + "**/*.test.tsx", + "**/*.stories.tsx" + ] +} diff --git a/packages/saas-ui-modals-provider/tsup.config.ts b/packages/saas-ui-modals-provider/tsup.config.ts new file mode 100644 index 000000000..1850e16cc --- /dev/null +++ b/packages/saas-ui-modals-provider/tsup.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'tsup' +import config from '../../tsup.config' + +export default defineConfig({ + ...config, + entry: ['src/index.ts', 'src/zod/index.ts', 'src/yup/index.ts'], +}) diff --git a/packages/saas-ui-modals/package.json b/packages/saas-ui-modals/package.json index d98a71e04..8688dfbd1 100644 --- a/packages/saas-ui-modals/package.json +++ b/packages/saas-ui-modals/package.json @@ -5,28 +5,22 @@ "source": "src/index.ts", "exports": { ".": { + "sui": "./src/index.ts", "types": "./dist/index.d.ts", "require": "./dist/index.js", "import": "./dist/index.mjs" }, "./zod": { + "sui": "./src/zod/index.ts", "types": "./dist/zod/index.d.ts", "require": "./dist/zod/index.js", "import": "./dist/zod/index.mjs" }, - "./zod/src": { - "default": "./src/zod/index.ts" - }, "./yup": { + "sui": "./src/yup/index.ts", "types": "./dist/yup/index.d.ts", "require": "./dist/yup/index.js", "import": "./dist/yup/index.mjs" - }, - "./yup/src": { - "default": "./src/yup/index.ts" - }, - "./src": { - "default": "./src/index.ts" } }, "typesVersions": { @@ -59,7 +53,7 @@ "publishConfig": { "access": "public" }, - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "homepage": "https://saas-ui.dev/", "repository": { @@ -87,7 +81,8 @@ "dependencies": { "@saas-ui/core": "workspace:*", "@saas-ui/forms": "workspace:*", - "@saas-ui/hooks": "workspace:*" + "@saas-ui/hooks": "workspace:*", + "@saas-ui/modals-provider": "workspace:*" }, "peerDependencies": { "@chakra-ui/react": "^3.0.0", diff --git a/packages/saas-ui-modals/src/dialog.tsx b/packages/saas-ui-modals/src/alert-dialog.tsx similarity index 52% rename from packages/saas-ui-modals/src/dialog.tsx rename to packages/saas-ui-modals/src/alert-dialog.tsx index d2fe738f8..cf41d7aa4 100644 --- a/packages/saas-ui-modals/src/dialog.tsx +++ b/packages/saas-ui-modals/src/alert-dialog.tsx @@ -1,8 +1,10 @@ import * as React from 'react' -import { Button, ButtonProps, Dialog } from '@chakra-ui/react' +import { Button, ButtonProps, type HTMLChakraProps } from '@chakra-ui/react' +import { callAll } from '@saas-ui/core/utils' +import { Dialog } from '@saas-ui/react/dialog' -export interface ConfirmDialogProps +export interface AlertDialogProps extends Omit< Dialog.RootProps, 'leastDestructiveRef' | 'onOpenChange' | 'open' @@ -14,31 +16,21 @@ export interface ConfirmDialogProps /** * Callback when the dialog is opened or closed */ - onOpenChange: (details: Dialog.OpenChangeDetails) => void + onOpenChange: (details: { open: boolean }) => void /** * The dialog title */ title?: React.ReactNode - /** - * The cancel button label - */ - cancelLabel?: React.ReactNode - /** - * The confirm button label - */ - confirmLabel?: React.ReactNode - /** - * The cancel button props - */ - cancelProps?: ButtonProps - /** - * The confirm button props - */ - confirmProps?: ButtonProps - /** - * The button group props - */ - footerProps?: Dialog.FooterProps + translations?: { + cancel?: React.ReactNode + confirm?: React.ReactNode + close?: React.ReactNode + } + slotProps?: { + cancel?: ButtonProps + confirm?: ButtonProps + footer?: HTMLChakraProps<'div'> + } /** * Close the dialog on cancel * @default true @@ -52,7 +44,7 @@ export interface ConfirmDialogProps /** * Hide the backdrop */ - hideBackdrop?: boolean + backdrop?: boolean /** * Hide the close button */ @@ -72,19 +64,14 @@ export interface ConfirmDialogProps onConfirm?: () => Promise | void } -export const ConfirmDialog: React.FC = (props) => { +export const AlertDialog: React.FC = (props) => { const { title, - cancelLabel = 'Cancel', - confirmLabel = 'Confirm', - cancelProps, - confirmProps, - footerProps, + translations, + slotProps, open, closeOnCancel = true, closeOnConfirm = true, - hideBackdrop = false, - hideCloseButton = false, leastDestructiveFocus = 'cancel', onOpenChange, onCancel, @@ -115,11 +102,16 @@ export const ConfirmDialog: React.FC = (props) => { /* eslint-enable */ } + const titleId = React.useId() + const contentId = React.useId() + return ( leastDestructiveFocus === 'cancel' @@ -127,36 +119,38 @@ export const ConfirmDialog: React.FC = (props) => { : confirmRef.current } > - {!hideBackdrop && } - - - {title} + + + {title} + - {children} + {children} - - - - - - + closeOnCancel && onOpenChange({ open: false }) + })} + > + {slotProps?.cancel?.children || translations?.cancel || 'Cancel'} + + + + ) } diff --git a/packages/saas-ui-modals/src/default-modals.ts b/packages/saas-ui-modals/src/default-modals.ts index fbfdc968e..a7265ab4b 100644 --- a/packages/saas-ui-modals/src/default-modals.ts +++ b/packages/saas-ui-modals/src/default-modals.ts @@ -1,10 +1,10 @@ -import { ConfirmDialog } from './dialog' +import { AlertDialog } from './alert-dialog' import { Drawer } from './drawer' import { Modal } from './modal' export const defaultModals = { - alert: ConfirmDialog, - confirm: ConfirmDialog, + alert: AlertDialog, + confirm: AlertDialog, drawer: Drawer, modal: Modal, } diff --git a/packages/saas-ui-modals/src/drawer.tsx b/packages/saas-ui-modals/src/drawer.tsx index 0f052b79e..2d7073a24 100644 --- a/packages/saas-ui-modals/src/drawer.tsx +++ b/packages/saas-ui-modals/src/drawer.tsx @@ -1,10 +1,10 @@ import * as React from 'react' -import { Drawer as ChakraDrawer, IconButton } from '@chakra-ui/react' +import { type HTMLChakraProps } from '@chakra-ui/react' import { MaybeRenderProp, runIfFn } from '@saas-ui/core/utils' +import { Drawer as BaseDrawer } from '@saas-ui/react/drawer' -export interface BaseDrawerProps - extends Omit { +export interface DrawerProps extends Omit { /** * The drawer title */ @@ -16,7 +16,7 @@ export interface BaseDrawerProps /** * Callback when the drawer is opened or closed */ - onOpenChange: (details: ChakraDrawer.OpenChangeDetails) => void + onOpenChange: (details: { open: boolean }) => void /** * The drawer children */ @@ -39,18 +39,22 @@ export interface BaseDrawerProps /** * Props for the modal header */ - headerProps?: ChakraDrawer.HeaderProps + headerProps?: HTMLChakraProps<'div'> /** * Props for the modal content */ - contentProps?: ChakraDrawer.ContentProps + contentProps?: BaseDrawer.ContentProps /** * Props for the modal footer */ - footerProps?: ChakraDrawer.FooterProps + footerProps?: HTMLChakraProps<'div'> + /** + * Props for the modal body + */ + bodyProps?: HTMLChakraProps<'div'> } -export const BaseDrawer: React.FC = (props) => { +export const Drawer: React.FC = (props) => { const { title, children, @@ -61,50 +65,27 @@ export const BaseDrawer: React.FC = (props) => { hideBackdrop, headerProps, contentProps, + bodyProps, footerProps, ...rest } = props return ( - - {!hideBackdrop && } - - - {title && ( - {title} - )} - {!hideCloseButton && ( - - - - )} - + + {!hideBackdrop && } + + {title && ( + {title} + )} + {!hideCloseButton && } + + {({ open, setOpen }) => runIfFn(children, { open, setOpen })} - - {footer && ( - {footer} - )} - - - - ) -} - -export interface DrawerProps extends BaseDrawerProps { - /** - * Drawer footer content, wrapped with `DrawerFooter` - */ - footer?: React.ReactNode -} - -export const Drawer: React.FC = (props) => { - const { children, ...rest } = props - return ( - - - - {({ open, setOpen }) => runIfFn(children, { open, setOpen })} - - - + + + {footer && ( + {footer} + )} + + ) } diff --git a/packages/saas-ui-modals/src/index.ts b/packages/saas-ui-modals/src/index.ts index f0d07c15c..7e17a99d9 100644 --- a/packages/saas-ui-modals/src/index.ts +++ b/packages/saas-ui-modals/src/index.ts @@ -1,14 +1,18 @@ +import { createModals } from '@saas-ui/modals-provider' + +import { defaultModals } from './default-modals.ts' + // Exporting from './dialog' -export { ConfirmDialog } from './dialog' -export type { ConfirmDialogProps } from './dialog' +export { AlertDialog } from './alert-dialog.tsx' +export type { AlertDialogProps } from './alert-dialog.tsx' // Exporting from './drawer' -export { BaseDrawer, Drawer } from './drawer' -export type { BaseDrawerProps, DrawerProps } from './drawer' +export { Drawer } from './drawer' +export type { DrawerProps } from './drawer' // Exporting from './modal' -export { BaseModal, Modal } from './modal' -export type { BaseModalProps } from './modal' +export { Modal } from './modal' +export type { ModalProps } from './modal' // Exporting from './form' // export { FormDialog, createFormDialog } from './form' @@ -19,23 +23,9 @@ export type { BaseModalProps } from './modal' // } from './form' // Exporting from './provider' -export { - ModalsContext, - ModalsProvider, - useModals, - useModalsContext, -} from './provider' -export type { - ConfirmDialogOptions, - DrawerOptions, - ModalConfig, - ModalId, - ModalScopes, - ModalsContextValue, - ModalsProviderProps, - OpenOptions, -} from './provider' - -// Exporting from './create-modals' -export { createModals } from './create-modals' -export type { CreateModalsOptions } from './create-modals' + +const { useModals, ModalsProvider } = createModals({ + modals: defaultModals, +}) + +export { ModalsProvider, useModals } diff --git a/packages/saas-ui-modals/src/modal.tsx b/packages/saas-ui-modals/src/modal.tsx index d8ad1ba1e..8cce3bdf3 100644 --- a/packages/saas-ui-modals/src/modal.tsx +++ b/packages/saas-ui-modals/src/modal.tsx @@ -1,11 +1,10 @@ import * as React from 'react' -import { Dialog } from '@chakra-ui/react' +import type { HTMLChakraProps } from '@chakra-ui/react' import { MaybeRenderProp, runIfFn } from '@saas-ui/core/utils' -import { CloseButton } from '@saas-ui/react/close-button' +import { Dialog as BaseDialog } from '@saas-ui/react/dialog' -export interface BaseModalProps - extends Omit { +export interface ModalProps extends Omit { /** * The modal title */ @@ -17,7 +16,7 @@ export interface BaseModalProps /** * Callback when the modal is opened or closed */ - onOpenChange: (details: Dialog.OpenChangeDetails) => void + onOpenChange: (details: { open: boolean }) => void /** * The modal children */ @@ -40,18 +39,22 @@ export interface BaseModalProps /** * Props for the modal header */ - headerProps?: Dialog.HeaderProps + headerProps?: HTMLChakraProps<'div'> /** * Props for the modal content */ - contentProps?: Dialog.ContentProps + contentProps?: HTMLChakraProps<'div'> + /** + * Props for the modal body + */ + bodyProps?: HTMLChakraProps<'div'> /** * Props for the modal footer */ - footerProps?: Dialog.FooterProps + footerProps?: HTMLChakraProps<'div'> } -export const BaseModal: React.FC = (props) => { +export const Modal: React.FC = (props) => { const { title, footer, @@ -62,39 +65,26 @@ export const BaseModal: React.FC = (props) => { hideBackdrop, headerProps, contentProps, + bodyProps, footerProps, ...rest } = props return ( - - {!hideBackdrop && } - - - {title && {title}} - {!hideCloseButton && ( - - - - )} - + + + {title && ( + {title} + )} + {!hideCloseButton && } + + {({ open, setOpen }) => runIfFn(children, { open, setOpen })} - - {footer && {footer}} - - - - ) -} - -export const Modal: React.FC = (props) => { - const { children, open, onOpenChange, ...rest } = props - return ( - - - - {({ open, setOpen }) => runIfFn(children, { open, setOpen })} - - - + + + {footer && ( + {footer} + )} + + ) } diff --git a/packages/saas-ui-modals/src/types.ts b/packages/saas-ui-modals/src/types.ts index d38729a71..2a75de962 100644 --- a/packages/saas-ui-modals/src/types.ts +++ b/packages/saas-ui-modals/src/types.ts @@ -1,40 +1,53 @@ -import { FormProps, FormType } from '@saas-ui/forms' -import { ModalId } from './provider' +import type { + FieldValues, + FormProps, + FormType, + WithFields, +} from '@saas-ui/forms' import { AnyObjectSchema, YupFormType } from '@saas-ui/forms/yup' -import { FormDialogFieldOverrides } from './form' -import { WithFields } from '@saas-ui/forms' -import { FieldValues } from '@saas-ui/forms' -import type { z } from 'zod' -import type { InferType } from 'yup' import { ZodFormType } from '@saas-ui/forms/zod' +import { ModalId } from '@saas-ui/modals-provider' +import type { InferType } from 'yup' +import type { z } from 'zod' + +import { FormDialogFieldOverrides } from './form' -export type FormDialogHandler = T extends YupFormType< - infer FieldDefs, - infer ExtraProps, - infer ExtraOverrides, - 'yup' -> - ? YupHandler - : T extends ZodFormType< - infer FieldDefs, - infer ExtraProps, - infer ExtraOverrides, - 'zod' - > - ? ZodHandler - : T extends FormType - ? FormHandler - : never +export type FormDialogHandler = + T extends YupFormType< + infer FieldDefs, + infer ExtraProps, + infer ExtraOverrides, + 'yup' + > + ? YupHandler + : T extends ZodFormType< + infer FieldDefs, + infer ExtraProps, + infer ExtraOverrides, + 'zod' + > + ? ZodHandler + : T extends FormType< + infer FieldDefs, + infer ExtraProps, + infer ExtraOverrides + > + ? FormHandler< + FieldDefs, + object, + ExtraOverrides & FormDialogFieldOverrides + > + : never export type ZodHandler< FieldDefs, ExtraProps = object, ExtraOverrides = object, - Type extends 'zod' = 'zod' + Type extends 'zod' = 'zod', > = < TSchema extends z.AnyZodObject = z.AnyZodObject, TFieldValues extends z.infer = z.infer, - TContext extends object = object + TContext extends object = object, >( props: WithFields< FormProps, @@ -42,17 +55,17 @@ export type ZodHandler< ExtraOverrides > & { ref?: React.ForwardedRef - } & ExtraProps + } & ExtraProps, ) => ModalId export type FormHandler< FieldDefs, ExtraProps = object, - ExtraOverrides = object + ExtraOverrides = object, > = < TSchema = unknown, TFieldValues extends FieldValues = FieldValues, - TContext extends object = object + TContext extends object = object, >( props: WithFields< FormProps, @@ -60,18 +73,18 @@ export type FormHandler< ExtraOverrides > & { ref?: React.ForwardedRef - } & ExtraProps + } & ExtraProps, ) => ModalId export type YupHandler< FieldDefs, ExtraProps = object, ExtraOverrides = object, - Type extends 'yup' = 'yup' + Type extends 'yup' = 'yup', > = < TSchema extends AnyObjectSchema = AnyObjectSchema, TFieldValues extends InferType = InferType, // placeholder - TContext extends object = object + TContext extends object = object, >( props: WithFields< FormProps, @@ -79,5 +92,5 @@ export type YupHandler< ExtraOverrides > & { ref?: React.ForwardedRef - } & ExtraProps + } & ExtraProps, ) => ModalId diff --git a/packages/saas-ui-modals/stories/modals.stories.tsx b/packages/saas-ui-modals/stories/modals.stories.tsx index 0bc3fe761..7f4b70069 100644 --- a/packages/saas-ui-modals/stories/modals.stories.tsx +++ b/packages/saas-ui-modals/stories/modals.stories.tsx @@ -1,11 +1,12 @@ import * as React from 'react' import { Button, Container, Stack } from '@chakra-ui/react' +import { createModals } from '@saas-ui/modals-provider' -import { createModals } from '../src' -import { BaseModalProps, Modal } from '../src/modal' +import { ModalsProvider, useModals } from '../src/index.ts' +import { Modal, ModalProps } from '../src/modal.tsx' -interface CustomModalProps extends Omit { +interface CustomModalProps extends Omit { customProp: 'test' children?: React.ReactNode } @@ -21,7 +22,7 @@ const CustomModal: React.FC = ({ ) -const { ModalsProvider: ModalsProvider, useModals } = createModals({ +const custom = createModals({ modals: { custom: CustomModal, }, @@ -29,6 +30,9 @@ const { ModalsProvider: ModalsProvider, useModals } = createModals({ export default { title: 'Modals/Modals Manager', + parameters: { + layout: 'fullscreen', + }, decorators: [ (Story: any) => ( @@ -49,7 +53,7 @@ export const Basic = () => { onClick={() => { const id = modals.open({ title: 'My Modal', - body: <>My modal, + children: <>My modal, footer: , }) }} @@ -60,7 +64,7 @@ export const Basic = () => { onClick={() => { const id = modals.open({ title: 'My Modal', - body: <>My modal, + children: <>My modal, size: 'xl', footer: , }) @@ -72,7 +76,7 @@ export const Basic = () => { onClick={() => modals.alert({ title: 'Import finished', - body: 'Your import has finish and can now be used.', + children: 'Your import has finish and can now be used.', }) } > @@ -82,10 +86,12 @@ export const Basic = () => { onClick={() => modals.confirm({ title: 'Delete user?', - body: 'Are you sure you want to delete this user?', - confirmProps: { - colorScheme: 'red', - children: 'Delete', + children: 'Are you sure you want to delete this user?', + slotProps: { + confirm: { + colorPalette: 'red', + children: 'Delete', + }, }, }) } @@ -94,7 +100,8 @@ export const Basic = () => { - ) -} +// return ( +// +// ) +// } -export const CustomAsComponent = () => { - const modals = useModals() +// export const CustomAsComponent = () => { +// const modals = useModals() - return ( - - ) -} +// return ( +// +// ) +// } export const OnClose = () => { const modals = useModals() @@ -260,7 +271,7 @@ export const AsyncConfirmDialog = () => { onConfirm: () => new Promise((resolve) => { setTimeout(() => { - resolve() + resolve(void 0) }, 2000) }), }) diff --git a/packages/saas-ui-nprogress/package.json b/packages/saas-ui-nprogress/package.json index ca0ffc5d5..23752e9be 100644 --- a/packages/saas-ui-nprogress/package.json +++ b/packages/saas-ui-nprogress/package.json @@ -31,12 +31,12 @@ "publishConfig": { "access": "public" }, - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "homepage": "https://saas-ui.dev/", "repository": { "type": "git", - "url": "https://github.com/saas-js/saas-ui", + "url": "git+https://github.com/saas-js/saas-ui.git", "directory": "packages/saas-ui-nprogress" }, "keywords": [ diff --git a/packages/saas-ui-palette/package.json b/packages/saas-ui-palette/package.json index 24f766b40..3300c68cc 100644 --- a/packages/saas-ui-palette/package.json +++ b/packages/saas-ui-palette/package.json @@ -31,12 +31,12 @@ "publishConfig": { "access": "public" }, - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "homepage": "https://saas-ui.dev/", "repository": { "type": "git", - "url": "https://github.com/saas-js/saas-ui", + "url": "git+https://github.com/saas-js/saas-ui.git", "directory": "packages/palette" }, "keywords": [ diff --git a/packages/saas-ui-react/package.json b/packages/saas-ui-react/package.json index 5f6fbd0d4..4de5fa468 100644 --- a/packages/saas-ui-react/package.json +++ b/packages/saas-ui-react/package.json @@ -11,7 +11,7 @@ "require": "./dist/index.js", "import": "./dist/index.mjs" }, - "./components/*": { + "./*": { "sui": "./src/components/*/index.ts", "types": "./dist/components/*/index.d.ts", "require": "./dist/components/*/index.cjs", @@ -40,7 +40,7 @@ "publishConfig": { "access": "public" }, - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "homepage": "https://saas-ui.dev/", "repository": { diff --git a/packages/saas-ui-react/src/components/dialog/dialog.tsx b/packages/saas-ui-react/src/components/dialog/dialog.tsx index 04056306a..6f0277fba 100644 --- a/packages/saas-ui-react/src/components/dialog/dialog.tsx +++ b/packages/saas-ui-react/src/components/dialog/dialog.tsx @@ -4,7 +4,7 @@ import { Dialog as ChakraDialog, Portal } from '@chakra-ui/react' import { CloseButton } from '#components/close-button' -interface DialogContentProps extends ChakraDialog.ContentProps { +export interface DialogContentProps extends ChakraDialog.ContentProps { portalled?: boolean portalRef?: React.RefObject backdrop?: boolean @@ -52,6 +52,8 @@ export const DialogCloseTrigger = forwardRef< ) }) +export type DialogRootProps = ChakraDialog.RootProps + export const DialogRoot = ChakraDialog.Root export const DialogFooter = ChakraDialog.Footer export const DialogHeader = ChakraDialog.Header @@ -61,3 +63,4 @@ export const DialogTitle = ChakraDialog.Title export const DialogDescription = ChakraDialog.Description export const DialogTrigger = ChakraDialog.Trigger export const DialogActionTrigger = ChakraDialog.ActionTrigger +export const DialogContext = ChakraDialog.Context diff --git a/packages/saas-ui-react/src/components/dialog/index.ts b/packages/saas-ui-react/src/components/dialog/index.ts index e69de29bb..af27dc208 100644 --- a/packages/saas-ui-react/src/components/dialog/index.ts +++ b/packages/saas-ui-react/src/components/dialog/index.ts @@ -0,0 +1 @@ +export * as Dialog from './namespace.ts' diff --git a/packages/saas-ui-react/src/components/dialog/namespace.ts b/packages/saas-ui-react/src/components/dialog/namespace.ts index 6379c1aa3..c9b313fa0 100644 --- a/packages/saas-ui-react/src/components/dialog/namespace.ts +++ b/packages/saas-ui-react/src/components/dialog/namespace.ts @@ -10,4 +10,9 @@ export { DialogDescription as Description, DialogTitle as Title, DialogTrigger as Trigger, + DialogContext as Context, +} from './dialog.tsx' +export type { + DialogContentProps as ContentProps, + DialogRootProps as RootProps, } from './dialog.tsx' diff --git a/packages/saas-ui-react/src/components/drawer/drawer.tsx b/packages/saas-ui-react/src/components/drawer/drawer.tsx index 287550cc3..7e03782c5 100644 --- a/packages/saas-ui-react/src/components/drawer/drawer.tsx +++ b/packages/saas-ui-react/src/components/drawer/drawer.tsx @@ -4,7 +4,7 @@ import { Drawer as ChakraDrawer, Portal } from '@chakra-ui/react' import { CloseButton } from '#components/close-button' -interface DrawerContentProps extends ChakraDrawer.ContentProps { +export interface DrawerContentProps extends ChakraDrawer.ContentProps { portalled?: boolean portalRef?: React.RefObject offset?: ChakraDrawer.ContentProps['padding'] @@ -51,3 +51,6 @@ export const DrawerBackdrop = ChakraDrawer.Backdrop export const DrawerDescription = ChakraDrawer.Description export const DrawerTitle = ChakraDrawer.Title export const DrawerActionTrigger = ChakraDrawer.ActionTrigger +export const DrawerContext = ChakraDrawer.Context + +export type DrawerRootProps = ChakraDrawer.RootProps diff --git a/packages/saas-ui-react/src/components/drawer/namespace.ts b/packages/saas-ui-react/src/components/drawer/namespace.ts index 62225b1f3..255fe8374 100644 --- a/packages/saas-ui-react/src/components/drawer/namespace.ts +++ b/packages/saas-ui-react/src/components/drawer/namespace.ts @@ -10,4 +10,10 @@ export { DrawerDescription as Description, DrawerTitle as Title, DrawerTrigger as Trigger, + DrawerContext as Context, +} from './drawer.tsx' + +export type { + DrawerContentProps as ContentProps, + DrawerRootProps as RootProps, } from './drawer.tsx' diff --git a/packages/saas-ui-react/src/index.ts b/packages/saas-ui-react/src/index.ts index e64749074..f50d47a1e 100644 --- a/packages/saas-ui-react/src/index.ts +++ b/packages/saas-ui-react/src/index.ts @@ -2,3 +2,6 @@ export * from '@saas-ui/core' export { defaultSystem, defaultConfig } from './preset.ts' export { createSystem } from '@chakra-ui/react' + +export { SuiProvider, SuiContext, useLink, useSui } from './provider/index.ts' +export type { SuiContextValue, SuiProviderProps } from './provider/index.ts' diff --git a/packages/saas-ui-core/src/provider/index.ts b/packages/saas-ui-react/src/provider/index.ts similarity index 100% rename from packages/saas-ui-core/src/provider/index.ts rename to packages/saas-ui-react/src/provider/index.ts diff --git a/packages/saas-ui-core/src/provider/sui-provider.tsx b/packages/saas-ui-react/src/provider/sui-provider.tsx similarity index 86% rename from packages/saas-ui-core/src/provider/sui-provider.tsx rename to packages/saas-ui-react/src/provider/sui-provider.tsx index e73ebeb02..aab6532d5 100644 --- a/packages/saas-ui-core/src/provider/sui-provider.tsx +++ b/packages/saas-ui-react/src/provider/sui-provider.tsx @@ -4,7 +4,6 @@ import { ChakraProvider, ChakraProviderProps } from '@chakra-ui/react' export interface SuiContextValue { linkComponent?: React.ElementType - onError?: (error: Error, errorInfo: React.ErrorInfo) => void } export const SuiContext = React.createContext({}) @@ -18,10 +17,12 @@ export interface SuiProviderProps extends ChakraProviderProps { export function SuiProvider(props: SuiProviderProps) { const { linkComponent, onError, children, ...rest } = props - const context = { - linkComponent, - onError, - } + const context = React.useMemo( + () => ({ + linkComponent, + }), + [], + ) return ( diff --git a/packages/saas-ui-core/src/provider/use-link.test.tsx b/packages/saas-ui-react/src/provider/use-link.test.tsx similarity index 100% rename from packages/saas-ui-core/src/provider/use-link.test.tsx rename to packages/saas-ui-react/src/provider/use-link.test.tsx diff --git a/packages/saas-ui-core/src/provider/use-link.tsx b/packages/saas-ui-react/src/provider/use-link.tsx similarity index 100% rename from packages/saas-ui-core/src/provider/use-link.tsx rename to packages/saas-ui-react/src/provider/use-link.tsx diff --git a/packages/saas-ui-supabase/package.json b/packages/saas-ui-supabase/package.json index fe9dd4275..42bba7f1a 100644 --- a/packages/saas-ui-supabase/package.json +++ b/packages/saas-ui-supabase/package.json @@ -32,12 +32,12 @@ "publishConfig": { "access": "public" }, - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "homepage": "https://saas-ui.dev/", "repository": { "type": "git", - "url": "https://github.com/saas-js/saas-ui", + "url": "git+https://github.com/saas-js/saas-ui.git", "directory": "packages/saas-ui-auth" }, "keywords": [ diff --git a/packages/saas-ui-theme-glass/package.json b/packages/saas-ui-theme-glass/package.json index 7ad64fbb8..19ea32803 100644 --- a/packages/saas-ui-theme-glass/package.json +++ b/packages/saas-ui-theme-glass/package.json @@ -49,12 +49,12 @@ "publishConfig": { "access": "public" }, - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "homepage": "https://saas-ui.dev/", "repository": { "type": "git", - "url": "https://github.com/saas-js/saas-ui", + "url": "git+https://github.com/saas-js/saas-ui.git", "directory": "packages/saas-ui-theme-glass" }, "keywords": [ diff --git a/packages/saas-ui-theme/package.json b/packages/saas-ui-theme/package.json index fad161f1f..41767340b 100644 --- a/packages/saas-ui-theme/package.json +++ b/packages/saas-ui-theme/package.json @@ -57,12 +57,12 @@ "publishConfig": { "access": "public" }, - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "homepage": "https://saas-ui.dev/", "repository": { "type": "git", - "url": "https://github.com/saas-js/saas-ui", + "url": "git+https://github.com/saas-js/saas-ui.git", "directory": "packages/saas-ui-theme" }, "keywords": [ diff --git a/packages/saas-ui-use-hotkeys/package.json b/packages/saas-ui-use-hotkeys/package.json index fd87d63fe..f64995a1f 100644 --- a/packages/saas-ui-use-hotkeys/package.json +++ b/packages/saas-ui-use-hotkeys/package.json @@ -31,12 +31,12 @@ "publishConfig": { "access": "public" }, - "author": "Eelco Wiersma ", + "author": "Eelco Wiersma ", "license": "MIT", "homepage": "https://saas-ui.dev/", "repository": { "type": "git", - "url": "https://github.com/saas-js/saas-ui", + "url": "git+https://github.com/saas-js/saas-ui.git", "directory": "packages/hotkeys" }, "keywords": [ diff --git a/yarn.lock b/yarn.lock index 7ca475c08..4f480d78b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4632,20 +4632,12 @@ __metadata: resolution: "@saas-ui/core@workspace:packages/saas-ui-core" dependencies: "@ark-ui/react": "npm:^4.2.0" - "@chakra-ui/react": "npm:^3.0.2" - "@react-aria/interactions": "npm:^3.22.4" "@react-aria/utils": "npm:^3.25.3" - "@react-types/shared": "npm:^3.25.0" - "@saas-ui/theme": "workspace:*" "@zag-js/dom-event": "npm:^0.77.0" "@zag-js/dom-utils": "npm:^0.2.4" jotai: "npm:^2.10.1" - react-icons: "npm:^5.3.0" tsup: "npm:^8.3.5" peerDependencies: - "@chakra-ui/react": ">=3.0.0-next.13" - "@emotion/react": ^11 - "@emotion/styled": ^11 react: ">=18" react-dom: ">=18" languageName: unknown @@ -4766,6 +4758,15 @@ __metadata: languageName: unknown linkType: soft +"@saas-ui/modals-provider@workspace:*, @saas-ui/modals-provider@workspace:packages/saas-ui-modals-provider": + version: 0.0.0-use.local + resolution: "@saas-ui/modals-provider@workspace:packages/saas-ui-modals-provider" + peerDependencies: + react: ">=18.0.0" + react-dom: ">=18.0.0" + languageName: unknown + linkType: soft + "@saas-ui/modals@workspace:*, @saas-ui/modals@workspace:packages/saas-ui-modals": version: 0.0.0-use.local resolution: "@saas-ui/modals@workspace:packages/saas-ui-modals" @@ -4773,6 +4774,7 @@ __metadata: "@saas-ui/core": "workspace:*" "@saas-ui/forms": "workspace:*" "@saas-ui/hooks": "workspace:*" + "@saas-ui/modals-provider": "workspace:*" yup: "npm:^1.4.0" zod: "npm:^3.23.8" peerDependencies: @@ -4919,7 +4921,7 @@ __metadata: languageName: unknown linkType: soft -"@saas-ui/theme@workspace:*, @saas-ui/theme@workspace:packages/saas-ui-theme": +"@saas-ui/theme@workspace:packages/saas-ui-theme": version: 0.0.0-use.local resolution: "@saas-ui/theme@workspace:packages/saas-ui-theme" peerDependencies: