Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/ds 218 modal #590

Merged
merged 16 commits into from
Feb 11, 2025
5 changes: 5 additions & 0 deletions .changeset/giant-hats-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hopper-ui/components": patch
---

Add Modal and CustomModal component
1 change: 1 addition & 0 deletions .stylelintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const config = {
"prettier/prettier": null,
// We want to enforce the use of logical properties
"csstools/use-logical": true,
"media-feature-range-notation": "prefix",
"selector-class-pattern": [
/** Selector that ensures our classNames have the pattern hop-ComponentName__element-name--modifier-name */
"^hop-([A-Z][A-z0-9]+)([-]?[a-z0-9]+)*(__[a-z0-9]([-]?[a-z0-9]+)*)?(--[a-z0-9]([-]?[a-z0-9]+)*)?$",
Expand Down
110 changes: 110 additions & 0 deletions apps/docs/content/components/overlays/Modal.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
title: Modal
description: Modals focus the user’s attention exclusively on one task or piece of information via a window that sits on top of the page content.
category: "overlays"
links:
source: https://github.com/gsoft-inc/wl-hopper/blob/main/packages/components/src/Modal/src/Modal.tsx
---

<Example src="Modal/docs/preview" isOpen />

### Composed Components

A `Modal` uses the following components:

<ComposedComponents components={["Image", "Heading", "Header", "Content", "Footer", "Button", "ButtonGroup"]}/>

## Usage

### Default

A modal must have an heading and a content.

<Example src="Modal/docs/default" />

### Image

A modal can have a side banner image. Make sure the image has no essential information as it could be cropped in mobile view. Images should not prevent a user from seeing the close button, be conscious of this.

<Example src="Modal/docs/image" />

### Choice

A modal can offer a choice between 2 options. Keep the copy not too long in order to help the user quickly make his choice.

<Example src="Modal/docs/choice" />

### Header

Use an header to provide additional information usually in the form of a link or a tooltip that provides more context to the task at hand. Links should open in a new window.

<Example src="Modal/docs/header" />

### Footer

Use a footer to provide trivial information about content present in the modal, like a step : 1/3.

<Example src="Modal/docs/footer" />

### Buttons

A modal can have a single button. Use a primary button to provide the main action.

<Example src="Modal/docs/button" />

Or a group of button. A maximum of 3 buttons are allowed in a modal, when necessary. The secondary and tertiary actions should be using a secondary variant.

<Example src="Modal/docs/button-group" />

### Dismissable

By default, a modal will dismiss on outside interactions and esc keydown. However, in some cases, you might want to force the user to explicitly dismiss the modal with a targeted call to action.
This is what the `isDismissable` and the `isKeyboardDismissDisabled` prop is for.

You can set the isDismissable prop to false and isKeyboardDismissDisabled to true and render a call to action which will manually dismiss the popover by calling a close function.

<Example src="Modal/docs/dismissable" />

### Controlled

The open state can be handled in controlled mode.

<Example src="Modal/docs/controlled" />

### Custom trigger

You don't have to use a ModalTrigger component if it doesn't fit your needs. A modal component can be used on it's own with any custom trigger.

<Example src="Modal/docs/custom-trigger" />

### Sizes

A modal can be small, medium, large, extra-large, fullscreen or fullscreenTakeover. The default size is medium.

<Example src="Modal/docs/sizes" />

### Responsive sizes

A modal can have different size in mobile and desktop view.

<Example src="Modal/docs/responsive-sizes" />

### Custom

A CustomModal is a Modal with a custom layout. A CustomModal must contain a `<Heading slot="title">` or have an aria-label or aria-labelledby attribute for accessibility.

<Example src="Modal/docs/custom" />

## Props

### Modal

<PropTable component="Modal" />

### ModalTrigger

<PropTable component="ModalTrigger" />

### CustomModal

<PropTable component="CustomModal" />
42 changes: 42 additions & 0 deletions apps/docs/examples/Preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,48 @@ export const Previews: Record<string, Preview> = {
"Link/docs/image": {
component: lazy(() => import("@/../../packages/components/src/Link/docs/image.tsx"))
},
"Modal/docs/preview": {
component: lazy(() => import("@/../../packages/components/src/Modal/docs/preview.tsx"))
},
"Modal/docs/default": {
component: lazy(() => import("@/../../packages/components/src/Modal/docs/default.tsx"))
},
"Modal/docs/image": {
component: lazy(() => import("@/../../packages/components/src/Modal/docs/image.tsx"))
},
"Modal/docs/choice": {
component: lazy(() => import("@/../../packages/components/src/Modal/docs/choice.tsx"))
},
"Modal/docs/header": {
component: lazy(() => import("@/../../packages/components/src/Modal/docs/header.tsx"))
},
"Modal/docs/footer": {
component: lazy(() => import("@/../../packages/components/src/Modal/docs/footer.tsx"))
},
"Modal/docs/button": {
component: lazy(() => import("@/../../packages/components/src/Modal/docs/button.tsx"))
},
"Modal/docs/button-group": {
component: lazy(() => import("@/../../packages/components/src/Modal/docs/button-group.tsx"))
},
"Modal/docs/dismissable": {
component: lazy(() => import("@/../../packages/components/src/Modal/docs/dismissable.tsx"))
},
"Modal/docs/controlled": {
component: lazy(() => import("@/../../packages/components/src/Modal/docs/controlled.tsx"))
},
"Modal/docs/custom-trigger": {
component: lazy(() => import("@/../../packages/components/src/Modal/docs/custom-trigger.tsx"))
},
"Modal/docs/sizes": {
component: lazy(() => import("@/../../packages/components/src/Modal/docs/sizes.tsx"))
},
"Modal/docs/responsive-sizes": {
component: lazy(() => import("@/../../packages/components/src/Modal/docs/responsive-sizes.tsx"))
},
"Modal/docs/custom": {
component: lazy(() => import("@/../../packages/components/src/Modal/docs/custom.tsx"))
},
"overlays/Popover/docs/preview": {
component: lazy(() => import("@/../../packages/components/src/overlays/Popover/docs/preview.tsx"))
},
Expand Down
Binary file added apps/docs/public/mossy-frog.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions packages/components/src/Modal/docs/button-group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Button, ButtonGroup, Content, Heading, Modal, ModalTrigger } from "@hopper-ui/components";

export default function Example() {
return (
<ModalTrigger>
<Button variant="secondary">Open modal</Button>
<Modal>
{({ close }) => (
<>
<Heading>Fascinating Frog Facts!</Heading>
<Content>
Frogs are amphibians, meaning they can live both in water and on land! With their powerful legs, some species can jump over 20 times their body length—that’s like a human leaping over a school bus!
</Content>
<ButtonGroup>
<Button variant="secondary" onPress={close}>Cancel</Button>
<Button onPress={close}>Save</Button>
</ButtonGroup>
</>
)}
</Modal>
</ModalTrigger>
);
}
22 changes: 22 additions & 0 deletions packages/components/src/Modal/docs/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Button, Content, Heading, Modal, ModalTrigger } from "@hopper-ui/components";

export default function Example() {
return (
<ModalTrigger>
<Button variant="secondary">Open modal</Button>
<Modal>
{({ close }) => (
<>
<Heading>Fascinating Frog Facts!</Heading>
<Content>
Frogs are amphibians, meaning they can live both in water and on land! With their powerful legs, some species can jump over 20 times their body length—that’s like a human leaping over a school bus!
</Content>
<Button onPress={close}>
Save
</Button>
</>
)}
</Modal>
</ModalTrigger>
);
}
40 changes: 40 additions & 0 deletions packages/components/src/Modal/docs/choice.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Button, Card, Content, Flex, Heading, Image, Modal, ModalTrigger } from "@hopper-ui/components";

