From e36eccdbe0569d0af43992eeaea3764c0f9d793a Mon Sep 17 00:00:00 2001 From: Dan Cormier Date: Fri, 21 Nov 2025 10:17:02 -0500 Subject: [PATCH 01/29] feat(vote): add vote (btn(s)) component --- .../components/vote-button/vote-button.less | 72 +++++++++++++++++++ .../stacks-classic/lib/stacks-static.less | 1 + .../stacks-docs/_data/site-navigation.json | 4 ++ .../product/components/vote-button.html | 51 +++++++++++++ 4 files changed, 128 insertions(+) create mode 100644 packages/stacks-classic/lib/components/vote-button/vote-button.less create mode 100644 packages/stacks-docs/product/components/vote-button.html diff --git a/packages/stacks-classic/lib/components/vote-button/vote-button.less b/packages/stacks-classic/lib/components/vote-button/vote-button.less new file mode 100644 index 0000000000..7a281081a9 --- /dev/null +++ b/packages/stacks-classic/lib/components/vote-button/vote-button.less @@ -0,0 +1,72 @@ +.s-vote { + --_vb-fd: column; + // btn + --_vb-btn-bg: var(--black-150); + --_vb-btn-br: unset; + --_vb-btn-size: unset; + --_vb-btn-width: 100%; + --_vb-btn-padding: var(--su12) var(--su4); + // count + // --_vb-count-padding: var(--su4); + // --_vb-count-height: unset; + --_vb-count-width: 100%; + // --_vb-w: calc(var(--su48) + var(--su2)); // 50px + + &:not(&--horizontal) { + :first-child { + --_vb-btn-br: var(--br-pill) var(--br-pill) 0 0; + } + + :last-child { + --_vb-btn-br: 0 0 var(--br-pill) var(--br-pill); + } + } + + // MODIFIERS + &&__horizontal { + --_vb-fd: row; + } + + // Sizes + // TODO SHINE add size modifiers + + // CHILD ELEMENTS + button { + &:focus-visible { + .focus-styles(true, true); + } + &:hover { + --_vb-btn-bg: var(--black-200); + } + + all: unset; + cursor: pointer; + } + + & &--btn { + background-color: var(--_vb-btn-bg); + border-radius: var(--_vb-btn-br); + height: var(--_vb-btn-height); + padding: var(--_vb-btn-padding); + width: var(--_vb-btn-width); + + align-items: center; + display: inline-flex; + justify-content: center; + } + + & &--count { + padding: var(--_vb-count-padding); + + background-color: var(--black-150); + font-weight: 600; + text-align: center; + width: var(--_vb-count-width); + } + + align-items: stretch; + display: flex; + flex-direction: var(--_vb-fd); + justify-content: stretch; + width: var(--_vb-w); +} diff --git a/packages/stacks-classic/lib/stacks-static.less b/packages/stacks-classic/lib/stacks-static.less index 6079483265..474bf698be 100644 --- a/packages/stacks-classic/lib/stacks-static.less +++ b/packages/stacks-classic/lib/stacks-static.less @@ -53,6 +53,7 @@ @import "components/topbar/topbar.less"; @import "components/uploader/uploader.less"; @import "components/user-card/user-card.less"; +@import "components/vote-button/vote-button.less"; // LESS CONSTANTS AND MIXINS @import "exports/exports.less"; diff --git a/packages/stacks-docs/_data/site-navigation.json b/packages/stacks-docs/_data/site-navigation.json index c8fec1e6f7..3f8a1451bf 100644 --- a/packages/stacks-docs/_data/site-navigation.json +++ b/packages/stacks-docs/_data/site-navigation.json @@ -342,6 +342,10 @@ { "title": "User cards", "url": "/product/components/user-cards/" + }, + { + "title": "Vote button", + "url": "/product/components/vote-button/" } ] } diff --git a/packages/stacks-docs/product/components/vote-button.html b/packages/stacks-docs/product/components/vote-button.html new file mode 100644 index 0000000000..20d7ec5657 --- /dev/null +++ b/packages/stacks-docs/product/components/vote-button.html @@ -0,0 +1,51 @@ +--- +layout: page +title: Vote Button +description: tbd +figma: https://www.figma.com/design/do4Ug0Yws8xCfRjHe9cJfZ/Project-SHINE---Product-UI?node-id=617-18830&p=f&t=u4su1MPgebbjmcfI-0 +svelte: https://beta.svelte.stackoverflow.design/?path=/docs/components-vote-button--docs +tags: components +--- +
+ {% header "h2", "IDK" %} +

tbd

+ {% header "h3", "…" %} +

+
+{% highlight html %} +👋 +{% endhighlight %} +
+
+ + 0 + +
+
+ + 0 +
+
+ + 0 + +
+
+ + 0 +
+
+
+
From 1cc91030fdc8737087e0ac49721316b7f5d04f0a Mon Sep 17 00:00:00 2001 From: Dan Cormier Date: Fri, 21 Nov 2025 15:58:10 -0500 Subject: [PATCH 02/29] Rename to just "vote", better styles --- .../components/vote-button/vote-button.less | 72 ------------ .../lib/components/vote/vote.less | 105 ++++++++++++++++++ .../stacks-classic/lib/stacks-static.less | 2 +- .../stacks-docs/_data/site-navigation.json | 4 +- .../product/components/vote-button.html | 51 --------- .../stacks-docs/product/components/vote.html | 88 +++++++++++++++ 6 files changed, 196 insertions(+), 126 deletions(-) delete mode 100644 packages/stacks-classic/lib/components/vote-button/vote-button.less create mode 100644 packages/stacks-classic/lib/components/vote/vote.less delete mode 100644 packages/stacks-docs/product/components/vote-button.html create mode 100644 packages/stacks-docs/product/components/vote.html diff --git a/packages/stacks-classic/lib/components/vote-button/vote-button.less b/packages/stacks-classic/lib/components/vote-button/vote-button.less deleted file mode 100644 index 7a281081a9..0000000000 --- a/packages/stacks-classic/lib/components/vote-button/vote-button.less +++ /dev/null @@ -1,72 +0,0 @@ -.s-vote { - --_vb-fd: column; - // btn - --_vb-btn-bg: var(--black-150); - --_vb-btn-br: unset; - --_vb-btn-size: unset; - --_vb-btn-width: 100%; - --_vb-btn-padding: var(--su12) var(--su4); - // count - // --_vb-count-padding: var(--su4); - // --_vb-count-height: unset; - --_vb-count-width: 100%; - // --_vb-w: calc(var(--su48) + var(--su2)); // 50px - - &:not(&--horizontal) { - :first-child { - --_vb-btn-br: var(--br-pill) var(--br-pill) 0 0; - } - - :last-child { - --_vb-btn-br: 0 0 var(--br-pill) var(--br-pill); - } - } - - // MODIFIERS - &&__horizontal { - --_vb-fd: row; - } - - // Sizes - // TODO SHINE add size modifiers - - // CHILD ELEMENTS - button { - &:focus-visible { - .focus-styles(true, true); - } - &:hover { - --_vb-btn-bg: var(--black-200); - } - - all: unset; - cursor: pointer; - } - - & &--btn { - background-color: var(--_vb-btn-bg); - border-radius: var(--_vb-btn-br); - height: var(--_vb-btn-height); - padding: var(--_vb-btn-padding); - width: var(--_vb-btn-width); - - align-items: center; - display: inline-flex; - justify-content: center; - } - - & &--count { - padding: var(--_vb-count-padding); - - background-color: var(--black-150); - font-weight: 600; - text-align: center; - width: var(--_vb-count-width); - } - - align-items: stretch; - display: flex; - flex-direction: var(--_vb-fd); - justify-content: stretch; - width: var(--_vb-w); -} diff --git a/packages/stacks-classic/lib/components/vote/vote.less b/packages/stacks-classic/lib/components/vote/vote.less new file mode 100644 index 0000000000..f95df48ae8 --- /dev/null +++ b/packages/stacks-classic/lib/components/vote/vote.less @@ -0,0 +1,105 @@ +.s-vote { + --_vo-fd: column; + --_vo-child-bg: var(--black-150); + --_vo-child-br: unset; + --_vo-child-fd: var(--_vo-fd); + --_vo-child-g: calc(var(--su8) + var(--su2)); // 10px + --_vo-child-h: unset; + --_vo-child-w: calc(var(--su48) + var(--su2)); // 50px + --_vo-child-p: unset; + + // CHILD ELEMENTS + &:not(&__horizontal){ + :first-child { + --_vo-child-p: calc(var(--su12) + var(--su2)) 0 calc(var(--su12) - var(--su2)); // 14px 0 10px + --_vo-child-br: var(--br-pill) var(--br-pill) 0 0; + } + + :last-child { + --_vo-child-p: calc(var(--su12) - var(--su2)) 0 calc(var(--su12) + var(--su2)); // 10px 0 14px + --_vo-child-br: 0 0 var(--br-pill) var(--br-pill); + } + + :only-child { + --_vo-child-br: var(--br-pill); + --_vo-child-g: calc(var(--su16) + var(--su4)); // 18px + --_vo-child-p: calc(var(--su12) + var(--su2)) 0; // 14px 0 + } + } + + // MODIFIERS + &&__horizontal { + --_vo-fd: row; + --_vo-child-h: var(--su32); + --_vo-child-p: 0 var(--su4); + --_vo-child-w: unset; + + :first-child { + --_vo-child-p: 0 var(--su6) 0 calc(var(--su8) + var(--su2)); // 0 6px 0 10px + --_vo-child-br: var(--br-pill) 0 0 var(--br-pill); + } + + :last-child { + --_vo-child-p: 0 calc(var(--su8) + var(--su2)) 0 var(--su6); // 0 10px 0 6px + --_vo-child-br: 0 var(--br-pill) var(--br-pill) 0; + } + + .s-vote--count:last-child:not(:only-child) { + --_vo-child-p: 0 calc(var(--su12) + var(--su2)) 0 var(--su4); // 0 14px 0 4px + } + + :only-child { + --_vo-child-br: var(--br-pill); + --_vo-child-g: calc(var(--su8) + var(--su2)); // 10px + --_vo-child-p: 0 calc(var(--su12) + var(--su2)) 0 calc(var(--su8) + var(--su2)); // 0 10px + } + } + + // CHILD ELEMENTS + > button { + // Reset button styles + appearance: none; + -webkit-appearance: none; + background: none; + border: 0; + color: inherit; + cursor: pointer; + font: inherit; + margin: 0; + padding: 0; + } + + & &--btn, + & > &--count { + background-color: var(--_vo-child-bg); + border-radius: var(--_vo-child-br); + flex-direction: var(--_vo-child-fd); + gap: var(--_vo-child-g); + height: var(--_vo-child-h); + padding: var(--_vo-child-p); + width: var(--_vo-child-w); + + align-items: center; + display: inline-flex; + justify-content: center; + overflow: hidden; + font-weight: 600; + text-align: center; + white-space: nowrap; + } + + // INTERACTION + & &--btn { + &:focus-visible { + .focus-styles(true, false); + } + + &:hover { + --_vo-child-bg: var(--black-200); + } + } + + flex-direction: var(--_vo-fd); + + display: flex; +} diff --git a/packages/stacks-classic/lib/stacks-static.less b/packages/stacks-classic/lib/stacks-static.less index 474bf698be..0506c51c71 100644 --- a/packages/stacks-classic/lib/stacks-static.less +++ b/packages/stacks-classic/lib/stacks-static.less @@ -53,7 +53,7 @@ @import "components/topbar/topbar.less"; @import "components/uploader/uploader.less"; @import "components/user-card/user-card.less"; -@import "components/vote-button/vote-button.less"; +@import "components/vote/vote.less"; // LESS CONSTANTS AND MIXINS @import "exports/exports.less"; diff --git a/packages/stacks-docs/_data/site-navigation.json b/packages/stacks-docs/_data/site-navigation.json index 3f8a1451bf..fd156c58f2 100644 --- a/packages/stacks-docs/_data/site-navigation.json +++ b/packages/stacks-docs/_data/site-navigation.json @@ -344,8 +344,8 @@ "url": "/product/components/user-cards/" }, { - "title": "Vote button", - "url": "/product/components/vote-button/" + "title": "Vote", + "url": "/product/components/vote/" } ] } diff --git a/packages/stacks-docs/product/components/vote-button.html b/packages/stacks-docs/product/components/vote-button.html deleted file mode 100644 index 20d7ec5657..0000000000 --- a/packages/stacks-docs/product/components/vote-button.html +++ /dev/null @@ -1,51 +0,0 @@ ---- -layout: page -title: Vote Button -description: tbd -figma: https://www.figma.com/design/do4Ug0Yws8xCfRjHe9cJfZ/Project-SHINE---Product-UI?node-id=617-18830&p=f&t=u4su1MPgebbjmcfI-0 -svelte: https://beta.svelte.stackoverflow.design/?path=/docs/components-vote-button--docs -tags: components ---- -
- {% header "h2", "IDK" %} -

tbd

- {% header "h3", "…" %} -

-
-{% highlight html %} -👋 -{% endhighlight %} -
-
- - 0 - -
-
- - 0 -
-
- - 0 - -
-
- - 0 -
-
-
-
diff --git a/packages/stacks-docs/product/components/vote.html b/packages/stacks-docs/product/components/vote.html new file mode 100644 index 0000000000..8a60c92ee9 --- /dev/null +++ b/packages/stacks-docs/product/components/vote.html @@ -0,0 +1,88 @@ +--- +layout: page +title: Vote +description: tbd +figma: https://www.figma.com/design/do4Ug0Yws8xCfRjHe9cJfZ/Project-SHINE---Product-UI?node-id=617-18830&p=f&t=u4su1MPgebbjmcfI-0 +svelte: https://beta.svelte.stackoverflow.design/?path=/docs/components-vote--docs +tags: components +--- +
+ {% header "h2", "IDK" %} +

tbd

+ {% header "h3", "The golden path" %} +
+{% highlight html %} +👋 +{% endhighlight %} +
+
+ Vertical 2-btn +
+ + 12 + +
+
+
+ Horizontal 2-btn +
+ + 5 + +
+
+
+ Horizontal 1-btn joint +
+ +
+
+
+
+ {% header "h3", "Experiments" %} +
+{% highlight html %} +🤔 +{% endhighlight %} +
+
+ Vertical 1-btn split +
+ + 12 +
+
+
+ Vertical 1-btn joint +
+ +
+
+
+ Horizontal 1-btn split +
+ + 5 +
+
+
+
+
From e31cb6c6eafdddc204235d76f7038918544d05c2 Mon Sep 17 00:00:00 2001 From: Dan Cormier Date: Tue, 2 Dec 2025 17:22:00 -0500 Subject: [PATCH 03/29] Add expanded modifier, positive/negative count styles --- .../lib/components/vote/vote.less | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/stacks-classic/lib/components/vote/vote.less b/packages/stacks-classic/lib/components/vote/vote.less index f95df48ae8..9f6ca70d98 100644 --- a/packages/stacks-classic/lib/components/vote/vote.less +++ b/packages/stacks-classic/lib/components/vote/vote.less @@ -28,6 +28,21 @@ } // MODIFIERS + &&__expanded { + --_vo-child-g: var(--su2); + --_vo-child-p: 0; + + .s-vote { + &--neutral { + display: none; + } + &--positive, + &--negative { + display: block; + } + } + } + &&__horizontal { --_vo-fd: row; --_vo-child-h: var(--su32); @@ -88,7 +103,21 @@ white-space: nowrap; } + & &--positive, + & &--negative { + display: none; + } + + & &--positive { + color: var(--green-500); + } + + & &--negative { + color: var(--red-500); + } + // INTERACTION + > button, & &--btn { &:focus-visible { .focus-styles(true, false); From e85689c56fa45fd993455253b6653b20d108d290 Mon Sep 17 00:00:00 2001 From: Dan Cormier Date: Tue, 2 Dec 2025 17:22:09 -0500 Subject: [PATCH 04/29] Update docs --- packages/stacks-docs/_data/vote.json | 110 ++++++ .../stacks-docs/product/components/vote.html | 327 ++++++++++++++---- 2 files changed, 373 insertions(+), 64 deletions(-) create mode 100644 packages/stacks-docs/_data/vote.json diff --git a/packages/stacks-docs/_data/vote.json b/packages/stacks-docs/_data/vote.json new file mode 100644 index 0000000000..953aef5ec5 --- /dev/null +++ b/packages/stacks-docs/_data/vote.json @@ -0,0 +1,110 @@ +{ + "classes": [ + { + "class": ".s-vote", + "applies": "N/A", + "description": "tbd" + }, + { + "class": ".s-vote__expanded", + "applies": ".s-vote", + "description": "tbd" + }, + { + "class": ".s-vote__horizontal", + "applies": ".s-vote", + "description": "tbd" + }, + { + "class": ".s-vote--btn", + "applies": ".s-vote", + "description": "tbd" + }, + { + "class": ".s-vote--count", + "applies": ".s-vote", + "description": "tbd" + }, + { + "class": ".s-vote--positive", + "applies": ".s-vote--count", + "description": "tbd" + }, + { + "class": ".s-vote--neutral", + "applies": ".s-vote--count", + "description": "tbd" + }, + { + "class": ".s-vote--negative", + "applies": ".s-vote--count", + "description": "tbd" + } + ], + "groups": { + "base": [ + { + "description": "Base", + "count": "12", + "upvote": true, + "downvote": true + }, + { + "description": "0 vote count", + "count": "Vote", + "upvote": true, + "downvote": true + }, + { + "description": "≥ 1,000 votes", + "count": "27.5K", + "upvote": true, + "downvote": true + } + ], + "expanded": [ + { + "description": "Expanded vote count", + "count": "20", + "expanded": true, + "upvote": true, + "downvote": true, + "positive": "+12", + "negative": "-8" + } + ], + "horizontal": [ + { + "description": "tbd", + "count": "5", + "horizontal": true, + "upvote": true, + "downvote": false + } + ], + "voted": [ + { + "description": "tbd", + "count": "27.5K", + "upvote": true, + "downvote": true, + "upvoted": true + }, + { + "description": "tbd", + "count": "11", + "upvote": true, + "downvote": true, + "downvoted": true + }, + { + "description": "tbd", + "count": "5", + "horizontal": true, + "upvote": true, + "downvote": false, + "upvoted": true + } + ] + } +} \ No newline at end of file diff --git a/packages/stacks-docs/product/components/vote.html b/packages/stacks-docs/product/components/vote.html index 8a60c92ee9..933a713bef 100644 --- a/packages/stacks-docs/product/components/vote.html +++ b/packages/stacks-docs/product/components/vote.html @@ -6,83 +6,282 @@ svelte: https://beta.svelte.stackoverflow.design/?path=/docs/components-vote--docs tags: components --- +
- {% header "h2", "IDK" %} -

tbd

- {% header "h3", "The golden path" %} + {% header "h2", "Classes" %} +
+ + + + + + + + + + {% for item in vote.classes %} + + + + + + {% endfor %} + +
ClassParentDescription
{{ item.class }}{{ item.applies }}{{ item.description }}
+
+
+ +
+ {% header "h2", "Examples" %} +

+ +

+ + {% header "h3", "Base" %} +

+ +

+
{% highlight html %} -👋 +
+ + + 12 + + +
{% endhighlight %} -
-
- Vertical 2-btn -
- - 12 - -
-
-
- Horizontal 2-btn -
- - 5 - +
+ {% for vote in vote.groups.base %} + {% assign classList = "s-vote" %} + {% if vote.horizontal %} + {% assign classList = classList | append: " s-vote__horizontal" %} + {% endif %} + {% if vote.expanded %} + {% assign classList = classList | append: " s-vote__expanded" %} + {% endif %} +
+ {{ vote.description }} +
+ + + {%if vote.positive %} + {{ vote.positive }} + {% endif %} + {{ vote.count }} + {%if vote.negative %} + {{ vote.negative }} + {% endif %} + + {% if vote.downvote %} + + {% endif %} +
-
-
- Horizontal 1-btn joint -
- -
-
+ {% endfor %}
- {% header "h3", "Experiments" %} + + {% header "h3", "Expanded" %} +

+ +

+
{% highlight html %} -🤔 +
+ + + 12 + 20 + 8 + + +
{% endhighlight %} -
-
- Vertical 1-btn split -
- - 12 +
+ {% for vote in vote.groups.expanded %} + {% assign classList = "s-vote" %} + {% if vote.horizontal %} + {% assign classList = classList | append: " s-vote__horizontal" %} + {% endif %} + {% if vote.expanded %} + {% assign classList = classList | append: " s-vote__expanded" %} + {% endif %} +
+ {{ vote.description }} +
+ + + {%if vote.positive %} + {{ vote.positive }} + {% endif %} + {{ vote.count }} + {%if vote.negative %} + {{ vote.negative }} + {% endif %} + + {% if vote.downvote %} + + {% endif %} +
-
-
- Vertical 1-btn joint -
- + {% endfor %} +
+
+ + {% header "h3", "Horizontal" %} +

+ +

+ +
+{% highlight html %} +
+ + + 12 + +
+{% endhighlight %} +
+ {% for vote in vote.groups.horizontal %} + {% assign classList = "s-vote" %} + {% if vote.horizontal %} + {% assign classList = classList | append: " s-vote__horizontal" %} + {% endif %} + {% if vote.expanded %} + {% assign classList = classList | append: " s-vote__expanded" %} + {% endif %} +
+ {{ vote.description }} +
+ + + {%if vote.positive %} + {{ vote.positive }} + {% endif %} + {{ vote.count }} + {%if vote.negative %} + {{ vote.negative }} + {% endif %} + + {% if vote.downvote %} + + {% endif %} +
-
-
- Horizontal 1-btn split -
- - 5 + {% endfor %} +
+
+ + + {% header "h3", "Voted" %} +

+ +

+ +
+{% highlight html %} +
+ + + 12 + + +
+{% endhighlight %} +
+ {% for vote in vote.groups.voted %} + {% assign classList = "s-vote" %} + {% if vote.horizontal %} + {% assign classList = classList | append: " s-vote__horizontal" %} + {% endif %} + {% if vote.expanded %} + {% assign classList = classList | append: " s-vote__expanded" %} + {% endif %} +
+ {{ vote.description }} +
+ + + {%if vote.positive %} + {{ vote.positive }} + {% endif %} + {{ vote.count }} + {%if vote.negative %} + {{ vote.negative }} + {% endif %} + + {% if vote.downvote %} + + {% endif %} +
-
+ {% endfor %}
From dba7ef204d56b9378e68cc0ea2bcb1ae213505b2 Mon Sep 17 00:00:00 2001 From: Dan Cormier Date: Tue, 2 Dec 2025 17:22:17 -0500 Subject: [PATCH 05/29] Add basic tests --- .../lib/components/vote/tag.a11y.test.ts | 55 +++++++++++++++++++ .../lib/components/vote/tag.visual.test.ts | 55 +++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 packages/stacks-classic/lib/components/vote/tag.a11y.test.ts create mode 100644 packages/stacks-classic/lib/components/vote/tag.visual.test.ts diff --git a/packages/stacks-classic/lib/components/vote/tag.a11y.test.ts b/packages/stacks-classic/lib/components/vote/tag.a11y.test.ts new file mode 100644 index 0000000000..da7a197cd1 --- /dev/null +++ b/packages/stacks-classic/lib/components/vote/tag.a11y.test.ts @@ -0,0 +1,55 @@ +import { Chevron16Up, Chevron16Down } from "@stackoverflow/stacks-icons"; +import { runA11yTests } from "../../test/a11y-test-utils"; +import "../../index"; + +const children = { + default: ` + + + +20 + 12 + -8 + + + `, + noVotes: ` + + + Vote + + + `, + noDownvote: ` + + + +20 + 12 + -8 + + ` +}; + +describe("vote", () => { + runA11yTests({ + baseClass: "s-vote", + variants: ["expanded"], + children: { + default: children.default, + noVotes: children.noVotes, + // upvoted: children.upvoted, // TODO SHINE add upvoted + // noDownvote: children.noDownvote, // TODO SHINE add no downvote + }, + }); + + runA11yTests({ + baseClass: "s-vote", + variants: ["horizontal"], + children: { + default: children.noDownvote, + // upvoted: children.noDownvoteUpvoted, // TODO SHINE add upvoted + }, + options: { + includeNullModifier: false, + }, + }); +}); diff --git a/packages/stacks-classic/lib/components/vote/tag.visual.test.ts b/packages/stacks-classic/lib/components/vote/tag.visual.test.ts new file mode 100644 index 0000000000..c50d5529ba --- /dev/null +++ b/packages/stacks-classic/lib/components/vote/tag.visual.test.ts @@ -0,0 +1,55 @@ +import { Chevron16Up, Chevron16Down } from "@stackoverflow/stacks-icons"; +import { runVisualTests } from "../../test/visual-test-utils"; +import "../../index"; + +const children = { + default: ` + + + +20 + 12 + -8 + + + `, + noVotes: ` + + + Vote + + + `, + noDownvote: ` + + + +20 + 12 + -8 + + ` +}; + +describe("vote", () => { + runVisualTests({ + baseClass: "s-vote", + variants: ["expanded"], + children: { + default: children.default, + noVotes: children.noVotes, + // upvoted: children.upvoted, // TODO SHINE add upvoted + // noDownvote: children.noDownvote, // TODO SHINE add no downvote + }, + }); + + runVisualTests({ + baseClass: "s-vote", + variants: ["horizontal"], + children: { + default: children.noDownvote, + // upvoted: children.noDownvoteUpvoted, // TODO SHINE add upvoted + }, + options: { + includeNullModifier: false, + }, + }); +}); From ba910b06ec4b9cf54d6645272ac31bcf88211a39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 10:13:32 -0500 Subject: [PATCH 06/29] build(deps-dev): bump stylelint-config-standard from 38.0.0 to 39.0.1 (#2004) Bumps [stylelint-config-standard](https://github.com/stylelint/stylelint-config-standard) from 38.0.0 to 39.0.1. - [Release notes](https://github.com/stylelint/stylelint-config-standard/releases) - [Changelog](https://github.com/stylelint/stylelint-config-standard/blob/main/CHANGELOG.md) - [Commits](https://github.com/stylelint/stylelint-config-standard/compare/38.0.0...39.0.1) --- updated-dependencies: - dependency-name: stylelint-config-standard dependency-version: 39.0.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 35 +++++++++++++++++++++++++++++------ package.json | 2 +- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 55f47d4c76..8c79f9e5ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -75,7 +75,7 @@ "storybook": "^9.1.5", "stylelint": "^16.25.0", "stylelint-config-recommended": "^16.0.0", - "stylelint-config-standard": "^38.0.0", + "stylelint-config-standard": "^39.0.1", "svelte": "^5.39.11", "svelte-check": "^4.3.3", "svelte-preprocess": "^6.0.3", @@ -15600,9 +15600,9 @@ } }, "node_modules/stylelint-config-standard": { - "version": "38.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-38.0.0.tgz", - "integrity": "sha512-uj3JIX+dpFseqd/DJx8Gy3PcRAJhlEZ2IrlFOc4LUxBX/PNMEQ198x7LCOE2Q5oT9Vw8nyc4CIL78xSqPr6iag==", + "version": "39.0.1", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-39.0.1.tgz", + "integrity": "sha512-b7Fja59EYHRNOTa3aXiuWnhUWXFU2Nfg6h61bLfAb5GS5fX3LMUD0U5t4S8N/4tpHQg3Acs2UVPR9jy2l1g/3A==", "dev": true, "funding": [ { @@ -15616,13 +15616,36 @@ ], "license": "MIT", "dependencies": { - "stylelint-config-recommended": "^16.0.0" + "stylelint-config-recommended": "^17.0.0" }, "engines": { "node": ">=18.12.0" }, "peerDependencies": { - "stylelint": "^16.18.0" + "stylelint": "^16.23.0" + } + }, + "node_modules/stylelint-config-standard/node_modules/stylelint-config-recommended": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-17.0.0.tgz", + "integrity": "sha512-WaMSdEiPfZTSFVoYmJbxorJfA610O0tlYuU2aEwY33UQhSPgFbClrVJYWvy3jGJx+XW37O+LyNLiZOEXhKhJmA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "stylelint": "^16.23.0" } }, "node_modules/stylelint/node_modules/balanced-match": { diff --git a/package.json b/package.json index 41f09859d0..424cfe51c9 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "storybook": "^9.1.5", "stylelint": "^16.25.0", "stylelint-config-recommended": "^16.0.0", - "stylelint-config-standard": "^38.0.0", + "stylelint-config-standard": "^39.0.1", "svelte": "^5.39.11", "svelte-check": "^4.3.3", "svelte-preprocess": "^6.0.3", From ac1d74ec521ca2aee17d195d0418a706eaefdd87 Mon Sep 17 00:00:00 2001 From: Dan Cormier Date: Tue, 2 Dec 2025 17:37:41 -0500 Subject: [PATCH 07/29] Better, but not perfect, vote icons --- .../stacks-docs/product/components/vote.html | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/stacks-docs/product/components/vote.html b/packages/stacks-docs/product/components/vote.html index 933a713bef..be3f917a9d 100644 --- a/packages/stacks-docs/product/components/vote.html +++ b/packages/stacks-docs/product/components/vote.html @@ -70,9 +70,9 @@
@@ -87,9 +87,9 @@ {% if vote.downvote %} {% endif %} @@ -134,9 +134,9 @@
@@ -151,9 +151,9 @@ {% if vote.downvote %} {% endif %} @@ -193,9 +193,9 @@
@@ -210,9 +210,9 @@ {% if vote.downvote %} {% endif %} @@ -256,9 +256,9 @@
@@ -273,9 +273,9 @@ {% if vote.downvote %} {% endif %} From 1d1fc916d526f40147eafbb4835318302d0fae02 Mon Sep 17 00:00:00 2001 From: Dan Cormier Date: Tue, 2 Dec 2025 18:04:58 -0500 Subject: [PATCH 08/29] Add basic Svelte component --- .../stacks-docs/product/components/vote.html | 1 + .../src/components/Vote/Vote.stories.svelte | 15 +++ .../src/components/Vote/Vote.svelte | 100 ++++++++++++++++++ .../src/components/Vote/Vote.test.ts | 9 ++ 4 files changed, 125 insertions(+) create mode 100644 packages/stacks-svelte/src/components/Vote/Vote.stories.svelte create mode 100644 packages/stacks-svelte/src/components/Vote/Vote.svelte create mode 100644 packages/stacks-svelte/src/components/Vote/Vote.test.ts diff --git a/packages/stacks-docs/product/components/vote.html b/packages/stacks-docs/product/components/vote.html index be3f917a9d..cac8f5af04 100644 --- a/packages/stacks-docs/product/components/vote.html +++ b/packages/stacks-docs/product/components/vote.html @@ -7,6 +7,7 @@ tags: components --- +
{% header "h2", "Classes" %}
diff --git a/packages/stacks-svelte/src/components/Vote/Vote.stories.svelte b/packages/stacks-svelte/src/components/Vote/Vote.stories.svelte new file mode 100644 index 0000000000..e000cab488 --- /dev/null +++ b/packages/stacks-svelte/src/components/Vote/Vote.stories.svelte @@ -0,0 +1,15 @@ + + + + {#snippet template(args)} + + {/snippet} + diff --git a/packages/stacks-svelte/src/components/Vote/Vote.svelte b/packages/stacks-svelte/src/components/Vote/Vote.svelte new file mode 100644 index 0000000000..04f0ba5d23 --- /dev/null +++ b/packages/stacks-svelte/src/components/Vote/Vote.svelte @@ -0,0 +1,100 @@ + + +
+ + + {#if positive} + {positive} + {/if} + {count} + {#if negative} + {negative} + {/if} + + {#if !horizontal} + + {/if} +
diff --git a/packages/stacks-svelte/src/components/Vote/Vote.test.ts b/packages/stacks-svelte/src/components/Vote/Vote.test.ts new file mode 100644 index 0000000000..ffd3b775a0 --- /dev/null +++ b/packages/stacks-svelte/src/components/Vote/Vote.test.ts @@ -0,0 +1,9 @@ +// import { expect } from "@open-wc/testing"; +// import { render, screen } from "@testing-library/svelte"; + +// import Vote from "./Vote.svelte"; + +// describe("Vote", () => { +// it("should render the vote component", () => { +// }); +// }); From e7282df356adf97f254890abcf9e9af1f000803d Mon Sep 17 00:00:00 2001 From: Dan Cormier Date: Fri, 5 Dec 2025 17:06:09 -0500 Subject: [PATCH 09/29] improve svelte component, update icons, add formatNumber, and more! --- package-lock.json | 10 +- package.json | 2 +- .../lib/components/vote/vote.less | 34 ++-- packages/stacks-docs/_data/vote.json | 6 +- .../stacks-docs/product/components/vote.html | 32 ++-- packages/stacks-svelte/package.json | 2 +- .../src/components/Vote/Vote.stories.svelte | 7 +- .../src/components/Vote/Vote.svelte | 145 +++++++++++++++--- .../stacks-svelte/src/utils/format.test.ts | 65 ++++++++ packages/stacks-svelte/src/utils/format.ts | 38 +++++ packages/stacks-svelte/src/utils/index.ts | 1 + types/stacks-icons-legacy.d.ts | 3 + 12 files changed, 281 insertions(+), 64 deletions(-) create mode 100644 packages/stacks-svelte/src/utils/format.test.ts create mode 100644 packages/stacks-svelte/src/utils/format.ts create mode 100644 packages/stacks-svelte/src/utils/index.ts create mode 100644 types/stacks-icons-legacy.d.ts diff --git a/package-lock.json b/package-lock.json index 8c79f9e5ca..108dfaa34e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@rollup/plugin-commonjs": "^26.0.1", "@rollup/plugin-replace": "^6.0.3", "@stackoverflow/prettier-config": "^1.0.0", - "@stackoverflow/stacks-icons": "^7.0.0-beta.4", + "@stackoverflow/stacks-icons": "^7.0.0-beta.7", "@stackoverflow/stacks-icons-legacy": "npm:@stackoverflow/stacks-icons@^6.6.1", "@storybook/addon-docs": "^9.1.5", "@storybook/addon-links": "^9.1.3", @@ -2918,9 +2918,9 @@ "license": "MIT" }, "node_modules/@stackoverflow/stacks-icons": { - "version": "7.0.0-beta.4", - "resolved": "https://registry.npmjs.org/@stackoverflow/stacks-icons/-/stacks-icons-7.0.0-beta.4.tgz", - "integrity": "sha512-Wt3KWS7J52rmbvw3Q/Qmi7wZh200ufMDIpzVZHqnin1XeDXbq04NUzCZtTzyXw6n6gDE3PkFgiyQp1t4OvR9uw==", + "version": "7.0.0-beta.7", + "resolved": "https://registry.npmjs.org/@stackoverflow/stacks-icons/-/stacks-icons-7.0.0-beta.7.tgz", + "integrity": "sha512-ECcRBn4OTeFxvdrwX23umlxoSgdB7SisT++Y0KNScr7s5PIgWjMWwGRl0GGstxtNGbcrNsAZL/MSQEVOpgulAg==", "license": "Apache-2.0" }, "node_modules/@stackoverflow/stacks-icons-legacy": { @@ -17607,7 +17607,7 @@ "version": "1.0.0-beta.5", "dependencies": { "@floating-ui/core": "^1.7.3", - "@stackoverflow/stacks-icons": "^7.0.0-beta.4", + "@stackoverflow/stacks-icons": "^7.0.0-beta.7", "@stackoverflow/stacks-icons-legacy": "npm:@stackoverflow/stacks-icons@^6.6.1", "svelte-floating-ui": "^1.6.2", "svelte-sonner": "^1.0.5" diff --git a/package.json b/package.json index 424cfe51c9..08685a3cbf 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@rollup/plugin-commonjs": "^26.0.1", "@rollup/plugin-replace": "^6.0.3", "@stackoverflow/prettier-config": "^1.0.0", - "@stackoverflow/stacks-icons": "^7.0.0-beta.4", + "@stackoverflow/stacks-icons": "^7.0.0-beta.7", "@stackoverflow/stacks-icons-legacy": "npm:@stackoverflow/stacks-icons@^6.6.1", "@storybook/addon-docs": "^9.1.5", "@storybook/addon-links": "^9.1.3", diff --git a/packages/stacks-classic/lib/components/vote/vote.less b/packages/stacks-classic/lib/components/vote/vote.less index 9f6ca70d98..529c0d41a2 100644 --- a/packages/stacks-classic/lib/components/vote/vote.less +++ b/packages/stacks-classic/lib/components/vote/vote.less @@ -33,12 +33,14 @@ --_vo-child-p: 0; .s-vote { - &--neutral { - display: none; - } - &--positive, - &--negative { - display: block; + &--count { + &--votes { + display: none; + } + &--upvotes, + &--downvotes { + display: block; + } } } } @@ -103,17 +105,19 @@ white-space: nowrap; } - & &--positive, - & &--negative { - display: none; - } + & &--count { + &--upvotes, + &--downvotes { + display: none; + } - & &--positive { - color: var(--green-500); - } + &--upvotes { + color: var(--green-500); + } - & &--negative { - color: var(--red-500); + &--downvotes { + color: var(--red-500); + } } // INTERACTION diff --git a/packages/stacks-docs/_data/vote.json b/packages/stacks-docs/_data/vote.json index 953aef5ec5..5a932b7039 100644 --- a/packages/stacks-docs/_data/vote.json +++ b/packages/stacks-docs/_data/vote.json @@ -26,17 +26,17 @@ "description": "tbd" }, { - "class": ".s-vote--positive", + "class": ".s-vote--count--upvotes", "applies": ".s-vote--count", "description": "tbd" }, { - "class": ".s-vote--neutral", + "class": ".s-vote--count--votes", "applies": ".s-vote--count", "description": "tbd" }, { - "class": ".s-vote--negative", + "class": ".s-vote--count--downvotes", "applies": ".s-vote--count", "description": "tbd" } diff --git a/packages/stacks-docs/product/components/vote.html b/packages/stacks-docs/product/components/vote.html index cac8f5af04..fca0d8e653 100644 --- a/packages/stacks-docs/product/components/vote.html +++ b/packages/stacks-docs/product/components/vote.html @@ -71,9 +71,9 @@
@@ -88,9 +88,9 @@ {% if vote.downvote %} {% endif %} @@ -135,9 +135,9 @@
@@ -152,9 +152,9 @@ {% if vote.downvote %} {% endif %} @@ -194,9 +194,9 @@
@@ -211,9 +211,9 @@ {% if vote.downvote %} {% endif %} @@ -257,9 +257,9 @@
@@ -274,9 +274,9 @@ {% if vote.downvote %} {% endif %} diff --git a/packages/stacks-svelte/package.json b/packages/stacks-svelte/package.json index de5b8b45fc..80e3e65be3 100644 --- a/packages/stacks-svelte/package.json +++ b/packages/stacks-svelte/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@floating-ui/core": "^1.7.3", - "@stackoverflow/stacks-icons": "^7.0.0-beta.4", + "@stackoverflow/stacks-icons": "^7.0.0-beta.7", "@stackoverflow/stacks-icons-legacy": "npm:@stackoverflow/stacks-icons@^6.6.1", "svelte-floating-ui": "^1.6.2", "svelte-sonner": "^1.0.5" diff --git a/packages/stacks-svelte/src/components/Vote/Vote.stories.svelte b/packages/stacks-svelte/src/components/Vote/Vote.stories.svelte index e000cab488..070936fd2f 100644 --- a/packages/stacks-svelte/src/components/Vote/Vote.stories.svelte +++ b/packages/stacks-svelte/src/components/Vote/Vote.stories.svelte @@ -8,8 +8,13 @@ }); - + {#snippet template(args)} {/snippet} + + + + + diff --git a/packages/stacks-svelte/src/components/Vote/Vote.svelte b/packages/stacks-svelte/src/components/Vote/Vote.svelte index 04f0ba5d23..3437d1d1a8 100644 --- a/packages/stacks-svelte/src/components/Vote/Vote.svelte +++ b/packages/stacks-svelte/src/components/Vote/Vote.svelte @@ -1,4 +1,17 @@ + +
- - - {#if positive} - {positive} + (expandable ? (expanded = !expanded) : null)} + > + {#if upvotes !== undefined} + +{formatNumber(upvotes)} {/if} - {count} - {#if negative} - {negative} + + {currentCount !== undefined ? formatNumber(currentCount) : i18nVote} + + {#if downvotes !== undefined} + -{formatNumber(downvotes)} {/if} - + {#if !horizontal} - {/if}
diff --git a/packages/stacks-svelte/src/utils/format.test.ts b/packages/stacks-svelte/src/utils/format.test.ts new file mode 100644 index 0000000000..fe127960f2 --- /dev/null +++ b/packages/stacks-svelte/src/utils/format.test.ts @@ -0,0 +1,65 @@ +import { expect } from "@open-wc/testing"; +import { formatNumber } from "./format"; + +describe("formatNumber", () => { + it("should return the number as-is for numbers < 1000", () => { + expect(formatNumber(0)).to.equal("0"); + expect(formatNumber(1)).to.equal("1"); + expect(formatNumber(999)).to.equal("999"); + }); + + it("should format numbers >= 1000 and < 10000 with one decimal and 'k'", () => { + expect(formatNumber(1000)).to.equal("1k"); + expect(formatNumber(1234)).to.equal("1.2k"); + expect(formatNumber(5678)).to.equal("5.7k"); + expect(formatNumber(9900)).to.equal("9.9k"); + expect(formatNumber(9999)).to.equal("10k"); // rounds to 10 + }); + + it("should format numbers >= 10000 and < 100000 without decimal (max 4 chars)", () => { + expect(formatNumber(10000)).to.equal("10k"); + expect(formatNumber(12345)).to.equal("12k"); + expect(formatNumber(56789)).to.equal("57k"); + expect(formatNumber(99999)).to.equal("100k"); + }); + + it("should format numbers >= 100000 and < 1000000 without decimal", () => { + expect(formatNumber(100000)).to.equal("100k"); + expect(formatNumber(123456)).to.equal("123k"); + expect(formatNumber(999999)).to.equal("1000k"); + }); + + it("should format numbers >= 1000000 and < 10000000 with one decimal and 'm'", () => { + expect(formatNumber(1000000)).to.equal("1m"); + expect(formatNumber(1234567)).to.equal("1.2m"); + expect(formatNumber(5678901)).to.equal("5.7m"); + expect(formatNumber(9900000)).to.equal("9.9m"); + expect(formatNumber(9999999)).to.equal("10m"); // rounds to 10 + }); + + it("should format numbers >= 10000000 without decimal", () => { + expect(formatNumber(10000000)).to.equal("10m"); + expect(formatNumber(12345678)).to.equal("12m"); + expect(formatNumber(56789012)).to.equal("57m"); + expect(formatNumber(99999999)).to.equal("100m"); + expect(formatNumber(100000000)).to.equal("100m"); + expect(formatNumber(123456789)).to.equal("123m"); + expect(formatNumber(999999999)).to.equal("1000m"); + }); + + it("should ensure result is at most 4 characters", () => { + // 3 chars or less + expect(formatNumber(999)).to.equal("999"); + expect(formatNumber(1000)).to.equal("1k"); + expect(formatNumber(1500)).to.equal("1.5k"); + + // Exactly 4 chars + expect(formatNumber(9999)).to.equal("10k"); + expect(formatNumber(10000)).to.equal("10k"); + expect(formatNumber(99999)).to.equal("100k"); + expect(formatNumber(100000)).to.equal("100k"); + expect(formatNumber(999999)).to.equal("1000k"); + expect(formatNumber(9999999)).to.equal("10m"); + expect(formatNumber(10000000)).to.equal("10m"); + }); +}); diff --git a/packages/stacks-svelte/src/utils/format.ts b/packages/stacks-svelte/src/utils/format.ts new file mode 100644 index 0000000000..a5018ea9a0 --- /dev/null +++ b/packages/stacks-svelte/src/utils/format.ts @@ -0,0 +1,38 @@ +/** + * Formats a number by abbreviating it with k (thousands) or m (millions) suffix. + * The result will be at most 4 characters long. + * + * @param num - The number to format + * @returns The formatted number as a string + * + * @example + * formatNumber(123) // "123" + * formatNumber(1234) // "1.2k" + * formatNumber(9999) // "10k" + * formatNumber(12345) // "12k" + * formatNumber(1234567) // "1.2m" + * formatNumber(12345678) // "12m" + */ +export function formatNumber(num: number): string { + if (num < 1000) { + return num.toString(); + } + + if (num < 1000000) { + const k = num / 1000; + const rounded = parseFloat(k.toFixed(1)); + // If the rounded result would be >= 10k, don't use decimal + if (rounded >= 10) { + return Math.round(k) + "k"; + } + return rounded + "k"; + } + + const m = num / 1000000; + const rounded = parseFloat(m.toFixed(1)); + // If the rounded result would be >= 10m, don't use decimal + if (rounded >= 10) { + return Math.round(m) + "m"; + } + return rounded + "m"; +} diff --git a/packages/stacks-svelte/src/utils/index.ts b/packages/stacks-svelte/src/utils/index.ts new file mode 100644 index 0000000000..039fa7607f --- /dev/null +++ b/packages/stacks-svelte/src/utils/index.ts @@ -0,0 +1 @@ +export { formatNumber } from "./format"; diff --git a/types/stacks-icons-legacy.d.ts b/types/stacks-icons-legacy.d.ts new file mode 100644 index 0000000000..df8a0856bb --- /dev/null +++ b/types/stacks-icons-legacy.d.ts @@ -0,0 +1,3 @@ +declare module "@stackoverflow/stacks-icons-legacy/icons" { + export * from "@stackoverflow/stacks-icons/icons"; +} From 9d135ea2433281813af0a9cb90a86d344387516d Mon Sep 17 00:00:00 2001 From: Dan Cormier Date: Fri, 5 Dec 2025 17:53:12 -0500 Subject: [PATCH 10/29] A lil renaming and cleanup --- .../lib/components/vote/vote.less | 38 ++++++------- packages/stacks-docs/_data/vote.json | 14 ++--- .../stacks-docs/product/components/vote.html | 52 +++++++++--------- .../src/components/Vote/Vote.stories.svelte | 54 +++++++++++++++++-- .../src/components/Vote/Vote.svelte | 44 +++++++-------- 5 files changed, 121 insertions(+), 81 deletions(-) diff --git a/packages/stacks-classic/lib/components/vote/vote.less b/packages/stacks-classic/lib/components/vote/vote.less index 529c0d41a2..284c0b589c 100644 --- a/packages/stacks-classic/lib/components/vote/vote.less +++ b/packages/stacks-classic/lib/components/vote/vote.less @@ -33,14 +33,12 @@ --_vo-child-p: 0; .s-vote { - &--count { - &--votes { - display: none; - } - &--upvotes, - &--downvotes { - display: block; - } + &--total { + display: none; + } + &--upvotes, + &--downvotes { + display: block; } } } @@ -61,7 +59,7 @@ --_vo-child-br: 0 var(--br-pill) var(--br-pill) 0; } - .s-vote--count:last-child:not(:only-child) { + .s-vote--votes:last-child:not(:only-child) { --_vo-child-p: 0 calc(var(--su12) + var(--su2)) 0 var(--su4); // 0 14px 0 4px } @@ -87,7 +85,7 @@ } & &--btn, - & > &--count { + & > &--votes { background-color: var(--_vo-child-bg); border-radius: var(--_vo-child-br); flex-direction: var(--_vo-child-fd); @@ -105,19 +103,17 @@ white-space: nowrap; } - & &--count { - &--upvotes, - &--downvotes { - display: none; - } + & &--upvotes, + & &--downvotes { + display: none; + } - &--upvotes { - color: var(--green-500); - } + & &--upvotes { + color: var(--green-500); + } - &--downvotes { - color: var(--red-500); - } + & &--downvotes { + color: var(--red-500); } // INTERACTION diff --git a/packages/stacks-docs/_data/vote.json b/packages/stacks-docs/_data/vote.json index 5a932b7039..9e8fc6515b 100644 --- a/packages/stacks-docs/_data/vote.json +++ b/packages/stacks-docs/_data/vote.json @@ -21,23 +21,23 @@ "description": "tbd" }, { - "class": ".s-vote--count", + "class": ".s-vote--votes", "applies": ".s-vote", "description": "tbd" }, { - "class": ".s-vote--count--upvotes", - "applies": ".s-vote--count", + "class": ".s-vote--upvotes", + "applies": ".s-vote--votes", "description": "tbd" }, { - "class": ".s-vote--count--votes", - "applies": ".s-vote--count", + "class": ".s-vote--total", + "applies": ".s-vote--votes", "description": "tbd" }, { - "class": ".s-vote--count--downvotes", - "applies": ".s-vote--count", + "class": ".s-vote--downvotes", + "applies": ".s-vote--votes", "description": "tbd" } ], diff --git a/packages/stacks-docs/product/components/vote.html b/packages/stacks-docs/product/components/vote.html index fca0d8e653..5a8e88139e 100644 --- a/packages/stacks-docs/product/components/vote.html +++ b/packages/stacks-docs/product/components/vote.html @@ -49,8 +49,8 @@ - - 12 + + 12 - + {%if vote.positive %} - {{ vote.positive }} + {{ vote.positive }} {% endif %} - {{ vote.count }} + {{ vote.count }} {%if vote.negative %} - {{ vote.negative }} + {{ vote.negative }} {% endif %} {% if vote.downvote %} @@ -111,10 +111,10 @@ - - 12 - 20 - 8 + + 12 + 20 + 8 - + {%if vote.positive %} - {{ vote.positive }} + {{ vote.positive }} {% endif %} - {{ vote.count }} + {{ vote.count }} {%if vote.negative %} - {{ vote.negative }} + {{ vote.negative }} {% endif %} {% if vote.downvote %} @@ -175,8 +175,8 @@ - - 12 + + 12
{% endhighlight %} @@ -199,13 +199,13 @@ {% icon "Vote16Up" %} {% endif %} - + {%if vote.positive %} - {{ vote.positive }} + {{ vote.positive }} {% endif %} - {{ vote.count }} + {{ vote.count }} {%if vote.negative %} - {{ vote.negative }} + {{ vote.negative }} {% endif %} {% if vote.downvote %} @@ -235,8 +235,8 @@ - - 12 + + 12 - + {%if vote.positive %} - {{ vote.positive }} + {{ vote.positive }} {% endif %} - {{ vote.count }} + {{ vote.count }} {%if vote.negative %} - {{ vote.negative }} + {{ vote.negative }} {% endif %} {% if vote.downvote %} diff --git a/packages/stacks-svelte/src/components/Vote/Vote.stories.svelte b/packages/stacks-svelte/src/components/Vote/Vote.stories.svelte index 070936fd2f..a17e34bffe 100644 --- a/packages/stacks-svelte/src/components/Vote/Vote.stories.svelte +++ b/packages/stacks-svelte/src/components/Vote/Vote.stories.svelte @@ -1,10 +1,18 @@ @@ -14,7 +22,43 @@ {/snippet} - - - - + + {#snippet template()} +
+ + + + + +
+ {/snippet} +
+ + + {#snippet template()} +
+ + +
+ {/snippet} +
+ + + {#snippet template()} +
+ + + +
+ {/snippet} +
+ + + {#snippet template()} +
+ + + +
+ {/snippet} +
diff --git a/packages/stacks-svelte/src/components/Vote/Vote.svelte b/packages/stacks-svelte/src/components/Vote/Vote.svelte index 3437d1d1a8..5409cb8aff 100644 --- a/packages/stacks-svelte/src/components/Vote/Vote.svelte +++ b/packages/stacks-svelte/src/components/Vote/Vote.svelte @@ -1,5 +1,5 @@
- - + @@ -131,7 +127,6 @@ {% assign classList = classList | append: " s-vote__expanded" %} {% endif %}
- {{ vote.description }}
+ + 12 + + +
+ +
12
{% endhighlight %}
{% for vote in vote.groups.base %} - {% assign classList = "s-vote" %} - {% if vote.horizontal %} - {% assign classList = classList | append: " s-vote__horizontal" %} - {% endif %} - {% if vote.expanded %} - {% assign classList = classList | append: " s-vote__expanded" %} - {% endif %}
{{ vote.description }} -
+
- {%if vote.positive %} - {{ vote.positive }} - {% endif %} {{ vote.count }} - {%if vote.negative %} - {{ vote.negative }} - {% endif %} - {% if vote.downvote %} - - {% endif %} +
{% endfor %} @@ -106,6 +87,7 @@
{% endhighlight %}
{% for vote in vote.groups.expanded %} - {% assign classList = "s-vote" %} - {% if vote.horizontal %} - {% assign classList = classList | append: " s-vote__horizontal" %} - {% endif %} - {% if vote.expanded %} - {% assign classList = classList | append: " s-vote__expanded" %} - {% endif %}
-
+
- {%if vote.positive %} - {{ vote.positive }} - {% endif %} + {{ vote.positive }} {{ vote.count }} - {%if vote.negative %} - {{ vote.negative }} - {% endif %} + {{ vote.negative }} - {% if vote.downvote %} - - {% endif %} +
{% endfor %} @@ -170,12 +134,14 @@
12
@@ -183,6 +149,7 @@
12 @@ -191,39 +158,20 @@ {% endhighlight %}
{% for vote in vote.groups.horizontal %} - {% assign classList = "s-vote" %} - {% if vote.horizontal %} - {% assign classList = classList | append: " s-vote__horizontal" %} - {% endif %} - {% if vote.expanded %} - {% assign classList = classList | append: " s-vote__expanded" %} - {% endif %}
{{ vote.description }} -
+
- {%if vote.positive %} - {{ vote.positive }} - {% endif %} {{ vote.count }} - {%if vote.negative %} - {{ vote.negative }} - {% endif %} {% if vote.downvote %} {% endif %}
@@ -243,12 +191,14 @@
12
{% endhighlight %} @@ -258,34 +208,29 @@ {% if vote.horizontal %} {% assign classList = classList | append: " s-vote__horizontal" %} {% endif %} - {% if vote.expanded %} - {% assign classList = classList | append: " s-vote__expanded" %} - {% endif %}
{{ vote.description }}
- {%if vote.positive %} - {{ vote.positive }} - {% endif %} {{ vote.count }} - {%if vote.negative %} - {{ vote.negative }} - {% endif %} {% if vote.downvote %} {% endif %} From 000bcac0407845b029fcaec1b0e568b178c68255 Mon Sep 17 00:00:00 2001 From: Dan Cormier Date: Tue, 9 Dec 2025 16:46:09 -0500 Subject: [PATCH 17/29] Improve stacks classic tests --- .../lib/components/vote/vote.a11y.test.ts | 58 ++++++++++--------- .../lib/components/vote/vote.visual.test.ts | 58 ++++++++++--------- 2 files changed, 64 insertions(+), 52 deletions(-) diff --git a/packages/stacks-classic/lib/components/vote/vote.a11y.test.ts b/packages/stacks-classic/lib/components/vote/vote.a11y.test.ts index 901762296e..6b04af7cb9 100644 --- a/packages/stacks-classic/lib/components/vote/vote.a11y.test.ts +++ b/packages/stacks-classic/lib/components/vote/vote.a11y.test.ts @@ -5,6 +5,35 @@ import { } from "@stackoverflow/stacks-icons/icons"; import "../../index"; +const children = { + default: ` + + + 12 + +20 + -8 + + + `, + upvoteOnly: ` + + + 12 + +20 + -8 + + ` +}; + describe("vote", () => { runA11yTests({ baseClass: "s-vote", @@ -12,23 +41,11 @@ describe("vote", () => { primary: ["expanded"], }, children: { - default: ` - - - +20 - 12 - -8 - - - `, + default: children.default, }, }); - // Horizontal without downvote button + // Horizontal with and without downvote runA11yTests({ baseClass: "s-vote", modifiers: { @@ -37,17 +54,6 @@ describe("vote", () => { options: { includeNullModifier: false, }, - children: { - default: ` - - - +20 - 12 - -8 - - `, - }, + children, }); }); diff --git a/packages/stacks-classic/lib/components/vote/vote.visual.test.ts b/packages/stacks-classic/lib/components/vote/vote.visual.test.ts index 854883f165..b75249271d 100644 --- a/packages/stacks-classic/lib/components/vote/vote.visual.test.ts +++ b/packages/stacks-classic/lib/components/vote/vote.visual.test.ts @@ -5,6 +5,35 @@ import { } from "@stackoverflow/stacks-icons/icons"; import "../../index"; +const children = { + default: ` + + + 12 + +20 + -8 + + + `, + upvoteOnly: ` + + + 12 + +20 + -8 + + ` +}; + describe("vote", () => { runVisualTests({ baseClass: "s-vote", @@ -12,23 +41,11 @@ describe("vote", () => { primary: ["expanded"], }, children: { - default: ` - - - +20 - 12 - -8 - - - `, + default: children.default, }, }); - // Horizontal without downvote button + // Horizontal with and without downvote runVisualTests({ baseClass: "s-vote", modifiers: { @@ -37,17 +54,6 @@ describe("vote", () => { options: { includeNullModifier: false, }, - children: { - default: ` - - - +20 - 12 - -8 - - `, - }, + children, }); }); From 1784d7b47ccf8849e9aa07b43c951841594bb823 Mon Sep 17 00:00:00 2001 From: Dan Cormier Date: Tue, 9 Dec 2025 16:49:16 -0500 Subject: [PATCH 18/29] Add i18nExpand, i18nExpanded --- .../src/components/Vote/Vote.svelte | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/stacks-svelte/src/components/Vote/Vote.svelte b/packages/stacks-svelte/src/components/Vote/Vote.svelte index 2d85605dcd..33e9282a41 100644 --- a/packages/stacks-svelte/src/components/Vote/Vote.svelte +++ b/packages/stacks-svelte/src/components/Vote/Vote.svelte @@ -63,6 +63,15 @@ */ i18nDownvoted?: string | undefined; + /** + * Screen reader text for the vote count when not expanded. Defaults to "Show vote details". + */ + i18nExpand?: string | undefined; + + /** + * Screen reader text for the vote count when expanded. Defaults to "Hide vote details". + */ + i18nExpanded?: string | undefined; /** * Callback fired when the upvote button is clicked. * Should return a promise that resolves on success. @@ -92,6 +101,8 @@ i18nUpvoted = "Upvoted", i18nDownvote = "Downvote", i18nDownvoted = "Downvoted", + i18nExpand = "Show vote details", + i18nExpanded = "Hide vote details", onupvote = () => Promise.resolve(), ondownvote = () => Promise.resolve(), class: className = "", @@ -157,11 +168,11 @@
-{formatNumber(downvotes)} {/if} + {#if expandable} + {expanded ? i18nExpanded : i18nExpand} + {/if} {#if !horizontal} {/if} From 58de3273339ecd701120faeee4e213d45595d128 Mon Sep 17 00:00:00 2001 From: Dan Cormier Date: Tue, 9 Dec 2025 16:51:02 -0500 Subject: [PATCH 19/29] Update svelte test --- packages/stacks-svelte/src/components/Vote/Vote.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/stacks-svelte/src/components/Vote/Vote.test.ts b/packages/stacks-svelte/src/components/Vote/Vote.test.ts index dbe1cbf5ad..2b9c363878 100644 --- a/packages/stacks-svelte/src/components/Vote/Vote.test.ts +++ b/packages/stacks-svelte/src/components/Vote/Vote.test.ts @@ -206,11 +206,15 @@ describe("Vote", () => { it("should use custom i18n text for screen readers", () => { render(Vote, { total: 12, + upvotes: 20, + downvotes: 8, i18nUpvote: "Vote up", i18nDownvote: "Vote down", + i18nExpand: "Expand votes", }); expect(screen.getByText("Vote up")).to.exist; expect(screen.getByText("Vote down")).to.exist; + expect(screen.getByText("Expand votes")).to.exist; }); it("should show count instead of 'Vote' text after voting on 0 count", async () => { From df9ebac5d09dabc1b47f2f5ce40f7ad337e8c918 Mon Sep 17 00:00:00 2001 From: Dan Cormier Date: Tue, 9 Dec 2025 16:56:25 -0500 Subject: [PATCH 20/29] Svelte component tweaks --- .../src/components/Vote/Vote.stories.svelte | 3 ++- .../stacks-svelte/src/components/Vote/Vote.svelte | 12 ++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/stacks-svelte/src/components/Vote/Vote.stories.svelte b/packages/stacks-svelte/src/components/Vote/Vote.stories.svelte index a17e34bffe..d60508a8cc 100644 --- a/packages/stacks-svelte/src/components/Vote/Vote.stories.svelte +++ b/packages/stacks-svelte/src/components/Vote/Vote.stories.svelte @@ -46,7 +46,7 @@ {#snippet template()}
- +
@@ -59,6 +59,7 @@ +
{/snippet} diff --git a/packages/stacks-svelte/src/components/Vote/Vote.svelte b/packages/stacks-svelte/src/components/Vote/Vote.svelte index 33e9282a41..b098df263d 100644 --- a/packages/stacks-svelte/src/components/Vote/Vote.svelte +++ b/packages/stacks-svelte/src/components/Vote/Vote.svelte @@ -38,6 +38,11 @@ */ status?: Status; + /** + * Whether to hide the downvote button. Defaults to false. + */ + hideDownvote?: boolean; + /** * Text to display when total is 0 and no vote has been cast. Defaults to "Vote". */ @@ -95,6 +100,7 @@ upvotes = undefined, downvotes = undefined, horizontal, + hideDownvote = false, status = null, i18nVote = "Vote", i18nUpvote = "Upvote", @@ -193,10 +199,12 @@ -{formatNumber(downvotes)} {/if} {#if expandable} - {expanded ? i18nExpanded : i18nExpand} + + {expanded ? i18nExpanded : i18nExpand} + {/if} - {#if !horizontal} + {#if !hideDownvote} - - 12 - -
- - -
- - - 12 - -
{% endhighlight %}
{% for vote in vote.groups.horizontal %}
- {{ vote.description }} + {% if vote.description %} + {{ vote.description }} + {% endif %}
- - {{ vote.count }} - - {% if vote.downvote %} - - {% endif %}
{% endfor %} @@ -211,19 +191,19 @@
{{ vote.description }}
- - - {{ vote.count }} - {% if vote.downvote %} + + + {{ vote.count }} + + {% else %} + {% endif %}
diff --git a/packages/stacks-svelte/src/components/Vote/Vote.stories.svelte b/packages/stacks-svelte/src/components/Vote/Vote.stories.svelte index d60508a8cc..a17e34bffe 100644 --- a/packages/stacks-svelte/src/components/Vote/Vote.stories.svelte +++ b/packages/stacks-svelte/src/components/Vote/Vote.stories.svelte @@ -46,7 +46,7 @@ {#snippet template()}
- +
@@ -59,7 +59,6 @@ -
{/snippet} diff --git a/packages/stacks-svelte/src/components/Vote/Vote.svelte b/packages/stacks-svelte/src/components/Vote/Vote.svelte index b098df263d..174c856f5b 100644 --- a/packages/stacks-svelte/src/components/Vote/Vote.svelte +++ b/packages/stacks-svelte/src/components/Vote/Vote.svelte @@ -38,11 +38,6 @@ */ status?: Status; - /** - * Whether to hide the downvote button. Defaults to false. - */ - hideDownvote?: boolean; - /** * Text to display when total is 0 and no vote has been cast. Defaults to "Vote". */ @@ -100,7 +95,6 @@ upvotes = undefined, downvotes = undefined, horizontal, - hideDownvote = false, status = null, i18nVote = "Vote", i18nUpvote = "Upvote", @@ -180,31 +174,41 @@ {i18nUpvote} {/if} - - (expandable ? (expanded = !expanded) : null)} - > - {#if upvotes !== undefined} - +{formatNumber(upvotes)} - {/if} - - {currentCount !== 0 || currentStatus !== null - ? formatNumber(currentCount) - : i18nVote} - - {#if downvotes !== undefined} - -{formatNumber(downvotes)} - {/if} - {#if expandable} - - {expanded ? i18nExpanded : i18nExpand} + {#if horizontal} + + + {currentCount !== 0 || currentStatus !== null + ? formatNumber(currentCount) + : i18nVote} + {/if} - - {#if !hideDownvote} + + {#if !horizontal} + (expandable ? (expanded = !expanded) : null)} + > + {#if upvotes !== undefined} + +{formatNumber(upvotes)} + {/if} + + {currentCount !== 0 || currentStatus !== null + ? formatNumber(currentCount) + : i18nVote} + + {#if downvotes !== undefined} + -{formatNumber(downvotes)} + {/if} + {#if expandable} + + {expanded ? i18nExpanded : i18nExpand} + + {/if} + - - 12 - +20 - -8 - - - `, - upvoteOnly: ` - - - 12 - +20 - -8 - - `, -}; - describe("vote", () => { runVisualTests({ baseClass: "s-vote", @@ -42,7 +13,21 @@ describe("vote", () => { primary: ["expanded"], }, children: { - default: children.default, + default: ` + + + 12 + +20 + -8 + + + `, }, template: ({ component, testid }) => html`
{ options: { includeNullModifier: false, }, - children, + children: { + default: ` + + `, + }, template: ({ component, testid }) => html`