kopia lustrzana https://github.com/shoelace-style/shoelace
Porównaj commity
7 Commity
ff4da89ed7
...
17b5386ddc
Autor | SHA1 | Data |
---|---|---|
RoCat | 17b5386ddc | |
Konnor Rogers | 64996b2d35 | |
Susanne Kirchner | 0daa5d8dee | |
Konnor Rogers | 16d5575307 | |
Konnor Rogers | a427433701 | |
Danny Andrews | c6da4f5b14 | |
rcatoio | c4856756b9 |
|
@ -247,6 +247,69 @@ const App = () => {
|
|||
};
|
||||
```
|
||||
|
||||
### Countdown
|
||||
|
||||
Set the `countdown` attribute to display a loading bar that indicates the alert remaining time. This is useful for alerts with relatively long duration.
|
||||
|
||||
```html:preview
|
||||
<div class="alert-countdown">
|
||||
<sl-button variant="primary">Show Alert</sl-button>
|
||||
|
||||
<sl-alert variant="primary" duration="10000" countdown="RtL" closable>
|
||||
<sl-icon slot="icon" name="info-circle"></sl-icon>
|
||||
You're not stuck, the alert will close after a pretty long duration.
|
||||
</sl-alert>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const container = document.querySelector('.alert-countdown');
|
||||
const button = container.querySelector('sl-button');
|
||||
const alert = container.querySelector('sl-alert');
|
||||
|
||||
button.addEventListener('click', () => alert.show());
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.alert-countdown sl-alert {
|
||||
margin-top: var(--sl-spacing-medium);
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
```jsx:react
|
||||
import { useState } from 'react';
|
||||
import SlAlert from '@shoelace-style/shoelace/dist/react/alert';
|
||||
import SlButton from '@shoelace-style/shoelace/dist/react/button';
|
||||
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';
|
||||
|
||||
const css = `
|
||||
.alert-countdown sl-alert {
|
||||
margin-top: var(--sl-spacing-medium);
|
||||
}
|
||||
`;
|
||||
|
||||
const App = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="alert-countdown">
|
||||
<SlButton variant="primary" onClick={() => setOpen(true)}>
|
||||
Show Alert
|
||||
</SlButton>
|
||||
|
||||
<SlAlert variant="primary" duration="3000" countdown="RtL" open={open} closable onSlAfterHide={() => setOpen(false)}>
|
||||
<SlIcon slot="icon" name="info-circle" />
|
||||
You're not stuck, the alert will close after a pretty long duration.
|
||||
</SlAlert>
|
||||
</div>
|
||||
|
||||
<style>{css}</style>
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Toast Notifications
|
||||
|
||||
To display an alert as a toast notification, or "toast", create the alert and call its `toast()` method. This will move the alert out of its position in the DOM and into [the toast stack](#the-toast-stack) where it will be shown. Once dismissed, it will be removed from the DOM completely. To reuse a toast, store a reference to it and call `toast()` again later on.
|
||||
|
|
|
@ -236,7 +236,7 @@ When a `target` is set, the link will receive `rel="noreferrer noopener"` for [s
|
|||
|
||||
### Setting a Custom Width
|
||||
|
||||
As expected, buttons can be given a custom width by setting the `width` attribute. This is useful for making buttons span the full width of their container on smaller screens.
|
||||
As expected, buttons can be given a custom width by passing inline styles to the component (or using a class). This is useful for making buttons span the full width of their container on smaller screens.
|
||||
|
||||
```html:preview
|
||||
<sl-button variant="default" size="small" style="width: 100%; margin-bottom: 1rem;">Small</sl-button>
|
||||
|
|
|
@ -14,7 +14,10 @@ New versions of Shoelace are released as-needed and generally occur when a criti
|
|||
|
||||
## Next
|
||||
|
||||
- Fixed a bug in `<sl-split-panel>` that caused it not to recalculate it's position when going from being `display: none;` to its original display value. [#1942]
|
||||
- Fixed a bug in `<dialog>` where when it showed it would cause a layout shift. [#1967]
|
||||
- Fixed a bug in `<sl-tooltip>` that allowed unwanted text properties to leak in [#1947]
|
||||
- Fixed a bug in `<sl-button-group>` classes [#1974]
|
||||
|
||||
## 2.15.0
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import { getAnimation, setDefaultAnimation } from '../../utilities/animation-reg
|
|||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import { html } from 'lit';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
import { property, query, state } from 'lit/decorators.js';
|
||||
import { waitForEvent } from '../../internal/event.js';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
@ -45,11 +45,15 @@ export default class SlAlert extends ShoelaceElement {
|
|||
static dependencies = { 'sl-icon-button': SlIconButton };
|
||||
|
||||
private autoHideTimeout: number;
|
||||
private remainingTimeInterval: number;
|
||||
private countdownAnimation?: Animation;
|
||||
private readonly hasSlotController = new HasSlotController(this, 'icon', 'suffix');
|
||||
private readonly localize = new LocalizeController(this);
|
||||
|
||||
@query('[part~="base"]') base: HTMLElement;
|
||||
|
||||
@query('.alert__countdown-elapsed') countdownElement: HTMLElement;
|
||||
|
||||
/**
|
||||
* Indicates whether or not the alert is open. You can toggle this attribute to show and hide the alert, or you can
|
||||
* use the `show()` and `hide()` methods and this attribute will reflect the alert's open state.
|
||||
|
@ -69,14 +73,57 @@ export default class SlAlert extends ShoelaceElement {
|
|||
*/
|
||||
@property({ type: Number }) duration = Infinity;
|
||||
|
||||
/**
|
||||
* Enables a countdown that indicates the remaining time the alert will be displayed.
|
||||
* Typically used to indicate the remaining time before a whole app refresh.
|
||||
*/
|
||||
@property({ type: String, reflect: true }) countdown?: 'RtL' | 'LtR';
|
||||
|
||||
@state() private remainingTime = this.duration;
|
||||
|
||||
firstUpdated() {
|
||||
this.base.hidden = !this.open;
|
||||
}
|
||||
|
||||
private restartAutoHide() {
|
||||
this.handleCountdownChange();
|
||||
clearTimeout(this.autoHideTimeout);
|
||||
clearInterval(this.remainingTimeInterval);
|
||||
if (this.open && this.duration < Infinity) {
|
||||
this.autoHideTimeout = window.setTimeout(() => this.hide(), this.duration);
|
||||
this.remainingTime = this.duration;
|
||||
this.remainingTimeInterval = window.setInterval(() => {
|
||||
this.remainingTime -= 100;
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
private pauseAutoHide() {
|
||||
this.countdownAnimation?.pause();
|
||||
clearTimeout(this.autoHideTimeout);
|
||||
clearInterval(this.remainingTimeInterval);
|
||||
}
|
||||
|
||||
private resumeAutoHide() {
|
||||
this.autoHideTimeout = window.setTimeout(() => this.hide(), this.remainingTime);
|
||||
this.remainingTimeInterval = window.setInterval(() => {
|
||||
this.remainingTime -= 100;
|
||||
}, 100);
|
||||
this.countdownAnimation?.play();
|
||||
}
|
||||
|
||||
private handleCountdownChange() {
|
||||
if(this.open && this.duration < Infinity && this.countdown) {
|
||||
const { countdownElement } = this;
|
||||
const start = this.countdown === 'LtR' ? '0' : '100%';
|
||||
const end = this.countdown === 'LtR' ? '100%' : '0';
|
||||
this.countdownAnimation = countdownElement.animate([
|
||||
{ width: start },
|
||||
{ width: end }
|
||||
], {
|
||||
duration: this.duration,
|
||||
easing: 'linear'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,10 +131,6 @@ export default class SlAlert extends ShoelaceElement {
|
|||
this.hide();
|
||||
}
|
||||
|
||||
private handleMouseMove() {
|
||||
this.restartAutoHide();
|
||||
}
|
||||
|
||||
@watch('open', { waitUntilFirstUpdate: true })
|
||||
async handleOpenChange() {
|
||||
if (this.open) {
|
||||
|
@ -109,6 +152,7 @@ export default class SlAlert extends ShoelaceElement {
|
|||
this.emit('sl-hide');
|
||||
|
||||
clearTimeout(this.autoHideTimeout);
|
||||
clearInterval(this.remainingTimeInterval);
|
||||
|
||||
await stopAnimations(this.base);
|
||||
const { keyframes, options } = getAnimation(this, 'alert.hide', { dir: this.localize.dir() });
|
||||
|
@ -151,6 +195,7 @@ export default class SlAlert extends ShoelaceElement {
|
|||
*/
|
||||
async toast() {
|
||||
return new Promise<void>(resolve => {
|
||||
this.handleCountdownChange();
|
||||
if (toastStack.parentElement === null) {
|
||||
document.body.append(toastStack);
|
||||
}
|
||||
|
@ -188,6 +233,7 @@ export default class SlAlert extends ShoelaceElement {
|
|||
alert: true,
|
||||
'alert--open': this.open,
|
||||
'alert--closable': this.closable,
|
||||
'alert--has-countdown': !!this.countdown,
|
||||
'alert--has-icon': this.hasSlotController.test('icon'),
|
||||
'alert--primary': this.variant === 'primary',
|
||||
'alert--success': this.variant === 'success',
|
||||
|
@ -197,7 +243,8 @@ export default class SlAlert extends ShoelaceElement {
|
|||
})}
|
||||
role="alert"
|
||||
aria-hidden=${this.open ? 'false' : 'true'}
|
||||
@mousemove=${this.handleMouseMove}
|
||||
@mouseenter=${this.pauseAutoHide}
|
||||
@mouseleave=${this.resumeAutoHide}
|
||||
>
|
||||
<div part="icon" class="alert__icon">
|
||||
<slot name="icon"></slot>
|
||||
|
@ -220,6 +267,18 @@ export default class SlAlert extends ShoelaceElement {
|
|||
></sl-icon-button>
|
||||
`
|
||||
: ''}
|
||||
|
||||
<div role="timer" class="alert__timer">${this.remainingTime}</div>
|
||||
|
||||
${this.countdown ? html`
|
||||
<div class="alert__countdown">
|
||||
<div class=${classMap({
|
||||
'alert__countdown-elapsed': true,
|
||||
'alert__countdown-elapsed--rtl': this.countdown !== 'LtR'
|
||||
})}>
|
||||
</div>
|
||||
</div>
|
||||
`: ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ export default css`
|
|||
line-height: 1.6;
|
||||
color: var(--sl-color-neutral-700);
|
||||
margin: inherit;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.alert:not(.alert--has-icon) .alert__icon,
|
||||
|
@ -37,6 +38,10 @@ export default css`
|
|||
padding-inline-start: var(--sl-spacing-large);
|
||||
}
|
||||
|
||||
.alert--has-countdown {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.alert--primary {
|
||||
border-top-color: var(--sl-color-primary-600);
|
||||
}
|
||||
|
@ -91,4 +96,45 @@ export default css`
|
|||
font-size: var(--sl-font-size-medium);
|
||||
padding-inline-end: var(--sl-spacing-medium);
|
||||
}
|
||||
|
||||
.alert__countdown {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(var(--sl-panel-border-width) * 3);
|
||||
background-color: var(--sl-panel-border-color);
|
||||
}
|
||||
|
||||
.alert__countdown .alert__countdown-elapsed {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.alert__countdown .alert__countdown-elapsed--rtl {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.alert--primary .alert__countdown-elapsed {
|
||||
background-color: var(--sl-color-primary-600);
|
||||
}
|
||||
|
||||
.alert--success .alert__countdown-elapsed {
|
||||
background-color: var(--sl-color-success-600);
|
||||
}
|
||||
|
||||
.alert--neutral .alert__countdown-elapsed {
|
||||
background-color: var(--sl-color-neutral-600);
|
||||
}
|
||||
|
||||
.alert--warning .alert__countdown-elapsed {
|
||||
background-color: var(--sl-color-warning-600);
|
||||
}
|
||||
|
||||
.alert--danger .alert__countdown-elapsed {
|
||||
background-color: var(--sl-color-danger-600);
|
||||
}
|
||||
|
||||
.alert__timer {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -590,7 +590,7 @@ export default css`
|
|||
|
||||
/* Focus and checked are always on top */
|
||||
:host([data-sl-button-group__button--focus]),
|
||||
:host([data-sl-button-group__button[checked]]) {
|
||||
:host([data-sl-button-group__button][checked]) {
|
||||
z-index: 2;
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -189,6 +189,14 @@ export default class SlSplitPanel extends ShoelaceElement {
|
|||
const { width, height } = entries[0].contentRect;
|
||||
this.size = this.vertical ? height : width;
|
||||
|
||||
// There's some weird logic that gets `this.cachedPositionInPixels = NaN` or `this.position === Infinity` when
|
||||
// a split-panel goes from `display: none;` to showing.
|
||||
if (isNaN(this.cachedPositionInPixels) || this.position === Infinity) {
|
||||
this.cachedPositionInPixels = Number(this.getAttribute('position-in-pixels'));
|
||||
this.positionInPixels = Number(this.getAttribute('position-in-pixels'));
|
||||
this.position = this.pixelsToPercentage(this.positionInPixels);
|
||||
}
|
||||
|
||||
// Resize when a primary panel is set
|
||||
if (this.primary) {
|
||||
this.position = this.pixelsToPercentage(this.cachedPositionInPixels);
|
||||
|
|
|
@ -33,6 +33,19 @@ export function lockBodyScrolling(lockingEl: HTMLElement) {
|
|||
if (!document.documentElement.classList.contains('sl-scroll-lock')) {
|
||||
/** Scrollbar width + body padding calculation can go away once Safari has scrollbar-gutter support. */
|
||||
const scrollbarWidth = getScrollbarWidth() + getExistingBodyPadding(); // must be measured before the `sl-scroll-lock` class is applied
|
||||
|
||||
let scrollbarGutterProperty = getComputedStyle(document.documentElement).scrollbarGutter;
|
||||
|
||||
// default is auto, unsupported browsers is "undefined"
|
||||
if (!scrollbarGutterProperty || scrollbarGutterProperty === 'auto') {
|
||||
scrollbarGutterProperty = 'stable';
|
||||
}
|
||||
|
||||
if (scrollbarWidth <= 0) {
|
||||
// if there's no scrollbar, just set it to "revert" so whatever the user has set gets used. This is useful is the page is not overflowing and showing a scrollbar, or if the user has overflow: hidden, or any other reason a scrollbar may not be showing.
|
||||
scrollbarGutterProperty = 'revert';
|
||||
}
|
||||
document.documentElement.style.setProperty('--sl-scroll-lock-gutter', scrollbarGutterProperty);
|
||||
document.documentElement.classList.add('sl-scroll-lock');
|
||||
document.documentElement.style.setProperty('--sl-scroll-lock-size', `${scrollbarWidth}px`);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,10 @@
|
|||
|
||||
@supports (scrollbar-gutter: stable) {
|
||||
.sl-scroll-lock {
|
||||
scrollbar-gutter: stable !important;
|
||||
scrollbar-gutter: var(--sl-scroll-lock-gutter) !important;
|
||||
}
|
||||
|
||||
.sl-scroll-lock body {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue