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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/modal/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
18 changes: 18 additions & 0 deletions packages/modal/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["plugin:@nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
7 changes: 7 additions & 0 deletions packages/modal/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# modal

This library was generated with [Nx](https://nx.dev).

## Running unit tests

Run `nx test modal` to execute the unit tests via [Vitest](https://vitest.dev/).
12 changes: 12 additions & 0 deletions packages/modal/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "modal",
"version": "0.0.1",
"main": "./index.js",
"types": "./index.d.ts",
"exports": {
".": {
"import": "./index.mjs",
"require": "./index.js"
}
}
}
32 changes: 32 additions & 0 deletions packages/modal/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "modal",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/modal/src",
"projectType": "library",
"tags": [],
"targets": {
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["packages/modal/**/*.{ts,tsx,js,jsx}"]
}
},
"build": {
"executor": "@nx/vite:build",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"outputPath": "dist/packages/modal"
},
"configurations": {
"development": {
"mode": "development"
},
"production": {
"mode": "production"
}
}
}
}
}
1 change: 1 addition & 0 deletions packages/modal/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './lib/Modal';
8 changes: 8 additions & 0 deletions packages/modal/src/lib/ModalBackdrop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
interface ModalBackdropProps {
onClick?: () => void
}
export const ModalBackdrop = (props: ModalBackdropProps) => {
return (
<div className="modal-backdrop fixed inset-0 bg-gray-800/90 z-[99]" onClick={props.onClick}></div>
)
}
13 changes: 13 additions & 0 deletions packages/modal/src/lib/ModalBody.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { cn } from "@bootwind/common"
import { ReactNode } from "react"

interface ModalBodyProps {
children?: ReactNode | string
className?: string
}

export const ModalBody = (props: ModalBodyProps) => (
<div className="modal-body p-5 pt-0">
{ props.children }
</div>
)
45 changes: 45 additions & 0 deletions packages/modal/src/lib/ModalContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { cn } from "@bootwind/common"
import { ReactNode, useEffect } from "react"
import { createPortal } from "react-dom"
import { ModalBackdrop } from "./ModalBackdrop"
import { useModalContext } from "./ModalProvider"

interface ModalContentProps {
children?: ReactNode | string
isOpen?: boolean
className?: string
onModalOpen?: () => void
onModalClose?: () => void
}

export const ModalContent = ({ isOpen = false, ...props }: ModalContentProps) => {
const context = useModalContext()

// Close or open modal reacting to the props
useEffect(() => {
if(isOpen) context.openModal()
else context.closeModal()
}, [isOpen])

// Close or open modal reacting to the context
useEffect(() => {
if(context.isOpen && props.onModalOpen) props.onModalOpen()
else if (!context.isOpen && props.onModalClose) props.onModalClose()
}, [context.isOpen])

return (
createPortal(
(
<div className={cn("fixed inset-0 transition duration-200 [&:not(.show)]:opacity-0 [&:is(.show)]:opacity-100 [&:not(.show)]:invisible [&:is(.show)]:visible [&:is(.show)]:transition [&:is(.show)_.modal]:top-24 ", isOpen ? 'show' : '')}>
<div className="relative">
<div className="modal z-[100] top-20 bg-white absolute max-w-[600px] w-full mx-5 left-1/2 -translate-x-1/2 rounded-md">
{props.children}
</div>
</div>
<ModalBackdrop onClick={() => context.closeModal()}/>
</div>
),
document.body
)
)
}
13 changes: 13 additions & 0 deletions packages/modal/src/lib/ModalFooter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { cn } from "@bootwind/common"
import { ReactNode } from "react"

interface ModalFooterProps {
children?: ReactNode | string
className?: string
}

export const ModalFooter = (props: ModalFooterProps) => (
<div className={cn("modal-footer p-5 pt-0", props.className)}>
{props.children}
</div>
)
13 changes: 13 additions & 0 deletions packages/modal/src/lib/ModalHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { cn } from "@bootwind/common"
import { ReactNode } from "react"

interface ModalHeaderProps {
children?: ReactNode | string
className?: string
}