export default function Example() {
return (
<ModalTrigger>
<Button variant="secondary">Open modal</Button>
<Modal>
<Heading>Fascinating Frog Facts!</Heading>
<Content>
<Flex gap="stack-lg">
<Card flex={1}>
<Image objectFit="cover" alt="Frog" src="/frog.jpg" />
<Flex direction="column" gap="stack-sm" padding="inset-md" height="100%" justifyContent="space-between">
<Flex direction="column" gap="stack-sm">
<Heading>Frog</Heading>
<Content>
Common frogs are found in ponds, marshes, and forests across the world. Unlike some of their flashier cousins, they rely on stealth and speed rather than bright colors to survive.
</Content>
<Button variant="secondary">Choose</Button>
</Flex>
</Flex>
</Card>
<Card flex={1}>
<Image objectFit="cover" alt="Mossy Frog" src="/mossy-frog.jpg" />
<Flex direction="column" gap="stack-sm" padding="inset-md" height="100%" justifyContent="space-between">
<Flex direction="column" gap="stack-sm">
<Heading>Mossy Frog</Heading>
<Content>
A mossy tree frog with rough, bark-like skin, blending perfectly into its surroundings for camouflage and protection.
</Content>
</Flex>
<Button variant="secondary">Choose</Button>
</Flex>
</Card>
</Flex>
</Content>
</Modal>
</ModalTrigger>
);
}
18 changes: 18 additions & 0 deletions packages/components/src/Modal/docs/controlled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Button, Content, Heading, Modal, ModalTrigger } from "@hopper-ui/components";
import { useState } from "react";

