Skip to content

Commit 2bca41e

Browse files
authored
feat: add selector component (#59)
* feat: add selector component * feat: improve selector + add flag-icon lib * feat: improve selector + doc * feat: add utility function to get language with country name * feat: test page for language selectors * chore: add Selector Story * chore: clean test page * fix: types
1 parent 31d5ff8 commit 2bca41e

File tree

8 files changed

+217
-12
lines changed

8 files changed

+217
-12
lines changed

infrastructure/eid-wallet/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@tauri-apps/api": "^2",
2222
"@tauri-apps/plugin-opener": "^2",
2323
"clsx": "^2.1.1",
24+
"flag-icons": "^7.3.2",
2425
"tailwind-merge": "^3.0.2"
2526
},
2627
"devDependencies": {

infrastructure/eid-wallet/src/app.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@import 'tailwindcss';
2+
@import 'flag-icons/css/flag-icons.min.css';
23
@plugin "daisyui" {
34
themes: false; /* Disable built-in themes */
45
darktheme: 'light';
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script context="module">
2+
export { BasicContent, WithIconContent }
3+
</script>
4+
5+
{#snippet BasicContent()}
6+
Choice
7+
{/snippet}
8+
9+
{#snippet WithIconContent()}
10+
<div
11+
class="rounded-full fi fis fi-gb scale-150 mr-12 outline-8 outline-gray-900"
12+
></div>
13+
{/snippet}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import Selector from './Selector.svelte'
2+
import {
3+
BasicContent,
4+
WithIconContent,
5+
} from './Selector.stories.snippet.svelte'
6+
import type { ComponentProps } from 'svelte'
7+
8+
export default {
9+
title: 'UI/Selector',
10+
component: Selector,
11+
tags: ['autodocs'],
12+
render: (args: {
13+
Component: Selector
14+
props: ComponentProps<typeof Selector>
15+
}) => ({
16+
Component: Selector,
17+
props: args,
18+
}),
19+
}
20+
21+
export const WithIcon = {
22+
render: () => ({
23+
Component: Selector,
24+
props: {
25+
id: 'option-1',
26+
name: 'lang',
27+
value: 'option-1',
28+
selected: 'option-1',
29+
icon: WithIconContent,
30+
children: BasicContent,
31+
},
32+
}),
33+
}
34+
35+
export const WithoutIcon = {
36+
render: () => ({
37+
Component: Selector,
38+
props: {
39+
id: 'option-1',
40+
name: 'lang',
41+
value: 'option-1',
42+
selected: 'option-1',
43+
children: BasicContent,
44+
},
45+
}),
46+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<script lang="ts">
2+
import { cn } from '$lib/utils'
3+
import { Tick01Icon } from '@hugeicons/core-free-icons'
4+
import { HugeiconsIcon } from '@hugeicons/svelte'
5+
import type { HTMLLabelAttributes } from 'svelte/elements'
6+
import { fade } from 'svelte/transition'
7+
8+
interface ISelectorProps extends HTMLLabelAttributes {
9+
id: string
10+
name: string
11+
value: string
12+
icon?: (id: string) => any
13+
selected?: string
14+
children?: () => any
15+
}
16+
17+
let {
18+
id,
19+
name,
20+
value,
21+
icon = undefined,
22+
selected = $bindable(),
23+
children = undefined,
24+
...restProps
25+
}: ISelectorProps = $props()
26+
</script>
27+
28+
<label
29+
{...restProps}
30+
for={id}
31+
class={cn(
32+
['flex w-full justify-between items-center py-4', restProps.class].join(' ')
33+
)}
34+
>
35+
<div class="flex">
36+
<div class="capitalize flex items-center">
37+
<input
38+
type="radio"
39+
{id}
40+
{name}
41+
{value}
42+
class="appearance-none"
43+
bind:group={selected}
44+
/>
45+
{#if icon}
46+
<div>{@render icon(id)}</div>
47+
{/if}
48+
{#if children}
49+
{@render children()}
50+
{/if}
51+
</div>
52+
</div>
53+
{#if selected === value}
54+
<div in:fade={{ duration: 150, delay: 0 }} class="overflow-hidden">
55+
<HugeiconsIcon
56+
color="var(--color-white)"
57+
icon={Tick01Icon}
58+
className="bg-primary-900 rounded-full w-6 h-6"
59+
/>
60+
</div>
61+
{/if}
62+
</label>
63+
64+
<!--
65+
@component
66+
export default Selector
67+
@description
68+
A radio button with an icon and a label
69+
@props
70+
- id: string
71+
- name: string
72+
- value: string
73+
- icon: (id: string) => any
74+
- selected: string
75+
- children: () => any
76+
@slots
77+
- default: The label of the radio button
78+
@example
79+
```svelte
80+
<script lang="ts">
81+
import Selector from '$lib/ui/Selector/Selector.svelte'
82+
import { getLanguageWithCountry } from '$lib/utils/getLanguage'
83+
import { writable } from 'svelte/store'
84+
85+
const AVAILABLE_LANGUAGES = ['en-GB', 'es', 'de', 'fr', 'ru']
86+
87+
const selectors = AVAILABLE_LANGUAGES.map((locale) => {
88+
const { code, name } = getLanguageWithCountry(locale)
89+
90+
return {
91+
id: code,
92+
value: name,
93+
checked: locale === 'en-GB',
94+
}
95+
})
96+
97+
let selected = writable(selectors[0].value)
98+
</script>
99+
100+
<h1 class="text-2xl font-bold">Select your language</h1>
101+
102+
<fieldset class="mx-8 flex flex-col gap-4 mt-12">
103+
{#each selectors as selector}
104+
{@const { id, value, checked } = selector}
105+
106+
<Selector {id} name="lang" {value} bind:selected={$selected}>
107+
{#snippet icon(id: string)}
108+
<div
109+
class="rounded-full fi fis fi-{id} scale-150 mr-12 outline-8 outline-gray-900"
110+
></div>
111+
{/snippet}
112+
{value}
113+
</Selector>
114+
{/each}
115+
</fieldset>
116+
```
117+
-->
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Get the language name with the country name in parentheses
3+
* @param {string} locale - The locale code
4+
* @returns {object} - The language code and name
5+
*
6+
* @example
7+
* getLanguageWithCountry('en-GB') // { code: 'gb', name: 'English (United Kingdom)' }
8+
* getLanguageWithCountry('es') // { code: 'es', name: 'Spanish' }
9+
*/
10+
export const getLanguageWithCountry = (locale: string) => {
11+
const parts = locale.split('-')
12+
const langCode = parts[0]
13+
const countryCode = parts[1]?.toLowerCase() || ''
14+
15+
return {
16+
code: countryCode || langCode,
17+
name:
18+
new Intl.DisplayNames(['en'], { type: 'language' }).of(langCode) +
19+
(countryCode
20+
? ` (${new Intl.DisplayNames(['en'], { type: 'region' }).of(countryCode.toUpperCase())})`
21+
: ''),
22+
}
23+
}
Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
<script lang="ts">
2-
3-
import { Drawer } from "$lib/ui";
4-
let isPaneOpen = $state(false);
2+
import { Drawer } from '$lib/ui'
3+
let isPaneOpen = $state(false)
54
</script>
65

7-
<button class="btn btn-soft" onclick={() => isPaneOpen = true}>Open</button>
8-
9-
10-
6+
<button class="btn btn-soft" onclick={() => (isPaneOpen = true)}>Open</button>
117

128
<Drawer bind:isPaneOpen isCancelRequired={true}>
13-
<div class="bg-red-300">aslkfamfoasdiownednciaosndoasfnas </div>
14-
<div>asfnladmfpamsfl asd</div>
15-
<div>aslkfasf;,;</div>
16-
<button class="btn btn-soft" >Open</button>
17-
<button class="btn btn-soft">Open</button>
9+
<div class="bg-red-300">aslkfamfoasdiownednciaosndoasfnas</div>
10+
<div>asfnladmfpamsfl asd</div>
11+
<div>aslkfasf;,;</div>
12+
<button class="btn btn-soft">Open</button>
13+
<button class="btn btn-soft">Open</button>
1814
</Drawer>

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)