@ks-digital/designsystem-angular 0.0.1-alpha.25 → 0.0.1-alpha.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/ks-digital-designsystem-angular.mjs +1100 -0
- package/fesm2022/ks-digital-designsystem-angular.mjs.map +1 -0
- package/index.d.ts +333 -0
- package/package.json +15 -20
- package/.storybook/customTheme.ts +0 -15
- package/.storybook/default-args.ts +0 -18
- package/.storybook/main.ts +0 -27
- package/.storybook/manager.ts +0 -10
- package/.storybook/preview-head.html +0 -16
- package/.storybook/preview.ts +0 -70
- package/.storybook/themes.ts +0 -9
- package/.storybook/tsconfig.json +0 -16
- package/.storybook/vite.config.mts +0 -5
- package/eslint.config.mjs +0 -28
- package/ng-package.json +0 -9
- package/project.json +0 -81
- package/src/components/alert/alert.mdx +0 -46
- package/src/components/alert/alert.spec.ts +0 -33
- package/src/components/alert/alert.stories.ts +0 -138
- package/src/components/alert/alert.ts +0 -46
- package/src/components/alert/index.ts +0 -1
- package/src/components/button/button.mdx +0 -40
- package/src/components/button/button.spec.ts +0 -86
- package/src/components/button/button.stories.ts +0 -123
- package/src/components/button/button.ts +0 -60
- package/src/components/button/index.ts +0 -1
- package/src/components/card/card-block.ts +0 -10
- package/src/components/card/card.mdx +0 -100
- package/src/components/card/card.spec.ts +0 -70
- package/src/components/card/card.stories.ts +0 -101
- package/src/components/card/card.ts +0 -44
- package/src/components/card/index.ts +0 -2
- package/src/components/checkbox/README.md +0 -13
- package/src/components/checkbox/checkbox.mdx +0 -50
- package/src/components/checkbox/checkbox.spec.ts +0 -21
- package/src/components/checkbox/checkbox.stories.ts +0 -182
- package/src/components/checkbox/index.ts +0 -0
- package/src/components/colors.ts +0 -36
- package/src/components/common-inputs.ts +0 -30
- package/src/components/details/controlled-details.ts +0 -63
- package/src/components/details/details-content.ts +0 -7
- package/src/components/details/details-summary.ts +0 -7
- package/src/components/details/details.mdx +0 -89
- package/src/components/details/details.spec.ts +0 -56
- package/src/components/details/details.stories.ts +0 -129
- package/src/components/details/details.ts +0 -69
- package/src/components/details/index.ts +0 -3
- package/src/components/field/field-counter.ts +0 -56
- package/src/components/field/field-description.ts +0 -10
- package/src/components/field/field-error.ts +0 -13
- package/src/components/field/field-observer.ts +0 -121
- package/src/components/field/field-state.ts +0 -21
- package/src/components/field/field.mdx +0 -40
- package/src/components/field/field.spec.ts +0 -131
- package/src/components/field/field.stories.ts +0 -98
- package/src/components/field/field.ts +0 -70
- package/src/components/field/index.ts +0 -3
- package/src/components/fieldset/fieldset-description.ts +0 -8
- package/src/components/fieldset/fieldset-legend.ts +0 -11
- package/src/components/fieldset/fieldset.spec.ts +0 -80
- package/src/components/fieldset/fieldset.ts +0 -11
- package/src/components/fieldset/index.ts +0 -3
- package/src/components/input/index.ts +0 -1
- package/src/components/input/input.mdx +0 -11
- package/src/components/input/input.spec.ts +0 -25
- package/src/components/input/input.stories.ts +0 -72
- package/src/components/input/input.ts +0 -67
- package/src/components/label/index.ts +0 -1
- package/src/components/label/label.ts +0 -17
- package/src/components/paragraph/index.ts +0 -1
- package/src/components/paragraph/paragraph.ts +0 -10
- package/src/components/popover/controlled-popover.ts +0 -62
- package/src/components/popover/index.ts +0 -1
- package/src/components/popover/popover.mdx +0 -81
- package/src/components/popover/popover.spec.ts +0 -143
- package/src/components/popover/popover.stories.ts +0 -63
- package/src/components/popover/popover.ts +0 -186
- package/src/components/radio/radio.mdx +0 -117
- package/src/components/radio/radio.stories.ts +0 -226
- package/src/components/search/index.ts +0 -4
- package/src/components/search/search-button.ts +0 -35
- package/src/components/search/search-clear.ts +0 -57
- package/src/components/search/search-input.ts +0 -18
- package/src/components/search/search.mdx +0 -56
- package/src/components/search/search.spec.ts +0 -48
- package/src/components/search/search.stories.ts +0 -205
- package/src/components/search/search.ts +0 -50
- package/src/components/spinner/index.ts +0 -1
- package/src/components/spinner/spinner.mdx +0 -24
- package/src/components/spinner/spinner.spec.ts +0 -13
- package/src/components/spinner/spinner.stories.ts +0 -54
- package/src/components/spinner/spinner.ts +0 -62
- package/src/components/switch/switch.mdx +0 -82
- package/src/components/switch/switch.stories.ts +0 -94
- package/src/components/textarea/textarea.mdx +0 -14
- package/src/components/textarea/textarea.stories.ts +0 -52
- package/src/components/validation-message/index.ts +0 -1
- package/src/components/validation-message/validation-message.ts +0 -11
- package/src/index.ts +0 -14
- package/src/test-setup.ts +0 -12
- package/src/utils/log-if-devmode.ts +0 -13
- package/src/utils/random-id.ts +0 -3
- package/tsconfig.json +0 -34
- package/tsconfig.lib.json +0 -28
- package/tsconfig.lib.prod.json +0 -9
- package/tsconfig.spec.json +0 -30
- package/vite.config.mts +0 -35
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @angular-eslint/no-input-rename */
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
booleanAttribute,
|
|
5
|
-
Component,
|
|
6
|
-
computed,
|
|
7
|
-
effect,
|
|
8
|
-
ElementRef,
|
|
9
|
-
input,
|
|
10
|
-
output,
|
|
11
|
-
signal,
|
|
12
|
-
viewChild,
|
|
13
|
-
} from '@angular/core'
|
|
14
|
-
import {
|
|
15
|
-
autoUpdate,
|
|
16
|
-
computePosition,
|
|
17
|
-
flip,
|
|
18
|
-
MiddlewareState,
|
|
19
|
-
offset,
|
|
20
|
-
Placement,
|
|
21
|
-
shift,
|
|
22
|
-
} from '@floating-ui/dom'
|
|
23
|
-
import { Color, SeverityColors } from '../colors'
|
|
24
|
-
import { Size } from '../common-inputs'
|
|
25
|
-
|
|
26
|
-
@Component({
|
|
27
|
-
selector: 'ksd-popover',
|
|
28
|
-
|
|
29
|
-
template: `
|
|
30
|
-
<div
|
|
31
|
-
#myPopover
|
|
32
|
-
popover="manual"
|
|
33
|
-
class="ds-popover"
|
|
34
|
-
data-testid="popover"
|
|
35
|
-
[id]="popoverId()"
|
|
36
|
-
[attr.data-size]="dataSize()"
|
|
37
|
-
[attr.data-color]="dataColor()"
|
|
38
|
-
[attr.data-variant]="variant()"
|
|
39
|
-
>
|
|
40
|
-
@if (controlledOpen()) {
|
|
41
|
-
<ng-content />
|
|
42
|
-
}
|
|
43
|
-
</div>
|
|
44
|
-
`,
|
|
45
|
-
imports: [],
|
|
46
|
-
})
|
|
47
|
-
export class Popover {
|
|
48
|
-
// use popoverId instead of id since id will be put on the angular element and not the popover-div
|
|
49
|
-
readonly popoverId = input.required<string>()
|
|
50
|
-
readonly placement = input<Placement>('top')
|
|
51
|
-
readonly autoPlacement = input(true, { transform: booleanAttribute })
|
|
52
|
-
|
|
53
|
-
// for controlled component
|
|
54
|
-
readonly open = input(undefined, { transform: booleanAttribute })
|
|
55
|
-
/*
|
|
56
|
-
the naming here is different from Designsystemet
|
|
57
|
-
since we need to use outputs for onOpen and onClose
|
|
58
|
-
*/
|
|
59
|
-
readonly triggeredClose = output()
|
|
60
|
-
readonly triggeredOpen = output()
|
|
61
|
-
|
|
62
|
-
protected readonly internalOpen = signal(false)
|
|
63
|
-
protected readonly controlledOpen = computed(
|
|
64
|
-
() => this.open() ?? this.internalOpen(),
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
readonly variant = input<'tinted' | 'default'>('default')
|
|
68
|
-
readonly dataSize = input<Size>('md', { alias: 'data-size' })
|
|
69
|
-
readonly dataColor = input<Color | SeverityColors>('neutral', {
|
|
70
|
-
alias: 'data-color',
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
private popoverRef = viewChild<ElementRef>('myPopover')
|
|
74
|
-
|
|
75
|
-
// enable controlled component
|
|
76
|
-
controlledComponent = effect((onCleanup) => {
|
|
77
|
-
const popover = this.popoverRef()?.nativeElement
|
|
78
|
-
const handleClick = (event: MouseEvent) => {
|
|
79
|
-
const el = event.target as Element | null
|
|
80
|
-
const isTrigger = el?.closest?.(`[popovertarget="${this.popoverId()}"]`)
|
|
81
|
-
const isOutside = !isTrigger && !popover?.contains(el as Node)
|
|
82
|
-
|
|
83
|
-
if (isTrigger) {
|
|
84
|
-
event.preventDefault() // Prevent native Popover API
|
|
85
|
-
this.internalOpen.update((open) => !open)
|
|
86
|
-
this.triggeredOpen.emit()
|
|
87
|
-
}
|
|
88
|
-
if (isOutside) {
|
|
89
|
-
this.internalOpen.set(false)
|
|
90
|
-
this.triggeredClose.emit()
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const handleKeydown = (event: KeyboardEvent) => {
|
|
95
|
-
if (event.key !== 'Escape' || !this.controlledOpen()) return
|
|
96
|
-
event.preventDefault() // Prevent closing fullscreen in Safari
|
|
97
|
-
this.internalOpen.set(false)
|
|
98
|
-
this.triggeredClose.emit()
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
popover?.togglePopover?.(this.controlledOpen())
|
|
102
|
-
document.addEventListener('click', handleClick, true) // Use capture to execute before React event API
|
|
103
|
-
document.addEventListener('keydown', handleKeydown)
|
|
104
|
-
|
|
105
|
-
onCleanup(() => {
|
|
106
|
-
document.removeEventListener('click', handleClick, true)
|
|
107
|
-
document.removeEventListener('keydown', handleKeydown)
|
|
108
|
-
})
|
|
109
|
-
}, {})
|
|
110
|
-
|
|
111
|
-
positionPopover = effect(() => {
|
|
112
|
-
const popover = this.popoverRef()?.nativeElement
|
|
113
|
-
|
|
114
|
-
const trigger = document.querySelector(
|
|
115
|
-
`[popovertarget="${this.popoverId()}"]`,
|
|
116
|
-
)
|
|
117
|
-
const placement = this.placement()
|
|
118
|
-
|
|
119
|
-
if (popover && trigger && this.controlledOpen()) {
|
|
120
|
-
autoUpdate(trigger, popover, () => {
|
|
121
|
-
computePosition(trigger, popover, {
|
|
122
|
-
placement,
|
|
123
|
-
strategy: 'fixed',
|
|
124
|
-
middleware: [
|
|
125
|
-
offset((data) => {
|
|
126
|
-
// get pseudo element arrow size
|
|
127
|
-
const styles = getComputedStyle(
|
|
128
|
-
data.elements.floating,
|
|
129
|
-
'::before',
|
|
130
|
-
)
|
|
131
|
-
return parseFloat(styles.height)
|
|
132
|
-
}),
|
|
133
|
-
...(this.autoPlacement()
|
|
134
|
-
? [flip({ fallbackAxisSideDirection: 'start' }), shift()]
|
|
135
|
-
: []),
|
|
136
|
-
this.arrowPseudoElement,
|
|
137
|
-
],
|
|
138
|
-
}).then(({ x, y }) => {
|
|
139
|
-
popover.style.translate = `${x}px ${y}px`
|
|
140
|
-
})
|
|
141
|
-
})
|
|
142
|
-
}
|
|
143
|
-
}, {})
|
|
144
|
-
|
|
145
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
146
|
-
arrowPseudoElement: any = {
|
|
147
|
-
name: 'ArrowPseudoElement',
|
|
148
|
-
fn(data: MiddlewareState) {
|
|
149
|
-
const { elements, rects, placement } = data
|
|
150
|
-
|
|
151
|
-
let arrowX = `${Math.round(rects.reference.width / 2 + rects.reference.x - data.x)}px`
|
|
152
|
-
let arrowY = `${Math.round(rects.reference.height / 2 + rects.reference.y - data.y)}px`
|
|
153
|
-
|
|
154
|
-
if (rects.reference.width > rects.floating.width) {
|
|
155
|
-
arrowX = `${Math.round(rects.floating.width / 2)}px`
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (rects.reference.height > rects.floating.height) {
|
|
159
|
-
arrowY = `${Math.round(rects.floating.height / 2)}px`
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
switch (placement.split('-')[0]) {
|
|
163
|
-
case 'top':
|
|
164
|
-
arrowY = '100%'
|
|
165
|
-
break
|
|
166
|
-
case 'right':
|
|
167
|
-
arrowX = '0'
|
|
168
|
-
break
|
|
169
|
-
case 'bottom':
|
|
170
|
-
arrowY = '0'
|
|
171
|
-
break
|
|
172
|
-
case 'left':
|
|
173
|
-
arrowX = '100%'
|
|
174
|
-
break
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
elements.floating.setAttribute(
|
|
178
|
-
'data-placement',
|
|
179
|
-
placement.split('-')[0] as string,
|
|
180
|
-
) // We only need top/left/right/bottom
|
|
181
|
-
elements.floating.style.setProperty('--ds-popover-arrow-x', arrowX)
|
|
182
|
-
elements.floating.style.setProperty('--ds-popover-arrow-y', arrowY)
|
|
183
|
-
return data
|
|
184
|
-
},
|
|
185
|
-
}
|
|
186
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Meta,
|
|
3
|
-
Canvas,
|
|
4
|
-
Controls,
|
|
5
|
-
Primary,
|
|
6
|
-
ArgTypes,
|
|
7
|
-
} from '@storybook/addon-docs/blocks'
|
|
8
|
-
import * as RadioStories from './radio.stories'
|
|
9
|
-
|
|
10
|
-
<Meta of={RadioStories} />
|
|
11
|
-
|
|
12
|
-
# Radio
|
|
13
|
-
|
|
14
|
-
`Radio` er et alternativ brukeren kan velge. Bruk flere `Radio` for å vise en liste med alternativer. Brukerne kan bytte mellom alternativene, men kan kun velge ett.
|
|
15
|
-
|
|
16
|
-
<Primary />
|
|
17
|
-
<Controls />
|
|
18
|
-
|
|
19
|
-
## Bruk
|
|
20
|
-
|
|
21
|
-
Bruk [`Fieldset`](/docs/komponenter-fieldset--docs) for å visuelt gruppere flere alternativer.
|
|
22
|
-
|
|
23
|
-
```tsx
|
|
24
|
-
<fieldset ksd-fieldset>
|
|
25
|
-
<legend ksd-fieldset-legend>Er du over 18 år?</legend>
|
|
26
|
-
|
|
27
|
-
<ksd-field>
|
|
28
|
-
<input type="radio" ksd-input label="Ja" name="alternativ" />
|
|
29
|
-
</ksd-field>
|
|
30
|
-
|
|
31
|
-
<ksd-field>
|
|
32
|
-
<input type="radio" ksd-input label="Nei" name="alternativ" />
|
|
33
|
-
</ksd-field>
|
|
34
|
-
</fieldset>
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## Eksempler
|
|
38
|
-
|
|
39
|
-
### Enkel radio
|
|
40
|
-
|
|
41
|
-
`Radio` skal alltid ha en `label`. Når du bruker `label` fra en annen plass, husk å bruke `aria-label` eller `aria-labelledby`.
|
|
42
|
-
|
|
43
|
-
<Canvas of={RadioStories.AriaLabel} />
|
|
44
|
-
|
|
45
|
-
### Gruppering av flere alternativ
|
|
46
|
-
|
|
47
|
-
Bruk `Fieldset` rundt, og `name` på `Radio`, for å gruppere.
|
|
48
|
-
|
|
49
|
-
<Canvas of={RadioStories.Group} />
|
|
50
|
-
|
|
51
|
-
### Feilmelding
|
|
52
|
-
|
|
53
|
-
Bruk `error` på `Fieldset` for å vise feilmelding.
|
|
54
|
-
|
|
55
|
-
Her må vi bruke `Fieldset`, fordi den aktiverer riktig stil og sørger for at innholdet har de riktige attributtene for tilgjengelighet.
|
|
56
|
-
|
|
57
|
-
<Canvas of={RadioStories.WithError} />
|
|
58
|
-
|
|
59
|
-
### Readonly
|
|
60
|
-
|
|
61
|
-
Felter med `readonly`-attributtet er med i tabrekkefølgen. Brukerne kan kopiere innholdet men ikke redigere det. Informasjon blir med når skjemaet sendes inn.
|
|
62
|
-
|
|
63
|
-
`readonly`-felter kan være forvirrende for noen brukere. Ikke alle vil skjønne hvorfor de ikke får til å endre innholdet i feltet. Vi anbefaler derfor å unngå `readonly` så langt det lar seg gjøre.
|
|
64
|
-
|
|
65
|
-
<Canvas of={RadioStories.ReadOnly} />
|
|
66
|
-
|
|
67
|
-
### Inline
|
|
68
|
-
|
|
69
|
-
`Radio` skal som hovedregel _ikke_ plasseres på samme linje. Men, hvis du har kun to alternativer med korte tekster som "Ja" og "Nei", kan du vurdere om de bør plasseres ved siden av hverandre.
|
|
70
|
-
|
|
71
|
-
<Canvas of={RadioStories.Inline} />
|
|
72
|
-
|
|
73
|
-
## Retningslinjer
|
|
74
|
-
|
|
75
|
-
Vi bruker `Radio` når vi skal gi brukerne mulighet til å velge kun ett alternativ. Skal de kunne velge flere, bruker du [`Checkbox`](/docs/komponenter-checkbox--docs).
|
|
76
|
-
|
|
77
|
-
Vi bør ikke ha mer enn syv alternativer i en gruppe. Trenger vi å gi brukerne flere valg, bør vi heller bruke [`Suggestion`](/docs/komponenter-suggestion--docs) eller [`Select`](/docs/komponenter-select--docs). Hvis vi bare skal gi brukerne ett valg, kan en [`Switch`](/docs/komponenter-switch--docs) eller [`Checkbox`](/docs/komponenter-checkbox--docs) passe bedre.
|
|
78
|
-
|
|
79
|
-
### Sortering
|
|
80
|
-
|
|
81
|
-
Alternativene skal som hovedregel sorteres alfabetisk. Velger du å presentere de mest aktuelle alternativene først kan det bli vanskelig å finne ønsket alternativ. Du kan også risikere å påvirke svaret, noe som er uheldig.
|
|
82
|
-
|
|
83
|
-
### Plassering
|
|
84
|
-
|
|
85
|
-
`Radio` skal som hovedregel plasseres vertikalt, av hensyn til lesbarhet. Det er enklere for brukere å skanne listen når alternativene ligger under hverandre. For brukere som trenger å forstørre (zoome inn) nettsiden for å se godt nok, er en horisontal liste ekstra vanskelig. Hvis vi bare har to valg med korte ledetekster, kan vi vise dem ved siden av hverandre.
|
|
86
|
-
|
|
87
|
-
I en [likert-skala (snl.no)](https://snl.no/Likert-skala) vil det også være naturlig å vise dem ved siden av hverandre. I en horisontal visning er det viktig at det kommer tydelig fram hvilken tekst som hører til hvilken boks.
|
|
88
|
-
|
|
89
|
-
### Unngå forhåndsvalgt alternativ
|
|
90
|
-
|
|
91
|
-
Tenk nøye over om du virkelig trenger å ha et forhåndsvalgt alternativ. Det kan ha motsatt effekt av det du ønsker, for eksempel at brukeren føler seg manipulert til å ta ett bestemt valg, eller ikke klarer å ta et bevisst valg selv.
|
|
92
|
-
|
|
93
|
-
## Tekst i komponenten
|
|
94
|
-
|
|
95
|
-
En gruppe med flere alternativer bør alltid ha en `legend` og eventuelt en `description` dersom hjelpetekst er nødvendig.
|
|
96
|
-
|
|
97
|
-
Radioknapper skal alltid ha en `label`. Den skal være så kort som mulig og hvert alternativ bør være formulert på samme måte. Radio-komponenten kan også ha `description`. Da kommer den rett under `label`.
|
|
98
|
-
|
|
99
|
-
## Tilgjengelighet
|
|
100
|
-
|
|
101
|
-
Pass på at det er nok luft rundt hver alternativknapp, slik at den er lett å velge også på berøringsskjermer.
|
|
102
|
-
|
|
103
|
-
Unngå å plassere alternativknapper ved siden av hverandre. Det gjør det vanskelig å lese. Det skaper også problemer for brukere som må forstørre (zoome) en nettside eller app for å se godt nok.
|
|
104
|
-
|
|
105
|
-
### Deaktiverte tilstander
|
|
106
|
-
|
|
107
|
-
Unngå deaktiverte tilstander om du kan. De har lav fargekontrast som er problematisk for noen brukere. `disabled` kan ikke møte kontrastkravene, for da kan brukeren tro at elementet er aktivt, prøve å trykke på det, og ikke skjønne hvorfor det ikke går. Nav har en god forklaring på [hvorfor deaktiverte tilstander er problematisk](https://aksel.nav.no/god-praksis/artikler/deaktiverte-tilstander) og hvilke alternativer som finnes.
|
|
108
|
-
|
|
109
|
-
### Tastaturnavigasjon
|
|
110
|
-
|
|
111
|
-
Merk at vi bruker piltastene til å velge alternativer i `Radio`-komponenten, ikke Tab-tasten.
|
|
112
|
-
|
|
113
|
-
- <kbd data-icon>↑</kbd> eller <kbd data-icon>←</kbd> går til forrige valg
|
|
114
|
-
- <kbd data-icon>↓</kbd> eller <kbd data-icon>→</kbd> går til neste valg
|
|
115
|
-
- <kbd>Space</kbd> går til det første alternativet i en gruppe med radioknapper,
|
|
116
|
-
når gruppa ikke har et forhåndsvalgt alternativ.
|
|
117
|
-
- <kbd>Tab</kbd> går til hele gruppa med alternativer.
|
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
argsToTemplate,
|
|
3
|
-
moduleMetadata,
|
|
4
|
-
type Meta,
|
|
5
|
-
type StoryObj,
|
|
6
|
-
} from '@storybook/angular'
|
|
7
|
-
import { CommonArgs } from '../../../.storybook/default-args'
|
|
8
|
-
import { Field } from '../field/field'
|
|
9
|
-
import { FieldDescription } from '../field/field-description'
|
|
10
|
-
import { Fieldset } from '../fieldset/fieldset'
|
|
11
|
-
import { FieldsetDescription } from '../fieldset/fieldset-description'
|
|
12
|
-
import { FieldsetLegend } from '../fieldset/fieldset-legend'
|
|
13
|
-
import { Input } from '../input/input'
|
|
14
|
-
import { Label } from '../label/label'
|
|
15
|
-
import { ValidationMessage } from '../validation-message'
|
|
16
|
-
|
|
17
|
-
type RadioArgs = CommonArgs & {
|
|
18
|
-
readonly: boolean
|
|
19
|
-
disabled: boolean
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const meta: Meta<Input> = {
|
|
23
|
-
component: Input,
|
|
24
|
-
title: 'Komponenter/Radio',
|
|
25
|
-
decorators: [
|
|
26
|
-
moduleMetadata({
|
|
27
|
-
imports: [
|
|
28
|
-
Label,
|
|
29
|
-
Field,
|
|
30
|
-
Input,
|
|
31
|
-
FieldDescription,
|
|
32
|
-
Fieldset,
|
|
33
|
-
FieldsetLegend,
|
|
34
|
-
FieldsetDescription,
|
|
35
|
-
ValidationMessage,
|
|
36
|
-
],
|
|
37
|
-
}),
|
|
38
|
-
],
|
|
39
|
-
}
|
|
40
|
-
export default meta
|
|
41
|
-
type Story = StoryObj<RadioArgs>
|
|
42
|
-
|
|
43
|
-
export const Preview: Story = {
|
|
44
|
-
args: {
|
|
45
|
-
readonly: false,
|
|
46
|
-
disabled: false,
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
render: (args) => ({
|
|
50
|
-
props: args,
|
|
51
|
-
template: `
|
|
52
|
-
<ksd-field>
|
|
53
|
-
<ksd-label>Label</ksd-label>
|
|
54
|
-
<input type="radio" ksd-input ${argsToTemplate(args)} />
|
|
55
|
-
<p ksd-field-description>Description</p>
|
|
56
|
-
</ksd-field>
|
|
57
|
-
`,
|
|
58
|
-
}),
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export const AriaLabel: Story = {
|
|
62
|
-
args: {
|
|
63
|
-
...Preview.args,
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
render: (args) => ({
|
|
67
|
-
props: args,
|
|
68
|
-
template: `
|
|
69
|
-
<input ksd-input type="radio" ${argsToTemplate(args)} aria-label="Radio" />
|
|
70
|
-
`,
|
|
71
|
-
}),
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export const Group: Story = {
|
|
75
|
-
args: {
|
|
76
|
-
...Preview.args,
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
render: (args) => ({
|
|
80
|
-
props: args,
|
|
81
|
-
template: `
|
|
82
|
-
<fieldset ksd-fieldset>
|
|
83
|
-
<legend ksd-fieldset-legend>Hvilken iskremsmak er best ? </legend>
|
|
84
|
-
<p ksd-fieldset-description>Velg din favorittsmak blant alternativene.</p>
|
|
85
|
-
<ksd-field>
|
|
86
|
-
<ksd-label>Vanilje</ksd-label>
|
|
87
|
-
<input type="radio" name="icecream" ksd-input ${argsToTemplate(args)} />
|
|
88
|
-
</ksd-field>
|
|
89
|
-
<ksd-field>
|
|
90
|
-
<ksd-label>Jordbær</ksd-label>
|
|
91
|
-
<input type="radio" name="icecream" ksd-input ${argsToTemplate(args)} />
|
|
92
|
-
</ksd-field>
|
|
93
|
-
<ksd-field>
|
|
94
|
-
<ksd-label>Sjokolade</ksd-label>
|
|
95
|
-
<input type="radio" name="icecream" ksd-input ${argsToTemplate(args)} />
|
|
96
|
-
</ksd-field>
|
|
97
|
-
<ksd-field>
|
|
98
|
-
<ksd-label>Jeg spiser ikke iskrem</ksd-label>
|
|
99
|
-
<input type="radio" name="icecream" ksd-input ${argsToTemplate(args)} />
|
|
100
|
-
</ksd-field>
|
|
101
|
-
</fieldset>
|
|
102
|
-
`,
|
|
103
|
-
}),
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export const WithError: Story = {
|
|
107
|
-
args: {
|
|
108
|
-
...Preview.args,
|
|
109
|
-
},
|
|
110
|
-
|
|
111
|
-
render: (args) => ({
|
|
112
|
-
props: args,
|
|
113
|
-
template: `
|
|
114
|
-
<fieldset ksd-fieldset>
|
|
115
|
-
<legend ksd-fieldset-legend>Hvilken bydel bor du i?</legend>
|
|
116
|
-
<p ksd-fieldset-description>Bergen er delt inn i åtte bydeler</p>
|
|
117
|
-
<ksd-field>
|
|
118
|
-
<ksd-label>Arna</ksd-label>
|
|
119
|
-
<input type="radio" name="city" ksd-input ${argsToTemplate(args)} />
|
|
120
|
-
</ksd-field>
|
|
121
|
-
<ksd-field>
|
|
122
|
-
<ksd-label>Bergenhus</ksd-label>
|
|
123
|
-
<input type="radio" name="city" ksd-input ${argsToTemplate(args)} />
|
|
124
|
-
</ksd-field>
|
|
125
|
-
<ksd-field>
|
|
126
|
-
<ksd-label>Fana</ksd-label>
|
|
127
|
-
<input type="radio" name="city" ksd-input ${argsToTemplate(args)} />
|
|
128
|
-
</ksd-field>
|
|
129
|
-
<ksd-field>
|
|
130
|
-
<ksd-label>Fyllingsdalen</ksd-label>
|
|
131
|
-
<input type="radio" name="city" ksd-input ${argsToTemplate(args)} />
|
|
132
|
-
</ksd-field>
|
|
133
|
-
<ksd-field>
|
|
134
|
-
<ksd-label>Laksevåg</ksd-label>
|
|
135
|
-
<input type="radio" name="city" ksd-input ${argsToTemplate(args)} />
|
|
136
|
-
</ksd-field>
|
|
137
|
-
<ksd-field>
|
|
138
|
-
<ksd-label>Ytrebygda</ksd-label>
|
|
139
|
-
<input type="radio" name="city" ksd-input ${argsToTemplate(args)} />
|
|
140
|
-
</ksd-field>
|
|
141
|
-
<ksd-field>
|
|
142
|
-
<ksd-label>Årstad</ksd-label>
|
|
143
|
-
<input type="radio" name="city" ksd-input ${argsToTemplate(args)} />
|
|
144
|
-
</ksd-field>
|
|
145
|
-
<ksd-field>
|
|
146
|
-
<ksd-label>Åsane</ksd-label>
|
|
147
|
-
<input type="radio" name="city" ksd-input ${argsToTemplate(args)} />
|
|
148
|
-
</ksd-field>
|
|
149
|
-
<p ksd-validation-message>Du må velge en bydel før du kan fortsette.</p>
|
|
150
|
-
`,
|
|
151
|
-
}),
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export const ReadOnly: Story = {
|
|
155
|
-
args: {
|
|
156
|
-
...Preview.args,
|
|
157
|
-
readonly: true,
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
render: (args) => ({
|
|
161
|
-
props: args,
|
|
162
|
-
template: `
|
|
163
|
-
<fieldset ksd-fieldset>
|
|
164
|
-
<legend ksd-fieldset-legend>Hvilken bydel bor du i?</legend>
|
|
165
|
-
<p ksd-fieldset-description>Bergen er delt inn i åtte bydeler</p>
|
|
166
|
-
<ksd-field>
|
|
167
|
-
<ksd-label>Arna</ksd-label>
|
|
168
|
-
<input type="radio" name="city" ksd-input ${argsToTemplate(args)} />
|
|
169
|
-
</ksd-field>
|
|
170
|
-
<ksd-field>
|
|
171
|
-
<ksd-label>Bergenhus</ksd-label>
|
|
172
|
-
<input type="radio" name="city" ksd-input ${argsToTemplate(args)} />
|
|
173
|
-
</ksd-field>
|
|
174
|
-
<ksd-field>
|
|
175
|
-
<ksd-label>Fana</ksd-label>
|
|
176
|
-
<input type="radio" name="city" ksd-input ${argsToTemplate(args)} />
|
|
177
|
-
</ksd-field>
|
|
178
|
-
<ksd-field>
|
|
179
|
-
<ksd-label>Fyllingsdalen</ksd-label>
|
|
180
|
-
<input type="radio" name="city" ksd-input ${argsToTemplate(args)} />
|
|
181
|
-
</ksd-field>
|
|
182
|
-
<ksd-field>
|
|
183
|
-
<ksd-label>Laksevåg</ksd-label>
|
|
184
|
-
<input type="radio" name="city" ksd-input ${argsToTemplate(args)} />
|
|
185
|
-
</ksd-field>
|
|
186
|
-
<ksd-field>
|
|
187
|
-
<ksd-label>Ytrebygda</ksd-label>
|
|
188
|
-
<input type="radio" name="city" ksd-input ${argsToTemplate(args)} />
|
|
189
|
-
</ksd-field>
|
|
190
|
-
<ksd-field>
|
|
191
|
-
<ksd-label>Årstad</ksd-label>
|
|
192
|
-
<input type="radio" name="city" ksd-input ${argsToTemplate(args)} />
|
|
193
|
-
</ksd-field>
|
|
194
|
-
<ksd-field>
|
|
195
|
-
<ksd-label>Åsane</ksd-label>
|
|
196
|
-
<input type="radio" name="city" ksd-input ${argsToTemplate(args)} />
|
|
197
|
-
</ksd-field>
|
|
198
|
-
`,
|
|
199
|
-
}),
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export const Inline: Story = {
|
|
203
|
-
args: {
|
|
204
|
-
...Preview.args,
|
|
205
|
-
},
|
|
206
|
-
|
|
207
|
-
render: (args) => ({
|
|
208
|
-
props: args,
|
|
209
|
-
template: `
|
|
210
|
-
<fieldset ksd-fieldset>
|
|
211
|
-
<legend ksd-fieldset-legend>Kontaktes på e-post?</legend>
|
|
212
|
-
<p ksd-fieldset-description>Bekreft om du ønsker å bli kontaktet per e-post.</p>
|
|
213
|
-
<div style="display: flex; flex-wrap: wrap; gap: var(--ds-size-6)">
|
|
214
|
-
<ksd-field>
|
|
215
|
-
<ksd-label>Ja</ksd-label>
|
|
216
|
-
<input type="radio" name="my-inline" ksd-input ${argsToTemplate(args)} />
|
|
217
|
-
</ksd-field>
|
|
218
|
-
<ksd-field>
|
|
219
|
-
<ksd-label>Nei</ksd-label>
|
|
220
|
-
<input type="radio" name="my-inline" ksd-input ${argsToTemplate(args)} />
|
|
221
|
-
</ksd-field>
|
|
222
|
-
</div>
|
|
223
|
-
</fieldset>
|
|
224
|
-
`,
|
|
225
|
-
}),
|
|
226
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { Directive, input } from '@angular/core'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Search button
|
|
5
|
-
*
|
|
6
|
-
* Used within Search to provide a submit button.
|
|
7
|
-
*
|
|
8
|
-
* @param {('primary' | 'secondary')} [variant] - Specify which button variant to use
|
|
9
|
-
* @param {string} [aria-label] - Aria label for the button
|
|
10
|
-
*
|
|
11
|
-
*/
|
|
12
|
-
@Directive({
|
|
13
|
-
// eslint-disable-next-line @angular-eslint/directive-selector
|
|
14
|
-
selector: 'button[ksd-search-button]',
|
|
15
|
-
standalone: true,
|
|
16
|
-
host: {
|
|
17
|
-
class: 'ds-button',
|
|
18
|
-
type: 'submit',
|
|
19
|
-
'[attr.aria-label]': 'this.ariaLabel()',
|
|
20
|
-
'[attr.data-variant]': 'this.variant()',
|
|
21
|
-
},
|
|
22
|
-
})
|
|
23
|
-
export class SearchButton {
|
|
24
|
-
/**
|
|
25
|
-
* Specify which button variant to use
|
|
26
|
-
*
|
|
27
|
-
* @default 'primary'
|
|
28
|
-
*/
|
|
29
|
-
readonly variant = input<'primary' | 'secondary'>('primary')
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Aria label for the button
|
|
33
|
-
*/
|
|
34
|
-
readonly ariaLabel = input('', { alias: 'aria-label' })
|
|
35
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { Directive, input, output } from '@angular/core'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Search clear button
|
|
5
|
-
*
|
|
6
|
-
* Used within Search to provide a clear button.
|
|
7
|
-
*
|
|
8
|
-
* @param {string} [aria-label] - Aria label for the button.
|
|
9
|
-
*
|
|
10
|
-
* @event clearInput - Emitted when the clear button is clicked.
|
|
11
|
-
* Use this to notify controlled forms that the input should be cleared.
|
|
12
|
-
*
|
|
13
|
-
*/
|
|
14
|
-
@Directive({
|
|
15
|
-
// eslint-disable-next-line @angular-eslint/directive-selector
|
|
16
|
-
selector: 'button[ksd-search-clear]',
|
|
17
|
-
standalone: true,
|
|
18
|
-
host: {
|
|
19
|
-
class: 'ds-button',
|
|
20
|
-
type: 'reset',
|
|
21
|
-
'[attr.data-variant]': "'tertiary'",
|
|
22
|
-
'[attr.aria-label]': 'this.ariaLabel()',
|
|
23
|
-
'(click)': 'handleClear($event)',
|
|
24
|
-
},
|
|
25
|
-
})
|
|
26
|
-
export class SearchClear {
|
|
27
|
-
/**
|
|
28
|
-
* Aria label for the button
|
|
29
|
-
* @default 'Tøm'
|
|
30
|
-
*/
|
|
31
|
-
readonly ariaLabel = input('Tøm', { alias: 'aria-label' })
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Output to notify controlled forms that input should be cleared
|
|
35
|
-
*/
|
|
36
|
-
clearInput = output<void>()
|
|
37
|
-
|
|
38
|
-
handleClear(e: Event): void {
|
|
39
|
-
const target = e.target as HTMLButtonElement
|
|
40
|
-
let inputElement: HTMLElement | null | undefined = null
|
|
41
|
-
|
|
42
|
-
if (target instanceof HTMLElement) {
|
|
43
|
-
inputElement = target.closest('.ds-search')?.querySelector('input')
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (!inputElement) throw new Error('Input is missing')
|
|
47
|
-
|
|
48
|
-
if (!(inputElement instanceof HTMLInputElement)) {
|
|
49
|
-
throw new Error('Input is not an input element')
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
e.preventDefault()
|
|
53
|
-
inputElement.value = ''
|
|
54
|
-
this.clearInput.emit()
|
|
55
|
-
inputElement.focus()
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { Directive } from '@angular/core'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Search input
|
|
5
|
-
*
|
|
6
|
-
* Used within Search to provide a search input.
|
|
7
|
-
*/
|
|
8
|
-
@Directive({
|
|
9
|
-
// eslint-disable-next-line @angular-eslint/directive-selector
|
|
10
|
-
selector: 'input[ksd-search-input]',
|
|
11
|
-
standalone: true,
|
|
12
|
-
host: {
|
|
13
|
-
class: 'ds-input',
|
|
14
|
-
type: 'search',
|
|
15
|
-
placeholder: '', // Need empty placeholder to enable show/hide for clear button
|
|
16
|
-
},
|
|
17
|
-
})
|
|
18
|
-
export class SearchInput {}
|