Skip to content

Commit ce0da79

Browse files
authored
feat(#5): add VoteCard Component (#8)
* feat(5): create vote card * feat(#5): add story for VoteCard component * feat(#5): make VoteCard story component is able to custom candidate name * feat(#5): use Design Tokens for typography instead of TailwiindCSS utility classes * feat(#5): fix follow code reviewed
1 parent c6cf2dd commit ce0da79

9 files changed

Lines changed: 252 additions & 9 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
},
2626
"dependencies": {
2727
"@sveltejs/adapter-static": "^2.0.3",
28+
"dayjs": "^1.11.10",
2829
"scrollama": "^3.2.0"
2930
},
3031
"devDependencies": {
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<script lang="ts">
2+
import { DefaultVotingResult } from '$models/voting';
3+
import type { VoteCardProps } from '../../routes/assemblies/[id]/+page';
4+
import VoteCard from './VoteCard.svelte';
5+
import type { Hst } from '@histoire/plugin-svelte';
6+
7+
export let Hst: Hst;
8+
9+
const passedVoting: VoteCardProps['voting'] = {
10+
id: 1,
11+
date: new Date('2023-08-31T17:00:00.000Z'),
12+
title: 'ร่าง พ.ร.บ. สุราก้าวหน้า (ส่งไป ครม.)',
13+
result: DefaultVotingResult.Passed
14+
};
15+
16+
const failedVoting: VoteCardProps['voting'] = {
17+
id: 2,
18+
date: new Date('2023-09-01T17:00:00.000Z'),
19+
title: 'ร่าง พ.ร.บ. สุราก้าวหน้า (ส่งไป ครม.)',
20+
result: DefaultVotingResult.Failed
21+
};
22+
23+
const passedHighlightedVoteByGroups: VoteCardProps['highlightedVoteByGroups'] = [
24+
{
25+
name: 'สส. ฝ่ายรัฐบาล',
26+
count: 160,
27+
total: 315
28+
},
29+
{
30+
name: 'สส. ฝ่ายค้าน',
31+
count: 164,
32+
total: 185
33+
},
34+
{
35+
name: 'สว.',
36+
count: 200,
37+
total: 250
38+
}
39+
];
40+
41+
const failedHighlightedVoteByGroups: VoteCardProps['highlightedVoteByGroups'] = [
42+
{
43+
name: 'สส. ฝ่ายรัฐบาล',
44+
count: 16,
45+
total: 315
46+
},
47+
{
48+
name: 'สส. ฝ่ายค้าน',
49+
count: 4,
50+
total: 185
51+
},
52+
{
53+
name: 'สว.',
54+
count: 20,
55+
total: 250
56+
}
57+
];
58+
59+
const dictVoteCardProps: Record<DefaultVotingResult, VoteCardProps> = {
60+
[DefaultVotingResult.Passed]: {
61+
voting: passedVoting,
62+
highlightedVoteByGroups: passedHighlightedVoteByGroups
63+
},
64+
[DefaultVotingResult.Failed]: {
65+
voting: failedVoting,
66+
highlightedVoteByGroups: failedHighlightedVoteByGroups
67+
}
68+
};
69+
70+
let result: DefaultVotingResult = DefaultVotingResult.Passed;
71+
let candidateName = '';
72+
73+
$: isCandidateResult = 'candidate' === (result as string);
74+
$: ({ voting, highlightedVoteByGroups } = dictVoteCardProps[result] || candidateVoteCardProps);
75+
76+
$: candidateVoting = {
77+
id: 3,
78+
/**
79+
* @author fResult <Styxmaz@gmail.com>
80+
* FIXME: Actually, it should be new Date('2023-09-02T17:00:00.000Z'), but I can't get Date object from Reactivity (Proxie)
81+
*/
82+
date: '2023-09-02T17:00:00.000Z' as unknown as Date,
83+
title: 'เลือกนายกรัฐมนตรีไทย คนที่ 29',
84+
result: candidateName || 'Mr. Candidate Krub'
85+
} satisfies VoteCardProps['voting'];
86+
87+
$: candidateVoteCardProps = {
88+
voting: candidateVoting,
89+
highlightedVoteByGroups: passedHighlightedVoteByGroups
90+
} satisfies VoteCardProps;
91+
</script>
92+
93+
<Hst.Story title="VoteCard">
94+
<VoteCard {voting} {highlightedVoteByGroups} />
95+
96+
<svelte:fragment slot="controls">
97+
<p>Card Result:</p>
98+
<Hst.Select
99+
title="Voting Result"
100+
bind:value={result}
101+
options={[
102+
{ label: 'DefaultVotingResult.Passed', value: DefaultVotingResult.Passed },
103+
{ label: 'DefaultVotingResult.Failed', value: DefaultVotingResult.Failed },
104+
{ label: 'Any String (Candidate Name)', value: 'candidate' }
105+
]}
106+
/>
107+
{#if isCandidateResult}
108+
<Hst.Text title="Candidate Name" bind:value={candidateName} />
109+
{/if}
110+
111+
<p>Example Props:</p>
112+
<Hst.Json title="Example `voting` Prop" bind:value={voting} />
113+
<Hst.Json title="Example `highlightedVoteByGroups` Prop" bind:value={highlightedVoteByGroups} />
114+
</svelte:fragment>
115+
</Hst.Story>
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<script lang="ts">
2+
import DirectionStraightRight from 'carbon-icons-svelte/lib/DirectionStraightRight.svelte';
3+
import { DefaultVotingResult, type Voting } from '$models/voting';
4+
import { Tag } from 'carbon-components-svelte';
5+
import dayjs from 'dayjs';
6+
import 'dayjs/locale/th';
7+
import buddhistEra from 'dayjs/plugin/buddhistEra';
8+
import type { VoteCardProps } from '../../routes/assemblies/[id]/+page';
9+
10+
dayjs.extend(buddhistEra);
11+
dayjs.locale('th');
12+
13+
interface VoteCardTheme {
14+
bg: string;
15+
hoveredBg: string;
16+
tagBg: string;
17+
tagFontColor: string;
18+
}
19+
const CARD_THEMES: Record<DefaultVotingResult, VoteCardTheme> = {
20+
[DefaultVotingResult.Passed]: {
21+
bg: 'bg-teal-10',
22+
hoveredBg: 'hover:bg-teal-20',
23+
tagBg: 'bg-teal-30',
24+
tagFontColor: 'text-text-01'
25+
},
26+
[DefaultVotingResult.Failed]: {
27+
bg: 'bg-red-10',
28+
hoveredBg: 'hover:bg-red-20',
29+
tagBg: 'bg-red-30',
30+
tagFontColor: 'text-text-01'
31+
}
32+
};
33+
const CANDIDATE_CARD_THEME: VoteCardTheme = {
34+
bg: 'bg-purple-10',
35+
hoveredBg: 'hover:bg-purple-20',
36+
tagBg: 'bg-purple-70',
37+
tagFontColor: 'text-white'
38+
};
39+
40+
export let voting: VoteCardProps['voting'] = {} as Voting;
41+
export let highlightedVoteByGroups: VoteCardProps['highlightedVoteByGroups'] = [];
42+
43+
interface HighlightedVoteSummary {
44+
totalCount: number;
45+
totalAmount: number;
46+
}
47+
$: ({ totalCount, totalAmount } = highlightedVoteByGroups.reduce<HighlightedVoteSummary>(
48+
reduceHighlightedVoteSummary,
49+
{ totalCount: 0, totalAmount: 0 }
50+
));
51+
$: theme = CARD_THEMES[voting.result as DefaultVotingResult] || CANDIDATE_CARD_THEME;
52+
$: isCandidate = ![DefaultVotingResult.Failed, DefaultVotingResult.Passed].includes(
53+
voting.result as DefaultVotingResult
54+
);
55+
56+
function reduceHighlightedVoteSummary(
57+
{ totalAmount, totalCount }: HighlightedVoteSummary,
58+
{ count, total }: VoteCardProps['highlightedVoteByGroups'][number]
59+
): HighlightedVoteSummary {
60+
return {
61+
totalCount: totalCount + count,
62+
totalAmount: totalAmount + total
63+
};
64+
}
65+
</script>
66+
67+
<div
68+
class="vote-card rounded-sm relative p-4 flex flex-col gap-y-2 w-72 h-64.5 whitespace-break-spaces {theme.bg} {theme.hoveredBg}"
69+
>
70+
<p class="body-compact-01 text-text-02">
71+
{dayjs(voting.date).format('D MMM BB')}
72+
</p>
73+
<a class="vote-card--url after:inset no-underline after:absolute w-56" href="#{voting.id}">
74+
<h3 class="fluid-heading-03 text-text-01">{voting.title}</h3>
75+
</a>
76+
<section class="vote-card__result flex flex-col gap-y-2 w-56">
77+
<Tag class={`label-01 ${theme.tagFontColor} ${theme.tagBg} w-fit m-0`}>{voting.result}</Tag>
78+
<div class="flex flex-col gap-x-1">
79+
<div class="flex align-middle justify-between">
80+
<p class="text-text-01 heading-01">{isCandidate ? voting.result : 'เห็นด้วย'}</p>
81+
<p>
82+
<span class="mr-[2px] text-text-primary heading-01">{totalCount}</span>
83+
<span class="text-text-02 body-01">/{totalAmount}</span>
84+
</p>
85+
</div>
86+
<ul class="vote-card__result--list">
87+
{#each highlightedVoteByGroups as voteByGroup (voteByGroup.name)}
88+
<li class="vote-card__result--item flex flex-row align-middle justify-between body-01">
89+
<p class="text-text-01">{voteByGroup.name}</p>
90+
<p>
91+
<span class="text-text-primary mr-[2px]">{voteByGroup.count}</span>
92+
<span class="text-text-02">/{voteByGroup.total}</span>
93+
</p>
94+
</li>
95+
{/each}
96+
</ul>
97+
</div>
98+
</section>
99+
<DirectionStraightRight class="ml-auto" />
100+
</div>
101+
102+
<style lang="scss">
103+
.vote-card {
104+
position: relative;
105+
106+
&--url {
107+
&::after {
108+
content: '';
109+
position: absolute;
110+
inset: 0;
111+
}
112+
}
113+
}
114+
</style>

src/mocks/data/voting.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,8 @@ export const failedVoting: Voting = {
4848
...passedVoting,
4949
result: DefaultVotingResult.Failed
5050
};
51+
52+
export const candidateVoting: Voting = {
53+
...passedVoting,
54+
result: 'ชื่อแคนดิเดต'
55+
};

src/routes/assemblies/[id]/+page.svelte

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<script lang="ts">
2-
export let data;
2+
import type { load } from './+page';
3+
4+
export let data: ReturnType<typeof load>;
35
</script>
46

57
<div class="whitespace-pre">

src/routes/assemblies/[id]/+page.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { Assembly } from '$models/assembly.js';
12
import type { Party } from '$models/party.js';
23
import type { Politician } from '$models/politician.js';
34
import { DefaultVotingResult, type Voting } from '$models/voting.js';
@@ -31,7 +32,7 @@ interface MainMember {
3132
partyRole?: string;
3233
}
3334

34-
interface VoteCard {
35+
export interface VoteCardProps {
3536
voting: Pick<Voting, 'id' | 'title' | 'date' | 'result'>;
3637
highlightedVoteByGroups: {
3738
name: string;
@@ -42,7 +43,8 @@ interface VoteCard {
4243

4344
export function load({ params }) {
4445
const isSenates = params.id === sen12.id;
45-
const { mainRoles, ...assembly } = isSenates ? sen12 : rep26;
46+
const { mainRoles, ...rest }: Assembly = isSenates ? sen12 : rep26;
47+
const assembly: Omit<Assembly, 'mainRoles'> = rest;
4648

4749
const mainMembers: MainMember[] = mainRoles.map((assemblyRole) => ({
4850
assemblyRole,
@@ -278,13 +280,13 @@ export function load({ params }) {
278280
]
279281
};
280282

281-
const latestVotes: VoteCard[] = new Array(5)
283+
const latestVotes: VoteCardProps[] = new Array(5)
282284
.fill([
283285
{ name: 'สส. ฝ่ายรัฐบาล', count: 160, total: 315 },
284286
{ name: 'สส. ฝ่ายค้าน', count: 164, total: 185 },
285287
{ name: 'สว.', count: 200, total: 250 }
286288
])
287-
.map((highlightedVoteByGroups, i) => ({
289+
.map<VoteCardProps>((highlightedVoteByGroups, i) => ({
288290
voting: {
289291
id: i + 1,
290292
date: new Date(`09/${i + 1}/2023`),

src/routes/politicians/[id]/+page.svelte

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
<script lang="ts">
2-
import SideNav from '../../../components/politicians/SideNav.svelte';
2+
import SideNav from '$components/politicians/SideNav.svelte';
33
4+
import Share from '$components/Share/Share.svelte';
45
import General from '$components/icons/GeneralIcon.svelte';
56
import Politician from '$components/icons/PoliticianIcon.svelte';
67
import Vote from '$components/icons/VoteIcon.svelte';
78
import PartyDetail from '$components/politicians/PartyDetail.svelte';
89
import PositionStatus from '$components/politicians/PositionStatus.svelte';
910
import Section from '$components/politicians/Section.svelte';
10-
import Share from '$components/Share/Share.svelte';
1111
import VotingResultTag from '$components/politicians/VotingResultTag.svelte';
1212
import { Breadcrumb, BreadcrumbItem, Button, InlineNotification } from 'carbon-components-svelte';
1313
import ArrowRight from 'carbon-icons-svelte/lib/ArrowRight.svelte';
1414
import ArrowUpRight from 'carbon-icons-svelte/lib/ArrowUpRight.svelte';
15-
import Download from 'carbon-icons-svelte/lib/Download.svelte';
16-
import TableSplit from 'carbon-icons-svelte/lib/TableSplit.svelte';
1715
import scrollama from 'scrollama';
1816
import { onMount } from 'svelte';
1917

tailwind.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export default {
4646
'text-02': '#6F6F6F',
4747
'text-03': '#A8A8A8',
4848
'text-04': '#FFFFFF',
49+
'text-primary': '#161616',
4950
'text-error': '#C72502',
5051
'decorative-01': '#E0E0E0',
5152
'ui-01': '#F4F4F4',

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1541,6 +1541,11 @@ data-urls@^3.0.2:
15411541
whatwg-mimetype "^3.0.0"
15421542
whatwg-url "^11.0.0"
15431543

1544+
dayjs@^1.11.10:
1545+
version "1.11.10"
1546+
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0"
1547+
integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==
1548+
15441549
debug@2.6.9:
15451550
version "2.6.9"
15461551
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"

0 commit comments

Comments
 (0)