Skip to content

Commit

Permalink
feat(modal): backported slots version from 7.0 (#413)
Browse files Browse the repository at this point in the history
* feat(modal): backported slots, non-breaking change

* chore: comments cleanup

* refactor(modal): removed clicktoclose

* refactor(modal): added new ruxmodalopened event, refactored how closed event fires

* docs(modal): updated modal story

* revert(button): changed back button focus styling

* refactor(modal): moved the auto-focus functionality to didRender

* docs(modal): updated docs on focusing

* revert(button): focus styling

* build: stencil build, also refactored the handleModalChoice func

* docs(modal): updated open close section

* test(modal): added snapshot

* style(modal): udpaetd footer and message padding

* chore(modal): comment cleanup

* docs: changeset

* test(modal): updated e2e

* refactor(modal): refac footer, added slot listeners

* refactor(modal): message no longer conditional

* test(modal): added changing slot tests

* build: stencil build

* style(modal): removed unused style
  • Loading branch information
micahjones13 authored May 25, 2022
1 parent dbc00d1 commit 7fba14e
Show file tree
Hide file tree
Showing 12 changed files with 509 additions and 62 deletions.
8 changes: 8 additions & 0 deletions .changeset/sour-balloons-enjoy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@astrouxds/angular": minor
"@astrouxds/astro-web-components": minor
"astro-website": minor
"@astrouxds/react": minor
---

Modal - Added in slots for message, title and footer to allow for more customization.
6 changes: 5 additions & 1 deletion packages/angular/src/directives/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21405,6 +21405,10 @@ export class RuxMenuItemDivider {


export declare interface RuxModal extends Components.RuxModal {
/**
* Event that is fired when modal opens
*/
ruxmodalopened: EventEmitter<CustomEvent<void>>;
/**
* Event that is fired when modal closes
*/
Expand All @@ -21426,7 +21430,7 @@ export class RuxModal {
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;
proxyOutputs(this, this.el, ['ruxmodalclosed']);
proxyOutputs(this, this.el, ['ruxmodalopened', 'ruxmodalclosed']);
}
}

Expand Down
13 changes: 13 additions & 0 deletions packages/web-components/.storybook/preview-head.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,19 @@
width: 100%;
height: 100%;
}

.sbdocs #story--components-modal--with-slots {
display: block;
min-height: 500px;
}

.sbdocs #story--components-modal--with-slots rux-modal::part(wrapper) {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
}
</style>

