Skip to content

Commit 6055c73

Browse files
committed
feat(Card): add remaining card features
Also adds new CountBadge and InfoIcon utility components
1 parent c28f113 commit 6055c73

File tree

12 files changed

+225
-4
lines changed

12 files changed

+225
-4
lines changed

src/lib/_style/_fonts.sass

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646
=fontColorDark
4747
color: var(--clr-primary-dark)
4848

49+
50+
=fontColorMedium
51+
color: var(--clr-primary-medium)
52+
4953
=fontColorLight
5054
color: var(--clr-primary-white)
5155

src/lib/card/Card.module.sass

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.card
2+
position: relative
23
border: 1px solid var(--clr-primary-dark-10)
34
border-radius: 6px
45
padding: 20px
@@ -9,7 +10,6 @@
910
outline: 2px solid var(--clr-signal-focused)
1011
outline-offset: -1px
1112

12-
1313
&.link
1414
cursor: pointer
1515

@@ -20,8 +20,25 @@
2020
border: 1px solid var(--clr-primary-dark-10)
2121
background: var(--clr-primary-dark-10-faded)
2222

23-
2423
&.disabled
2524
opacity: 0.5
2625
pointer-events: none
2726
cursor: not-allowed
27+
28+
.infoIcon
29+
position: absolute
30+
top: 4px
31+
right: 4px
32+
33+
.infoText
34+
position: absolute
35+
top: 7px
36+
right: 12px
37+
+fontBody
38+
+fontColorMedium
39+
+fontSizeXs
40+
41+
.countBadge
42+
position: absolute
43+
right: 4px
44+
bottom: 4px

