Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e36eccd
feat(vote): add vote (btn(s)) component
dancormier Nov 21, 2025
1cc9103
Rename to just "vote", better styles
dancormier Nov 21, 2025
e31cb6c
Add expanded modifier, positive/negative count styles
dancormier Dec 2, 2025
e85689c
Update docs
dancormier Dec 2, 2025
dba7ef2
Add basic tests
dancormier Dec 2, 2025
ba910b0
build(deps-dev): bump stylelint-config-standard from 38.0.0 to 39.0.1…
dependabot[bot] Nov 21, 2025
ac1d74e
Better, but not perfect, vote icons
dancormier Dec 2, 2025
1d1fc91
Add basic Svelte component
dancormier Dec 2, 2025
e7282df
improve svelte component, update icons, add formatNumber, and more!
dancormier Dec 5, 2025
9d135ea
A lil renaming and cleanup
dancormier Dec 5, 2025
23025e1
Add a11y, visual tests
dancormier Dec 5, 2025
0380597
Add svelte test and cleanup
dancormier Dec 5, 2025
f6bc5b6
Doc updates
dancormier Dec 9, 2025
7cb667a
More docs updates
dancormier Dec 9, 2025
561d153
Merge branch 'beta' into SPARK-103/vote-btn-component
dancormier Dec 9, 2025
6dc3a5b
Use moduleResolution: "bundler" in Stacks Classic
dancormier Dec 9, 2025
f7b4b72
Cleanup
dancormier Dec 9, 2025
000bcac
Improve stacks classic tests
dancormier Dec 9, 2025
1784d7b
Add i18nExpand, i18nExpanded
dancormier Dec 9, 2025
58de327
Update svelte test
dancormier Dec 9, 2025
df9ebac
Svelte component tweaks
dancormier Dec 9, 2025
5e4b1f0
changeset
dancormier Dec 9, 2025
17e3f0d
Update visual test
dancormier Dec 9, 2025
8fbb4b6
Add visual regression test images
dancormier Dec 9, 2025
bfbd612
formatting, tests updates
dancormier Dec 9, 2025
9ebd356
Fix a test
dancormier Dec 9, 2025
f6d835e
formatting!!!!
dancormier Dec 9, 2025
8e4d8b6
add missing "for"
dancormier Dec 12, 2025
6399e18
Match horizontal vote mocks
dancormier Dec 12, 2025
8c5962e
Merge branch 'beta' into SPARK-103/vote-btn-component
dancormier Dec 12, 2025
fa90b94
Address reviews
dancormier Dec 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/eight-pants-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@stackoverflow/stacks": minor
"@stackoverflow/stacks-svelte": minor
---

Add Vote component
35 changes: 29 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
59 changes: 59 additions & 0 deletions packages/stacks-classic/lib/components/vote/vote.a11y.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { runA11yTests } from "../../test/a11y-test-utils";
import {
IconVote16Up,
IconVote16Down,
} from "@stackoverflow/stacks-icons/icons";
import "../../index";

const children = {
default: `
<button class="s-vote--btn">
${IconVote16Up}
<span class="v-visible-sr">upvote</span>
</button>
<span class="s-vote--votes">
<span class="s-vote--total">12</span>
<span class="s-vote--upvotes">+20</span>
<span class="s-vote--downvotes">-8</span>
</span>
<button class="s-vote--btn">
${IconVote16Down}
<span class="v-visible-sr">downvote</span>
</button>
`,
upvoteOnly: `
<button class="s-vote--btn">
${IconVote16Up}
<span class="v-visible-sr">upvote</span>
</button>
<span class="s-vote--votes">
<span class="s-vote--total">12</span>
<span class="s-vote--upvotes">+20</span>
<span class="s-vote--downvotes">-8</span>
</span>
`,
};

describe("vote", () => {
runA11yTests({
baseClass: "s-vote",
modifiers: {
primary: ["expanded"],
},
children: {
default: children.default,
},
});

// Horizontal with and without downvote
runA11yTests({
baseClass: "s-vote",
modifiers: {
primary: ["horizontal"],
},
options: {
includeNullModifier: false,
},
children,
});
});
134 changes: 134 additions & 0 deletions packages/stacks-classic/lib/components/vote/vote.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
.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
&&__expanded {
--_vo-child-g: var(--su2);
--_vo-child-p: 0;

.s-vote {
&--total {
display: none;
}
&--upvotes,
&--downvotes {
display: block;
}
}
}

&&__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--votes: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,
& > &--votes {
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;
}

& &--upvotes,
& &--downvotes {
display: none;
}

& &--upvotes {
color: var(--green-500);
}

& &--downvotes {
color: var(--red-500);
}

// INTERACTION
> button,
& &--btn {
&:focus-visible {
.focus-styles(true, false);
}

&:hover {
--_vo-child-bg: var(--black-200);
}
}

flex-direction: var(--_vo-fd);

display: flex;
}
71 changes: 71 additions & 0 deletions packages/stacks-classic/lib/components/vote/vote.visual.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { runVisualTests } from "../../test/visual-test-utils";
import { html } from "@open-wc/testing";
import {
IconVote16Up,
IconVote16Down,
} from "@stackoverflow/stacks-icons/icons";
import "../../index";

describe("vote", () => {
runVisualTests({
baseClass: "s-vote",
modifiers: {
primary: ["expanded"],
},
children: {
default: `
<button class="s-vote--btn">
${IconVote16Up}
<span class="v-visible-sr">upvote</span>
</button>
<span class="s-vote--votes">
<span class="s-vote--total">12</span>
<span class="s-vote--upvotes">+20</span>
<span class="s-vote--downvotes">-8</span>
</span>
<button class="s-vote--btn">
${IconVote16Down}
<span class="v-visible-sr">downvote</span>
</button>
`,
},
template: ({ component, testid }) => html`
<div
class="d-inline-flex ai-center jc-center ws1 p8"
data-testid="${testid}"
>
${component}
</div>
`,
});

// Horizontal with and without downvote
runVisualTests({
baseClass: "s-vote",
modifiers: {
primary: ["horizontal"],
},
options: {
includeNullModifier: false,
},
children: {
default: `
<button class="s-vote--btn">
${IconVote16Up}
<span class="v-visible-sr">upvote</span>
<span class="s-vote--votes">
<span class="s-vote--upvotes">+20</span>
</span>
</button>
`,
},
template: ({ component, testid }) => html`
<div
class="d-inline-flex ai-center jc-center ws1 p8"
data-testid="${testid}"
>
${component}
</div>
`,
});
});
1 change: 1 addition & 0 deletions packages/stacks-classic/lib/stacks-static.less
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
@import "components/topbar/topbar.less";
@import "components/uploader/uploader.less";
@import "components/user-card/user-card.less";
@import "components/vote/vote.less";

// LESS CONSTANTS AND MIXINS
@import "exports/exports.less";
Expand Down
2 changes: 1 addition & 1 deletion packages/stacks-classic/lib/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"lib": ["dom", "es6", "dom.iterable", "scripthost"],
"outDir": "../dist",
"sourceMap": true,
"moduleResolution": "node",
"moduleResolution": "bundler",
"skipLibCheck": true,
"declaration": true,
"allowSyntheticDefaultImports": true,
Expand Down
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Loading