export default function Example() {
const [isOpen, setIsOpen] = useState(false);

return (
<ModalTrigger isOpen={isOpen} onOpenChange={setIsOpen}>
<Button variant="secondary">Open modal</Button>
<Modal>
<Heading>Fascinating Frog Facts!</Heading>
<Content>
Frogs are amphibians, meaning they can live both in water and on land! With their powerful legs, some species can jump over 20 times their body length—that’s like a human leaping over a school bus!
</Content>
</Modal>
</ModalTrigger>
);
}
18 changes: 18 additions & 0 deletions packages/components/src/Modal/docs/custom-trigger.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Button, Content, Heading, Modal } from "@hopper-ui/components";
import { useState } from "react";

export default function Example() {
const [isOpen, setIsOpen] = useState(false);

return (
<>
<Button variant="secondary" onPress={() => setIsOpen(true)}>Open modal</Button>
<Modal isOpen={isOpen} onOpenChange={setIsOpen}>
<Heading>Fascinating Frog Facts!</Heading>
<Content>
Frogs are amphibians, meaning they can live both in water and on land! With their powerful legs, some species can jump over 20 times their body length—that’s like a human leaping over a school bus!
</Content>
</Modal>
</>
);
}
21 changes: 21 additions & 0 deletions packages/components/src/Modal/docs/custom.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Button, Content, CustomModal, Heading, Image, ModalTrigger, Stack } from "@hopper-ui/components";

export default function Example() {
return (
<ModalTrigger>
<Button variant="secondary">Open modal</Button>
<CustomModal padding="inset-lg">
{({ close }) => (
<Stack>
<Button variant="secondary" onPress={close}>Close</Button>
<Heading>Fascinating Frog Facts!</Heading>
<Content>
Frogs are amphibians, meaning they can live both in water and on land! With their powerful legs, some species can jump over 20 times their body length—that’s like a human leaping over a school bus!
</Content>
<Image src="/frog.jpg" alt="Frog" />
</Stack>
)}
</CustomModal>
</ModalTrigger>
);
}
15 changes: 15 additions & 0 deletions packages/components/src/Modal/docs/default.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Button, Content, Heading, Modal, ModalTrigger } from "@hopper-ui/components";

export default function Example() {
return (
<ModalTrigger>
<Button variant="secondary">Open modal</Button>
<Modal>
<Heading>Fascinating Frog Facts!</Heading>
<Content>
Frogs are amphibians, meaning they can live both in water and on land! With their powerful legs, some species can jump over 20 times their body length—that’s like a human leaping over a school bus!
</Content>
</Modal>
</ModalTrigger>
);
}
22 changes: 22 additions & 0 deletions packages/components/src/Modal/docs/dismissable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Button, Content, Heading, Modal, ModalTrigger } from "@hopper-ui/components";

export default function Example() {
return (
<ModalTrigger>
<Button variant="secondary">Open modal</Button>
<Modal isDismissible={false} isKeyboardDismissDisabled>
{({ close }) => (
<>
<Heading>Fascinating Frog Facts!</Heading>
<Content>
Frogs are amphibians, meaning they can live both in water and on land! With their powerful legs, some species can jump over 20 times their body length—that’s like a human leaping over a school bus!
</Content>
<Button onPress={close}>
Save
</Button>
</>
)}
</Modal>
</ModalTrigger>
);
}
18 changes: 18 additions & 0 deletions packages/components/src/Modal/docs/footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Button, Content, Footer, Heading, Modal, ModalTrigger } from "@hopper-ui/components";

export default function Example() {
return (
<ModalTrigger>
<Button variant="secondary">Open modal</Button>
<Modal>
<Heading>Fascinating Frog Facts!</Heading>
<Content>
Frogs are amphibians, meaning they can live both in water and on land! With their powerful legs, some species can jump over 20 times their body length—that’s like a human leaping over a school bus!
</Content>
<Footer>
Copyright 2025
</Footer>
</Modal>
</ModalTrigger>
);
}
Loading
Loading