export const ModalHeader = (props: ModalHeaderProps) => (
<div className={cn("modal-header | p-5 pb-0", props.className)}>
{props.children}
</div>
)
28 changes: 28 additions & 0 deletions packages/modal/src/lib/ModalProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ReactNode, useContext, createContext, useState } from "react"

interface IModalProvider {
isOpen: boolean
modal?: ReactNode
openModal: () => void
closeModal: () => void
}

const ModalContext = createContext<IModalProvider>({
isOpen: false,
} as IModalProvider)

export function ModalProvider({ children }: { children: ReactNode }) {
const [isOpen, setIsOpen] = useState(false)
const openModal = () => setIsOpen(true)
const closeModal = () => setIsOpen(false)
const [modal, setModal] = useState()


return (
<ModalContext.Provider value={{closeModal, openModal, isOpen, modal}}>
{children}
</ModalContext.Provider>
)
}

export const useModalContext = () => useContext(ModalContext)
12 changes: 12 additions & 0 deletions packages/modal/src/lib/ModalTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { cn } from "@bootwind/common"
import { ReactNode } from "react"
import { Text } from "@bootwind/typography"

interface ModalTitleProps {
children?: ReactNode | string
className?: string
}

export const ModalTitle = (props: ModalTitleProps) => (
<Text className={cn("modal-title", props.className)}>{ props.children }</Text>
)
16 changes: 16 additions & 0 deletions packages/modal/src/lib/ModalTrigger.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ReactNode } from "react"
import { useModalContext } from "./ModalProvider"

interface ModalTriggerProps {
children: ReactNode | string
}

export const ModalTrigger = (props: ModalTriggerProps) => {
const context = useModalContext()

return (
<div className="modal-trigger" onClick={() => context.openModal()}>
{ props.children }
</div>
)
}
17 changes: 17 additions & 0 deletions packages/modal/src/lib/modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ReactNode } from "react";
import { ModalProvider } from "./ModalProvider";

/* eslint-disable-next-line */
export interface ModalProps {
children: ReactNode | string
}

export function Modal(props: ModalProps) {
return (
<ModalProvider>
{props.children}
</ModalProvider>
);
}

export default Modal;
43 changes: 43 additions & 0 deletions packages/modal/src/stories/modal.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Meta } from '@storybook/react';
import Modal from '../lib/Modal';
import { Button } from "@bootwind/button"
import { ModalTrigger } from '../lib/ModalTrigger';
import { ModalContent } from '../lib/ModalContent';
import { ModalHeader } from '../lib/ModalHeader';
import { ModalTitle } from '../lib/ModalTitle';
import { ModalBody } from '../lib/ModalBody';
import { ModalFooter } from '../lib/ModalFooter';
import { useState } from 'react';

export default {
title: 'Components/Modal',
component: Modal,
tags: ['autodocs'],
} as Meta;

export const Basic = () => {
const [isOpen, setIsOpen] = useState(false)

return (
<>
<div className="test">
<Modal>
<ModalTrigger>
<Button>Open Modal</Button>
</ModalTrigger>
<ModalContent isOpen={isOpen} onModalOpen={() => setIsOpen(true)} onModalClose={() => setIsOpen(false)}>
<ModalHeader>
<ModalTitle>Sign In</ModalTitle>
</ModalHeader>
<ModalBody>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Saepe mollitia a, molestiae soluta cupiditate consequuntur ullam voluptates, commodi magnam unde amet similique quae! Nostrum ducimus veniam sed labore praesentium molestias.
</ModalBody>
<ModalFooter>
<Button onClick={() => setIsOpen(false)}>Close</Button>
</ModalFooter>
</ModalContent>
</Modal>
</div>
</>
)
}
18 changes: 18 additions & 0 deletions packages/modal/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"types": ["vite/client"]
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
],
"extends": "../../tsconfig.base.json"
}
23 changes: 23 additions & 0 deletions packages/modal/tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": [
"node",
"@nx/react/typings/cssmodule.d.ts",
"@nx/react/typings/image.d.ts",
"vite/client"
]
},
"exclude": [
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx"
],
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
}
Loading