Skip to content

Commit

Permalink
feat: implement modal with PrimeVue
Browse files Browse the repository at this point in the history
  • Loading branch information
NateWaldschmidt committed Feb 7, 2024
1 parent cbe0c14 commit 85e729f
Show file tree
Hide file tree
Showing 18 changed files with 559 additions and 219 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# CHANGELOG

## v2.0.9

- Implement new `Modal` component

### Breaking Changes

- The `Modal` component has been moved to `LegacyModal` due to the new `Modal` utilizing `v-model`

## v2.0.8

- Fix `LobTable`s with nested slots
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lob/ui-components",
"version": "2.0.8",
"version": "2.0.9",
"engines": {
"node": ">=20.2.0",
"npm": ">=10.2.0"
Expand Down
10 changes: 10 additions & 0 deletions src/assets/styles/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,13 @@
}
}
}

// This is a hack for the modal styles flashing.
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
8 changes: 4 additions & 4 deletions src/components/Dropdown/ConfirmChangeModal.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<Modal
<LegacyModal
:visible="visible"
:close-button-aria-label="
t('dropdown.confirmChangeModal.closeButtonAriaLabel')
Expand Down Expand Up @@ -33,16 +33,16 @@
</LobButton>
</div>
</div>
</Modal>
</LegacyModal>
</template>

