diff --git a/demo/partials/chips.html b/demo/partials/chips.html index 525e898..f3f4b9a 100644 --- a/demo/partials/chips.html +++ b/demo/partials/chips.html @@ -2,7 +2,7 @@

Chips

Default chips

-
+
Label Primary Secondary @@ -10,14 +10,14 @@

Selectable chips

-
- - - +
+ + +

Small chips

-
+
Small Tiny
@@ -40,7 +40,7 @@ return { chips, remove }; }, template: ` -
+
{{ chip.label }} diff --git a/public/assets/avatar-placeholder.jpg b/public/assets/avatar-placeholder.jpg new file mode 100644 index 0000000..68b05a9 --- /dev/null +++ b/public/assets/avatar-placeholder.jpg Binary files differ diff --git a/public/assets/avatar-placeholder.png b/public/assets/avatar-placeholder.png deleted file mode 100644 index f332fbc..0000000 --- a/public/assets/avatar-placeholder.png +++ /dev/null Binary files differ diff --git a/src/scss/components/_chips.scss b/src/scss/components/_chips.scss index 49287e3..748e810 100644 --- a/src/scss/components/_chips.scss +++ b/src/scss/components/_chips.scss @@ -10,29 +10,32 @@ align-items: center; justify-content: center; gap: $space-1; - min-height: 28px; + min-height: 30px; padding: $space-1 $space-3; font-family: $font-family-base; - font-size: 13px; - font-weight: $font-weight-medium; + font-size: $font-size-xs; + font-weight: $font-weight-semibold; line-height: 1; - text-transform: none; + text-transform: uppercase; white-space: nowrap; border-radius: $radius-full; - background-color: $neutral-100; - color: $neutral-900; + border: $border-width-base $border-style-base $border-color-muted; + background-color: $neutral-0; + color: $neutral-700; transition: background-color $motion-base $motion-ease, + border-color $motion-base $motion-ease, color $motion-base $motion-ease; .chip-remove { display: inline-flex; align-items: center; justify-content: center; - width: 16px; - height: 16px; + width: $icon-size-sm; + height: $icon-size-sm; padding: 0; margin-left: $space-1; + margin-right: -$space-1; border: 0; border-radius: 50%; background: transparent; @@ -52,23 +55,45 @@ // Variants &.chip-primary { - background-color: $neutral-900; - color: $neutral-25; + color: $neutral-900; + background-color: $neutral-100; + border-color: $neutral-900; } &.chip-secondary { - background-color: $mint-300; - color: $neutral-900; + color: $mint-800; + background-color: $mint-50; + border-color: $mint-600; } &.chip-accent { - background-color: $yellow-300; color: $neutral-900; + background-color: $yellow-100; + border-color: $yellow-400; + } + + &.chip-success { + color: $success-text; + background-color: $success-bg; + border-color: $success-text; + } + + &.chip-warning { + color: $warning-text; + background-color: $warning-bg; + border-color: $warning-text; + } + + &.chip-danger, + &.chip-error { + color: $danger-text; + background-color: $danger-bg; + border-color: $danger-text; } &.chip-outline { background-color: transparent; - border: $border-width-base $border-style-base $neutral-300; + border-color: $neutral-300; color: $neutral-700; &:hover { @@ -77,6 +102,36 @@ } } + // Selectable / pressed state + &.chip-selected, + &[aria-pressed="true"], + &[aria-selected="true"] { + color: $neutral-0; + background-color: $neutral-900; + border-color: $neutral-900; + + &.chip-secondary { + color: $neutral-0; + background-color: $mint-600; + border-color: $mint-600; + } + + &.chip-accent { + color: $neutral-900; + background-color: $yellow-400; + border-color: $yellow-400; + } + } + + &.chip-disabled, + &:disabled { + color: $neutral-500; + background-color: $neutral-100; + border-color: $border-color-muted; + cursor: not-allowed; + opacity: 0.7; + } + // Sizes &.chip-sm { min-height: 22px; @@ -96,6 +151,21 @@ } } +button.chip, +a.chip { + cursor: pointer; + + &:focus-visible { + @include focus_ring; + } + + &:hover:not(:disabled):not(.chip-disabled) { + color: $neutral-900; + background-color: $neutral-100; + border-color: $neutral-400; + } +} + .chip-group { display: flex; flex-wrap: wrap; diff --git a/src/vue/components/GnToastProvider.js b/src/vue/components/GnToastProvider.js index 7bb868a..0634f0d 100644 --- a/src/vue/components/GnToastProvider.js +++ b/src/vue/components/GnToastProvider.js @@ -28,41 +28,74 @@ const toast = ref(null); const closing = ref(false); const showing = ref(false); + const hovered = ref(false); let timer = null; let closeTimer = null; - let progressTimer = null; - const progress = ref(100); + let startedAt = 0; + let remaining = 0; + + const clearTimers = () => { + window.clearTimeout(timer); + window.clearTimeout(closeTimer); + timer = null; + closeTimer = null; + }; const dismiss = () => { - window.clearTimeout(closeTimer); - window.clearInterval(progressTimer); + clearTimers(); closing.value = true; showing.value = false; closeTimer = window.setTimeout(() => { toast.value = null; closing.value = false; - progress.value = 100; - window.clearTimeout(timer); - timer = null; }, 300); }; const close = () => { - window.clearTimeout(closeTimer); - window.clearTimeout(timer); - window.clearInterval(progressTimer); + clearTimers(); closing.value = false; showing.value = false; - progress.value = 100; + hovered.value = false; toast.value = null; + remaining = 0; + }; + + const scheduleClose = delay => { + clearTimers(); + startedAt = Date.now(); + remaining = delay; + timer = window.setTimeout(() => { + remaining = 0; + dismiss(); + }, delay); + }; + + const pause = () => { + if (!timer) return; + remaining = Math.max(0, remaining - (Date.now() - startedAt)); + window.clearTimeout(timer); + timer = null; + hovered.value = true; + }; + + const resume = () => { + hovered.value = false; + if (!toast.value || remaining <= 0) return; + scheduleClose(remaining); }; const show = options => { - window.clearTimeout(closeTimer); - window.clearInterval(progressTimer); + const alone = options.alone !== undefined ? options.alone : true; + if (alone && toast.value) { + dismiss(); + } + + clearTimers(); closing.value = false; showing.value = false; - progress.value = 100; + hovered.value = false; + remaining = 0; + const variant = normalizeVariant(options.variant || options.type || "info", "info"); const lifetime = options.lifetime !== undefined ? options.lifetime : props.lifetime; toast.value = { @@ -74,20 +107,8 @@ lifetime }; - window.clearTimeout(timer); - - if (lifetime !== 0) { - const step = 100; - const totalSteps = lifetime / step; - progress.value = 100; - progressTimer = window.setInterval(() => { - progress.value -= 100 / totalSteps; - if (progress.value <= 0) { - window.clearInterval(progressTimer); - } - }, step); - - timer = window.setTimeout(dismiss, lifetime); + if (lifetime > 0) { + scheduleClose(lifetime); } nextTick(() => { @@ -125,7 +146,9 @@ slots.default?.(), toast.value && h("div", { class: cx("toast", toastClass(), `toast-${toast.value.variant}`), - role: "alert" + role: "alert", + onMouseenter: pause, + onMouseleave: resume }, [ h("div", { class: "toast-content" }, [ h("div", { class: "toast-header" }, [ @@ -138,12 +161,12 @@ class: "btn-icon toast-close", type: "button", "aria-label": "Close", - onClick: dismiss + onClick: close }, [iconNode("ph-x")]), - toast.value.lifetime !== 0 && h("div", { class: "toast-progress" }, [ + toast.value.lifetime > 0 && h("div", { class: "toast-progress" }, [ h("div", { class: "toast-progress-bar", - style: { transform: `scaleX(${Math.max(0, progress.value / 100)})` } + style: { animationDuration: `${toast.value.lifetime}ms` } }) ]) ])