<script
Expand Down
4 changes: 4 additions & 0 deletions packages/web-components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32305,6 +32305,10 @@ declare namespace LocalJSX {
* Event that is fired when modal closes
*/
"onRuxmodalclosed"?: (event: CustomEvent<boolean>) => void;
/**
* Event that is fired when modal opens
*/
"onRuxmodalopened"?: (event: CustomEvent<void>) => void;
/**
* Shows and hides modal
*/
Expand Down
11 changes: 11 additions & 0 deletions packages/web-components/src/components/rux-modal/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ Pass properties as attributes of the Astro Rux Modal custom element:
| Event | Description | Type |
| ---------------- | ------------------------------------- | ---------------------- |
| `ruxmodalclosed` | Event that is fired when modal closes | `CustomEvent<boolean>` |
| `ruxmodalopened` | Event that is fired when modal opens | `CustomEvent<void>` |


## Slots

| Slot | Description |
| ------------- | ------------------------------ |
| `"(default)"` | the modal's message or content |
| `"footer"` | the footer of the modal |
| `"header"` | the header of the modal |


## Shadow Parts
Expand All @@ -77,6 +87,7 @@ Pass properties as attributes of the Astro Rux Modal custom element:
| `"container"` | the modal container |
| `"deny-button"` | the modal's deny button |
| `"dialog"` | the native dialog element |
| `"footer"` | the footer of the modal |
| `"header"` | the header of the modal |
| `"message"` | the message of the modal |
| `"wrapper"` | the modal wrapper overlay ! DEPRECATED IN FAVOR OF CONTAINER ! |
Expand Down
27 changes: 24 additions & 3 deletions packages/web-components/src/components/rux-modal/rux-modal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ rux-button-group {
box-shadow: var(--shadow-outer-dialog);
}

&__titlebar {
&__header {
display: flex;
flex-grow: 0;
flex-shrink: 0;
Expand All @@ -81,18 +81,33 @@ rux-button-group {
background-color: var(--color-background-surface-header);
color: var(--modal-title-color);
user-select: none;
cursor: move;
font-family: var(--font-heading-2-font-family);
font-size: var(--font-heading-2-font-size);
font-weight: var(--font-heading-2-font-weight);
letter-spacing: var(--font-heading-2-letter-spacing);
::slotted(h6),
::slotted(h5),
::slotted(h4),
::slotted(h3),
::slotted(h2),
::slotted(h1) {
margin-block-start: 0;
margin-block-end: 0;
}
}

&__content {
display: flex;
flex-direction: column;
flex-grow: 1;
padding: 1rem;
padding: 1rem 1rem 0 1rem;
color: var(--color-text-primary);
}
&__footer {
display: flex;
flex-direction: column;
flex-grow: 1;
padding: 0 1rem 1rem 1rem;
color: var(--color-text-primary);
}

Expand All @@ -102,6 +117,12 @@ rux-button-group {
.rux-button {
box-shadow: none !important;
}
&__header.hidden {
display: none;
}
&__message.hidden {
display: none;
}
}

rux-icon {
Expand Down
165 changes: 114 additions & 51 deletions packages/web-components/src/components/rux-modal/rux-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import {
Listen,
Watch,
Host,
State,
Fragment,
} from '@stencil/core'
import { hasSlot } from '../../utils/utils'

/**
* @part wrapper - the modal wrapper overlay ! DEPRECATED IN FAVOR OF CONTAINER !
Expand All @@ -18,6 +21,11 @@ import {
* @part message - the message of the modal
* @part confirm-button - the modal's confirm button
* @part deny-button - the modal's deny button
* @part footer - the footer of the modal
*
* @slot header - the header of the modal
* @slot (default) - the modal's message or content
* @slot footer - the footer of the modal
*/
@Component({
tag: 'rux-modal',
Expand Down Expand Up @@ -45,6 +53,15 @@ export class RuxModal {
* Text for close button
*/
@Prop() denyText: string = 'Cancel'
/**
* Event that is fired when modal opens
*/
@Event({
eventName: 'ruxmodalopened',
composed: true,
bubbles: true,
})
ruxModalOpened!: EventEmitter<void>
/**
* Event that is fired when modal closes
*/
Expand All @@ -57,6 +74,10 @@ export class RuxModal {

@Element() element!: HTMLRuxModalElement

@State() hasFooter = hasSlot(this.element, 'footer')
@State() hasHeader = hasSlot(this.element, 'header')
@State() hasMessage = hasSlot(this.element)

// confirm dialog if Enter key is pressed
@Listen('keydown', { target: 'window' })
handleKeyDown(ev: KeyboardEvent) {
Expand All @@ -73,26 +94,24 @@ export class RuxModal {
handleClick(ev: MouseEvent) {
const wrapper = this._getWrapper()
if (ev.composedPath()[0] === wrapper) {
this.ruxModalClosed.emit(false)
this.open = false
}
}

@Watch('open')
validateName(isOpen: boolean) {
if (isOpen) {
handleOpen(isOpen: boolean) {
if (isOpen && !this.hasFooter) {
setTimeout(() => {
const button = this._getDefaultButton()
button && button.focus()
})
if (button) {
button.focus()
}
}, 0)
}
this.open ? this.ruxModalOpened.emit() : this.ruxModalClosed.emit()
}

private _handleModalChoice(e: MouseEvent) {
// convert string value to boolean
const target = e.currentTarget as HTMLElement
const choice = target.dataset.value === 'true'
this.ruxModalClosed.emit(choice)
private _handleModalChoice() {
this.open = false
}

Expand All @@ -103,7 +122,9 @@ export class RuxModal {

if (buttonSet.length > 0) {
const defaultButton = buttonSet[buttonSet.length - 1]
return defaultButton
const shadow = defaultButton.shadowRoot?.querySelector('button')

if (shadow) return shadow
}

return null
Expand All @@ -121,18 +142,19 @@ export class RuxModal {
}

connectedCallback() {
setTimeout(() => {
const button = this._getDefaultButton()
button && button.focus()
})
this._handleModalChoice = this._handleModalChoice.bind(this)
this._handleSlotChange = this._handleSlotChange.bind(this)
}

componentDidLoad() {
setTimeout(() => {
const button = this._getDefaultButton()
button && button.focus()
})
componentDidRender() {
const button = this._getDefaultButton()
button && button.focus()
}

private _handleSlotChange() {
this.hasHeader = hasSlot(this.element, 'header')
this.hasMessage = hasSlot(this.element)
this.hasFooter = hasSlot(this.element, 'footer')
}

render() {
Expand All @@ -154,40 +176,81 @@ export class RuxModal {
role="dialog"
part="dialog"
>
{modalTitle && (
<header
class="rux-modal__titlebar"
part="header"
<header
class={{
hidden:
!this.hasHeader &&
modalTitle === undefined,
'rux-modal__header': true,
}}
part="header"
>
<slot
name="header"
onSlotchange={this._handleSlotChange}
>
{modalTitle}
</slot>
</header>

<div class="rux-modal__content" part="message">
<div
class={{
hidden:
!this.hasMessage &&
modalMessage === undefined,
'rux-modal__message': true,
}}
part="message"
>
<div>{modalTitle}</div>
</header>
)}
<div class="rux-modal__content">
<div class="rux-modal__message" part="message">
{modalMessage}
<slot onSlotchange={this._handleSlotChange}>
{modalMessage}
</slot>
</div>
<rux-button-group h-align="right">
<rux-button
secondary={confirmText.length > 0}
onClick={_handleModalChoice}
data-value="false"
hidden={!denyText}
tabindex="-1"
exportparts="container:deny-button"
>
{denyText}
</rux-button>
<rux-button
onClick={_handleModalChoice}
data-value="true"
hidden={!confirmText}
tabindex="0"
exportparts="container:confirm-button"
>
{confirmText}
</rux-button>
</rux-button-group>
</div>
<footer
class={{
'rux-modal__footer': true,
}}
part="footer"
>
{this.hasFooter ? (
<slot
name="footer"
onSlotchange={this._handleSlotChange}
></slot>
) : (
<Fragment>
<rux-button-group h-align="right">
<rux-button
secondary={
confirmText.length > 0
}
onClick={_handleModalChoice}
hidden={!denyText}
tabindex="-1"
exportparts="container:deny-button"
>
{denyText}
</rux-button>
<rux-button
onClick={_handleModalChoice}
hidden={!confirmText}
tabindex="0"
exportparts="container:confirm-button"
>
{confirmText}
</rux-button>
</rux-button-group>
<slot
name="footer"
onSlotchange={
this._handleSlotChange
}
></slot>
</Fragment>
)}
</footer>
</dialog>
</div>
</Host>
Expand Down
Loading

0 comments on commit 7fba14e

Please sign in to comment.