src/lib/card/Card.stories.svelte

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import {Card} from '$lib';
44
import {defineMeta} from '@storybook/addon-svelte-csf';
55
6-
76
const {Story} = defineMeta({
87
title: 'Handson/Card',
98
component: Card,
@@ -18,6 +17,16 @@
1817
{/snippet}
1918
<Story name="Generic" args={{children: content}}/>
2019
<Story name="Disabled" args={{disabled: true, link: 'https://hawk.de', linkTarget: '_blank', children: content}}/>
20+
<Story name="Info Action"
21+
args={{
22+
oninfoclick: fn(),
23+
infoTitle: 'Get more information',
24+
children: content,
25+
// Also enable the link, to check if the info icon does not interfere with it
26+
link: 'https://hawk.de',
27+
linkTarget: '_blank'}}/>
28+
<Story name="Info Text" args={{infoText: '11:33 Uhr', children: content}}/>
29+
<Story name="Count" args={{count: 3, countTitle: 'Zahl der neuen Beiträge', children: content}}/>
2130

2231
{#snippet linkCardContent()}
2332
You can assign a link to a card, which will make it clickable and focusable like a button.

src/lib/card/Card.svelte

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import type {HTMLAttributes} from 'svelte/elements';
33
import style from './Card.module.sass';
44
import {mergeProps} from '$lib/util/mergeProps.js';
5+
import InfoIcon from '$lib/util/infoIcon/InfoIcon.svelte';
6+
import CountBadge from '$lib/util/countBadge/CountBadge.svelte';
57
68
export interface Props extends HTMLAttributes<HTMLDivElement> {
79
/**
@@ -18,6 +20,37 @@
1820
* If true, the card will be disabled and not clickable.
1921
*/
2022
disabled?: boolean;
23+
24+
/**
25+
* If given, an "info" icon will be shown in the top right corner of the card.
26+
*/
27+
oninfoclick?: () => void;
28+
29+
/**
30+
* An optional title to show when hovering the info icon.
31+
* This is only relevant if `oninfoclick` is set.
32+
*/
33+
infoTitle?: string;
34+
35+
/**
36+
* An optional text to show in the top right corner of the card.
37+
* Note, you can only set either `oninfoclick` or `infoText`, not both.
38+
* If both are present, `oninfoclick` will take precedence and `infoText` will be ignored.
39+
*/
40+
infoText?: string;
41+
42+
/**
43+
* An optional count to show in the bottom right corner of the card.
44+
* This is useful to show the number of items related to the card.
45+
*/
46+
count?: number;
47+
48+
/**
49+
* An optional title to show when hovering the count badge.
50+
* This is useful to provide additional context for the count.
51+
* This is only relevant if `count` is set.
52+
*/
53+
countTitle?: string;
2154
}
2255
2356
let {
@@ -26,6 +59,11 @@
2659
linkTarget,
2760
onclick: givenOnClick,
2861
disabled = false,
62+
oninfoclick,
63+
infoTitle,
64+
infoText,
65+
count,
66+
countTitle,
2967
...restProps
3068
}: Props = $props();
3169
@@ -59,5 +97,24 @@
5997
},
6098
restProps
6199
)}>
100+
{#if oninfoclick}
101+
<InfoIcon
102+
class={style.infoIcon}
103+
onclick={(e) => {
104+
e.stopPropagation();
105+
oninfoclick();
106+
}}
107+
onkeydown={(e) => e.stopPropagation()}
108+
title={infoTitle}/>
109+
{:else if infoText}
110+
<span class={style.infoText}>
111+
{infoText}
112+
</span>
113+
{/if}
114+
62115
{@render children?.()}
116+
117+
{#if count}
118+
<CountBadge count={count} class={style.countBadge} title={countTitle}/>
119+
{/if}
63120
</div>

src/lib/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,5 @@ export {default as Card} from '$lib/card/Card.svelte';
2828
export {default as CardWithImage} from '$lib/card/CardWithImage.svelte';
2929
export {default as CardRadioGroup} from '$lib/radio-card/RadioCardGroup.svelte';
3030
export {default as CardRadio} from '$lib/radio-card/RadioCard.svelte';
31+
export {default as CountBadge} from '$lib/util/countBadge/CountBadge.svelte';
32+
export {default as InfoIcon} from '$lib/util/infoIcon/InfoIcon.svelte';

src/lib/input/InputPassword.stories.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script module>
22
import {defineMeta} from '@storybook/addon-svelte-csf';
3-
import InputPassword from './InputPassword.svelte';
3+
import {InputPassword} from '$lib';
44
55
const {Story} = defineMeta({
66
title: 'Handson/InputPassword',
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.badge
2+
display: inline-block
3+
width: fit-content
4+
+fontBody
5+
+fontSizeXs
6+
+fontColorLight
7+
padding: 2px 6px
8+
background: var(--count-badge-gradient, linear-gradient(90deg, var(--clr-gradient-start) 0%, var(--clr-gradient-end) 100%))
9+
border-radius: 20px
10+
height: 18px
11+
line-height: 15px
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script module>
2+
import {defineMeta} from '@storybook/addon-svelte-csf';
3+
import {CountBadge} from '$lib';
4+
5+
const {Story} = defineMeta({
6+
title: 'Handson/Util/CountBadge',
7+
component: CountBadge,
8+
tags: ['autodocs'],
9+
argTypes: {},
10+
args: {}
11+
});
12+
</script>
13+
14+
<Story name="Generic" args={{}} parameters={{controls: {disable: true}}}>
15+
<div style="display: flex; gap: 1rem; flex-direction: column">
16+
<CountBadge count={5}/>
17+
<CountBadge count={-3}/>
18+
<CountBadge count={0}/>
19+
<CountBadge count={100}/>
20+
<CountBadge count={10000}/>
21+
</div>
22+
</Story>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script lang="ts">
2+
import type {HTMLAttributes} from 'svelte/elements';
3+
import {mergeProps} from '$lib/util/mergeProps.js';
4+
import style from 'CountBadge.module.sass';
5+
6+
interface Props extends HTMLAttributes<HTMLSpanElement> {
7+
/**
8+
* The count to display in the badge.
9+
*/
10+
count: number;
11+
}
12+
13+
const {
14+
count = 0,
15+
...restProps
16+
}: Props = $props();
17+
18+
const countFormatted = $derived.by(() => {
19+
const absCount = Math.abs(count);
20+
return absCount >= 1000 ? absCount.toLocaleString() : absCount.toString();
21+
});
22+
</script>
23+
24+
<span {...mergeProps(
25+
{
26+
class: [style.badge]
27+
},
28+
restProps
29+
)}>{countFormatted}</span>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.button
2+
display: inline-block
3+
padding: 0
4+
margin: 0
5+
background: none
6+
border: 0
7+
outline: 0
8+
cursor: pointer
9+
10+
&:focus
11+
.icon
12+
color: var(--info-icon-color-focus, var(--clr-signal-focused))
13+
14+
.icon
15+
color: var(--info-icon-color, var(--clr-primary-medium))

0 commit comments

Comments
 (0)