<script>
import Modal from '../Modal/Modal.vue';
import LegacyModal from '../LegacyModal/LegacyModal.vue';
import { TriangleExclamation } from '../Icons';
import LobButton from '../Button/Button.vue';
export default {
name: 'ConfirmChangeModal',
components: { Modal, TriangleExclamation, LobButton },
components: { LegacyModal, TriangleExclamation, LobButton },
props: {
visible: {
type: Boolean,
Expand Down
8 changes: 4 additions & 4 deletions src/components/Dropzone/ConfirmRemoveFileModal.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<Modal
<LegacyModal
:visible="visible"
:close-button-aria-label="
t('dropzone.confirmRemoveFileModal.closeButtonAriaLabel')
Expand Down Expand Up @@ -30,16 +30,16 @@
</LobButton>
</div>
</div>
</Modal>
</LegacyModal>
</template>

<script>
import { Modal } from '@/components';
import { LegacyModal } from '@/components';
import { TriangleExclamation } from '../Icons';
import { LobButton } from '@/components';
export default {
name: 'ConfirmRemoveFileModal',
components: { Modal, TriangleExclamation, LobButton },
components: { LegacyModal, TriangleExclamation, LobButton },
props: {
visible: {
type: Boolean,
Expand Down
57 changes: 57 additions & 0 deletions src/components/LegacyModal/LegacyModal.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Canvas, ArgTypes, PRIMARY_STORY } from '@storybook/addon-docs';
import { Primary } from './LegacyModal.stories';

# Modal

A modal is a card that is opened upon action to display information.

<Canvas of={Primary} />

## How to Use

Utilize the `Modal` component going forward

This `LegacyModal` component is designed to be used with slots to fill in its header and footer, and the default slot for its body.

Here is an example of a button opening a modal.

Visibility is controlled from the parent component using the `visible` prop. Note: this is not a `v-model`. You will need to update this when the `close` event is emitted.

The `header` prop (required) will provide the modal heading; no heading will render if an empty string is passed to the prop.

The `closeButtonAriaLabel` is also required, and will provide the close (X) button with an aria-label.

To add a footer, use the `#footer` slot. If no footer slot is provided, nothing will be rendered in it's place.

```html
<LobButton @click="isModalVisible = true"> Open Modal </LobButton>

<LegacyModal
:visible="isModalVisible"
header="Select Tracking Events"
closeButtonAriaLabel="Close Modal"
@close="isModalVisible = false"
>
Would you like to export an additional CSV of associated tracking events?
<RadioGroup>
<radio-button
name="exportCSV"
value="yes"
label="Yes"
v-model="radioModel"
/>
<radio-button name="exportCSV" value="no" label="No" v-model="radioModel" />
</RadioGroup>

<template v-slot:footer>
<div class="flex self-end">
<LobButton variant="secondary">Go back</LobButton>
<LobButton variant="primary" class="ml-2">Submit</LobButton>
</div>
</template>
</LegacyModal>
```

## Props

<ArgTypes story={PRIMARY_STORY} />
99 changes: 99 additions & 0 deletions src/components/LegacyModal/LegacyModal.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import Dropdown from '../Dropdown/Dropdown.vue';
import LobButton from '../Button/Button.vue';
import RadioButton from '../RadioButton/RadioButton.vue';
import RadioGroup from '../RadioGroup/RadioGroup.vue';
import LegacyModal from './LegacyModal.vue';
import mdx from './LegacyModal.mdx';

export default {
title: 'Components/LegacyModal',
component: LegacyModal,
parameters: {
docs: {
page: mdx
}
}
};

const isModalVisible = false;
const radioModel = 'yes';

const PrimaryTemplate = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { LegacyModal, LobButton, RadioButton, RadioGroup },
setup: () => ({ args }),
data: () => ({ isModalVisible, radioModel }),
template: `
<LobButton @click="isModalVisible = true">
Open Modal
</LobButton>
<LegacyModal
v-bind="args"
:visible="isModalVisible"
header="Select Tracking Events"
closeButtonAriaLabel="Close Tracking Events Modal"
@close="isModalVisible = false"
>
Would you like to export an additional CSV of associated tracking events?
<RadioGroup>
<radio-button name="exportCSV" id="yes" value="yes" label="Yes" v-model="radioModel"/>
<radio-button name="exportCSV" id="no" value="no" label="No" v-model="radioModel" />
</RadioGroup>
<template v-slot:footer>
<div class="flex self-end">
<LobButton variant="secondary" @click="isModalVisible = false">Go back</LobButton>
<LobButton variant="primary" class="ml-2">Submit</LobButton>
</div>
</template>
</LegacyModal>
`
});

const dropVModel = '';
const WithDropdownTemplate = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { LegacyModal, LobButton, Dropdown },
setup: () => ({ args }),
data: () => ({ isModalVisible, dropVModel }),
template: `
<LobButton @click="isModalVisible = true">
Open Modal
</LobButton>
<LegacyModal
v-bind="args"
width="500px"
:visible="isModalVisible"
header="A Modal with a Dropdown"
closeButtonAriaLabel="Close modal with dropdown"
@close="isModalVisible = false"
>
<div style="height: 150px;">
<div class="mb-5">Select a thing to continue:</div>
<Dropdown
id="dropdown1"
label="thing"
srOnlyLabel
placeholder="Select a value"
:options="['one', 'two']"
v-model="dropVModel"/>
</div>
<template v-slot:footer>
<div class="flex self-end">
<LobButton
class="ml-2"
:disabled="!dropVModel"
@click="isModalVisible=false">OK</LobButton>
</div>
</template>
</LegacyModal>
`
});

export const Primary = PrimaryTemplate.bind({});
Primary.args = {};

export const WithDropdown = WithDropdownTemplate.bind({});
WithDropdown.args = {};
124 changes: 124 additions & 0 deletions src/components/LegacyModal/LegacyModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<template>
<transition name="fade">
<div
v-show="visible"
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-60 z-30"
:aria-hidden="!visible"
@mousedown="closeModal"
@keydown.esc="closeModal"
>
<div
role="dialog"
aria-modal="true"
aria-labelledby="header"
aria-describedby="modalDescription"
:style="{ width: width }"
:class="[
'relative bg-white flex flex-col overflow-y-auto shadow rounded-lg max-h-5/6',
paddingClass
]"
@mousedown.stop
>
<header
v-if="header"
id="header"
:class="['pb-4', { 'border-b border-gray-100': !noSectionDividers }]"
>
<h1 class="pageheading">
{{ header }}
</h1>
<h2 v-if="subheader" class="text-default mt-2">
{{ subheader }}
</h2>
</header>
<section id="modalDescription" :class="paddingClass">
<slot />
</section>
<footer
v-if="hasFooter"
:class="[
'flex flex-col pt-4',
{ 'border-t border-gray-100': !noSectionDividers }
]"
>
<slot name="footer" />
</footer>
<button
:class="[
'absolute top-6 right-4 rounded-full w-7 h-7 p-1 cursor-pointer hover:bg-white-200',
'focus:outline-none focus:ring-2 focus:ring-primary-100'
]"
:aria-label="closeButtonAriaLabel"
@click="closeModal"
@keyup.enter="closeModal"
>
<XmarkLarge />
</button>
</div>
</div>
</transition>
</template>

<script>
import XmarkLarge from '../Icons/XmarkLarge';
export default {
name: 'LegacyModal',
components: { XmarkLarge },
props: {
visible: {
type: Boolean,
default: false
},
width: {
type: String,
default: ''
},
header: {
type: String,
default: null
},
subheader: {
type: String,
default: null
},
closeButtonAriaLabel: {
type: String,
required: true
},
noPadding: {
type: Boolean,
default: false
},
noSectionDividers: {
type: Boolean,
default: false
}
},
emits: ['close'],
computed: {
hasFooter() {
return Boolean(this.$slots.footer);
},
paddingClass() {
return this.noPadding ? 'p-0' : 'p-7';
}
},
methods: {
closeModal() {
this.$emit('close');
}
}
};
</script>

<style scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
Loading

0 comments on commit 85e729f

Please sign in to comment.