@@ -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` }
})
])
])