@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.
Files changed (107) hide show
  1. package/fesm2022/ks-digital-designsystem-angular.mjs +1100 -0
  2. package/fesm2022/ks-digital-designsystem-angular.mjs.map +1 -0
  3. package/index.d.ts +333 -0
  4. package/package.json +15 -20
  5. package/.storybook/customTheme.ts +0 -15
  6. package/.storybook/default-args.ts +0 -18
  7. package/.storybook/main.ts +0 -27
  8. package/.storybook/manager.ts +0 -10
  9. package/.storybook/preview-head.html +0 -16
  10. package/.storybook/preview.ts +0 -70
  11. package/.storybook/themes.ts +0 -9
  12. package/.storybook/tsconfig.json +0 -16
  13. package/.storybook/vite.config.mts +0 -5
  14. package/eslint.config.mjs +0 -28
  15. package/ng-package.json +0 -9
  16. package/project.json +0 -81
  17. package/src/components/alert/alert.mdx +0 -46
  18. package/src/components/alert/alert.spec.ts +0 -33
  19. package/src/components/alert/alert.stories.ts +0 -138
  20. package/src/components/alert/alert.ts +0 -46
  21. package/src/components/alert/index.ts +0 -1
  22. package/src/components/button/button.mdx +0 -40
  23. package/src/components/button/button.spec.ts +0 -86
  24. package/src/components/button/button.stories.ts +0 -123
  25. package/src/components/button/button.ts +0 -60
  26. package/src/components/button/index.ts +0 -1
  27. package/src/components/card/card-block.ts +0 -10
  28. package/src/components/card/card.mdx +0 -100
  29. package/src/components/card/card.spec.ts +0 -70
  30. package/src/components/card/card.stories.ts +0 -101
  31. package/src/components/card/card.ts +0 -44
  32. package/src/components/card/index.ts +0 -2
  33. package/src/components/checkbox/README.md +0 -13
  34. package/src/components/checkbox/checkbox.mdx +0 -50
  35. package/src/components/checkbox/checkbox.spec.ts +0 -21
  36. package/src/components/checkbox/checkbox.stories.ts +0 -182
  37. package/src/components/checkbox/index.ts +0 -0
  38. package/src/components/colors.ts +0 -36
  39. package/src/components/common-inputs.ts +0 -30
  40. package/src/components/details/controlled-details.ts +0 -63
  41. package/src/components/details/details-content.ts +0 -7
  42. package/src/components/details/details-summary.ts +0 -7
  43. package/src/components/details/details.mdx +0 -89
  44. package/src/components/details/details.spec.ts +0 -56
  45. package/src/components/details/details.stories.ts +0 -129
  46. package/src/components/details/details.ts +0 -69
  47. package/src/components/details/index.ts +0 -3
  48. package/src/components/field/field-counter.ts +0 -56
  49. package/src/components/field/field-description.ts +0 -10
  50. package/src/components/field/field-error.ts +0 -13
  51. package/src/components/field/field-observer.ts +0 -121
  52. package/src/components/field/field-state.ts +0 -21
  53. package/src/components/field/field.mdx +0 -40
  54. package/src/components/field/field.spec.ts +0 -131
  55. package/src/components/field/field.stories.ts +0 -98
  56. package/src/components/field/field.ts +0 -70
  57. package/src/components/field/index.ts +0 -3
  58. package/src/components/fieldset/fieldset-description.ts +0 -8
  59. package/src/components/fieldset/fieldset-legend.ts +0 -11
  60. package/src/components/fieldset/fieldset.spec.ts +0 -80
  61. package/src/components/fieldset/fieldset.ts +0 -11
  62. package/src/components/fieldset/index.ts +0 -3
  63. package/src/components/input/index.ts +0 -1
  64. package/src/components/input/input.mdx +0 -11
  65. package/src/components/input/input.spec.ts +0 -25
  66. package/src/components/input/input.stories.ts +0 -72
  67. package/src/components/input/input.ts +0 -67
  68. package/src/components/label/index.ts +0 -1
  69. package/src/components/label/label.ts +0 -17
  70. package/src/components/paragraph/index.ts +0 -1
  71. package/src/components/paragraph/paragraph.ts +0 -10
  72. package/src/components/popover/controlled-popover.ts +0 -62
  73. package/src/components/popover/index.ts +0 -1
  74. package/src/components/popover/popover.mdx +0 -81
  75. package/src/components/popover/popover.spec.ts +0 -143
  76. package/src/components/popover/popover.stories.ts +0 -63
  77. package/src/components/popover/popover.ts +0 -186
  78. package/src/components/radio/radio.mdx +0 -117
  79. package/src/components/radio/radio.stories.ts +0 -226
  80. package/src/components/search/index.ts +0 -4
  81. package/src/components/search/search-button.ts +0 -35
  82. package/src/components/search/search-clear.ts +0 -57
  83. package/src/components/search/search-input.ts +0 -18
  84. package/src/components/search/search.mdx +0 -56
  85. package/src/components/search/search.spec.ts +0 -48
  86. package/src/components/search/search.stories.ts +0 -205
  87. package/src/components/search/search.ts +0 -50
  88. package/src/components/spinner/index.ts +0 -1
  89. package/src/components/spinner/spinner.mdx +0 -24
  90. package/src/components/spinner/spinner.spec.ts +0 -13
  91. package/src/components/spinner/spinner.stories.ts +0 -54
  92. package/src/components/spinner/spinner.ts +0 -62
  93. package/src/components/switch/switch.mdx +0 -82
  94. package/src/components/switch/switch.stories.ts +0 -94
  95. package/src/components/textarea/textarea.mdx +0 -14
  96. package/src/components/textarea/textarea.stories.ts +0 -52
  97. package/src/components/validation-message/index.ts +0 -1
  98. package/src/components/validation-message/validation-message.ts +0 -11
  99. package/src/index.ts +0 -14
  100. package/src/test-setup.ts +0 -12
  101. package/src/utils/log-if-devmode.ts +0 -13
  102. package/src/utils/random-id.ts +0 -3
  103. package/tsconfig.json +0 -34
  104. package/tsconfig.lib.json +0 -28
  105. package/tsconfig.lib.prod.json +0 -9
  106. package/tsconfig.spec.json +0 -30
  107. 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,4 +0,0 @@
1
- export { Search } from './search'
2
- export { SearchButton } from './search-button'
3
- export { SearchClear } from './search-clear'
4
- export { SearchInput } from './search-input'
@@ -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 {}