@ks-digital/designsystem-angular 0.0.1-alpha.24 → 0.0.1-alpha.25

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 (109) hide show
  1. package/.storybook/customTheme.ts +15 -0
  2. package/.storybook/default-args.ts +18 -0
  3. package/.storybook/main.ts +27 -0
  4. package/.storybook/manager.ts +10 -0
  5. package/.storybook/preview-head.html +16 -0
  6. package/.storybook/preview.ts +70 -0
  7. package/.storybook/themes.ts +9 -0
  8. package/.storybook/tsconfig.json +16 -0
  9. package/.storybook/vite.config.mts +5 -0
  10. package/README.md +3 -3
  11. package/eslint.config.mjs +28 -0
  12. package/ng-package.json +9 -0
  13. package/package.json +18 -27
  14. package/project.json +81 -0
  15. package/src/components/alert/alert.mdx +46 -0
  16. package/src/components/alert/alert.spec.ts +33 -0
  17. package/src/components/alert/alert.stories.ts +138 -0
  18. package/src/components/alert/alert.ts +46 -0
  19. package/src/components/alert/index.ts +1 -0
  20. package/src/components/button/button.mdx +40 -0
  21. package/src/components/button/button.spec.ts +86 -0
  22. package/src/components/button/button.stories.ts +123 -0
  23. package/src/components/button/button.ts +60 -0
  24. package/src/components/button/index.ts +1 -0
  25. package/src/components/card/card-block.ts +10 -0
  26. package/src/components/card/card.mdx +100 -0
  27. package/src/components/card/card.spec.ts +70 -0
  28. package/src/components/card/card.stories.ts +101 -0
  29. package/src/components/card/card.ts +44 -0
  30. package/src/components/card/index.ts +2 -0
  31. package/src/components/checkbox/README.md +13 -0
  32. package/src/components/checkbox/checkbox.mdx +50 -0
  33. package/src/components/checkbox/checkbox.spec.ts +21 -0
  34. package/src/components/checkbox/checkbox.stories.ts +182 -0
  35. package/src/components/checkbox/index.ts +0 -0
  36. package/src/components/colors.ts +36 -0
  37. package/src/components/common-inputs.ts +30 -0
  38. package/src/components/details/controlled-details.ts +63 -0
  39. package/src/components/details/details-content.ts +7 -0
  40. package/src/components/details/details-summary.ts +7 -0
  41. package/src/components/details/details.mdx +89 -0
  42. package/src/components/details/details.spec.ts +56 -0
  43. package/src/components/details/details.stories.ts +129 -0
  44. package/src/components/details/details.ts +69 -0
  45. package/src/components/details/index.ts +3 -0
  46. package/src/components/field/field-counter.ts +56 -0
  47. package/src/components/field/field-description.ts +10 -0
  48. package/src/components/field/field-error.ts +13 -0
  49. package/src/components/field/field-observer.ts +121 -0
  50. package/src/components/field/field-state.ts +21 -0
  51. package/src/components/field/field.mdx +40 -0
  52. package/src/components/field/field.spec.ts +131 -0
  53. package/src/components/field/field.stories.ts +98 -0
  54. package/src/components/field/field.ts +70 -0
  55. package/src/components/field/index.ts +3 -0
  56. package/src/components/fieldset/fieldset-description.ts +8 -0
  57. package/src/components/fieldset/fieldset-legend.ts +11 -0
  58. package/src/components/fieldset/fieldset.spec.ts +80 -0
  59. package/src/components/fieldset/fieldset.ts +11 -0
  60. package/src/components/fieldset/index.ts +3 -0
  61. package/src/components/input/index.ts +1 -0
  62. package/src/components/input/input.mdx +11 -0
  63. package/src/components/input/input.spec.ts +25 -0
  64. package/src/components/input/input.stories.ts +72 -0
  65. package/src/components/input/input.ts +67 -0
  66. package/src/components/label/index.ts +1 -0
  67. package/src/components/label/label.ts +17 -0
  68. package/src/components/paragraph/index.ts +1 -0
  69. package/src/components/paragraph/paragraph.ts +10 -0
  70. package/src/components/popover/controlled-popover.ts +62 -0
  71. package/src/components/popover/index.ts +1 -0
  72. package/src/components/popover/popover.mdx +81 -0
  73. package/src/components/popover/popover.spec.ts +143 -0
  74. package/src/components/popover/popover.stories.ts +63 -0
  75. package/src/components/popover/popover.ts +186 -0
  76. package/src/components/radio/radio.mdx +117 -0
  77. package/src/components/radio/radio.stories.ts +226 -0
  78. package/src/components/search/index.ts +4 -0
  79. package/src/components/search/search-button.ts +35 -0
  80. package/src/components/search/search-clear.ts +57 -0
  81. package/src/components/search/search-input.ts +18 -0
  82. package/src/components/search/search.mdx +56 -0
  83. package/src/components/search/search.spec.ts +48 -0
  84. package/src/components/search/search.stories.ts +205 -0
  85. package/src/components/search/search.ts +50 -0
  86. package/src/components/spinner/index.ts +1 -0
  87. package/src/components/spinner/spinner.mdx +24 -0
  88. package/src/components/spinner/spinner.spec.ts +13 -0
  89. package/src/components/spinner/spinner.stories.ts +54 -0
  90. package/src/components/spinner/spinner.ts +62 -0
  91. package/src/components/switch/switch.mdx +82 -0
  92. package/src/components/switch/switch.stories.ts +94 -0
  93. package/src/components/textarea/textarea.mdx +14 -0
  94. package/src/components/textarea/textarea.stories.ts +52 -0
  95. package/src/components/validation-message/index.ts +1 -0
  96. package/src/components/validation-message/validation-message.ts +11 -0
  97. package/src/index.ts +14 -0
  98. package/src/test-setup.ts +12 -0
  99. package/src/utils/log-if-devmode.ts +13 -0
  100. package/src/utils/random-id.ts +3 -0
  101. package/tsconfig.json +34 -0
  102. package/tsconfig.lib.json +28 -0
  103. package/tsconfig.lib.prod.json +9 -0
  104. package/tsconfig.spec.json +30 -0
  105. package/vite.config.mts +35 -0
  106. package/dist/README.md +0 -55
  107. package/dist/fesm2022/ks-digital-designsystem-angular.mjs +0 -1094
  108. package/dist/fesm2022/ks-digital-designsystem-angular.mjs.map +0 -1
  109. package/dist/index.d.ts +0 -333
@@ -0,0 +1,56 @@
1
+ import {
2
+ Meta,
3
+ Primary,
4
+ Controls,
5
+ Story,
6
+ Canvas,
7
+ } from '@storybook/addon-docs/blocks'
8
+
9
+ import * as SearchStories from './search.stories'
10
+
11
+ <Meta of={SearchStories} />
12
+
13
+ # Search
14
+
15
+ Search lar brukere raskt finne relevant innhold på et nettsted i en applikasjon. Komponenten består av et søkefelt, med eller uten en søkeknapp
16
+
17
+ <Primary />
18
+ <Controls />
19
+
20
+ ## Bruk
21
+
22
+ ```tsx
23
+ import {
24
+ Search,
25
+ SearchInput,
26
+ SearchClear,
27
+ SearchButton,
28
+ } from '@ks-digital/designsystem-angular'
29
+ ;<ksd-search>
30
+ <input ksd-search-input />
31
+ <button ksd-search-clear></button>
32
+ <button ksd-search-button></button>
33
+ </ksd-search>
34
+ ```
35
+
36
+ ## Kontrollert
37
+
38
+ <Canvas of={SearchStories.Controlled} />
39
+
40
+ ## Variants
41
+
42
+ Du kan endre variant på Button for å tilpasse visningen. Alternativt kan du fjerne knappen for å bruke et søkefelt med ikon. Eksemplene nedenfor viser varianter som inkluderer ikon, primær- og sekundærknapp.
43
+
44
+ <Canvas of={SearchStories.Variants} />
45
+
46
+ ## Med Label
47
+
48
+ Dersom du vil ha label på søkefeltet, må du legge til en Label komponent som du kobler sammen med SearchInput. I eksempelet har vi brukt Field for å få oppkobling mellom Label og SearchInput.
49
+
50
+ <Canvas of={SearchStories.WithLabel} />
51
+
52
+ ## Skjema
53
+
54
+ Søkeknappen har typen "submit" og vil dermed automatisk sende inn skjema.
55
+
56
+ <Canvas of={SearchStories.Form} />
@@ -0,0 +1,48 @@
1
+ import { render, screen } from '@testing-library/angular'
2
+ import userEvent from '@testing-library/user-event'
3
+ import { Input } from '../input'
4
+ import { Search } from './search'
5
+ import { SearchButton } from './search-button'
6
+ import { SearchClear } from './search-clear'
7
+ import { SearchInput } from './search-input'
8
+
9
+ test('should render minimal search component', async () => {
10
+ await render(
11
+ `
12
+ <ksd-search>
13
+ <input ksd-search-input role="searchbox" />
14
+ </ksd-search>
15
+ `,
16
+ { imports: [SearchInput, Search, Input] },
17
+ )
18
+
19
+ const searchElement = screen.getByRole('searchbox').parentElement
20
+ expect(searchElement).toBeInTheDocument()
21
+ expect(searchElement).toHaveClass('ds-search')
22
+ })
23
+
24
+ test('should clear the input when the clear button is clicked', async () => {
25
+ await render(
26
+ `
27
+ <ksd-search>
28
+ <input ksd-search-input role="searchbox" />
29
+ <button ksd-search-clear role="button"></button>
30
+ <button ksd-search-button></button>
31
+ </ksd-search>
32
+ `,
33
+ {
34
+ imports: [SearchInput, SearchClear, SearchButton, Search],
35
+ },
36
+ )
37
+
38
+ const searchInput = screen.getByRole('searchbox') as HTMLInputElement
39
+ const clearButton = screen.getByRole('button', {
40
+ name: /tøm/i,
41
+ }) as HTMLButtonElement
42
+
43
+ await userEvent.type(searchInput, 'test')
44
+ expect(searchInput.value).toBe('test')
45
+
46
+ await userEvent.click(clearButton)
47
+ expect(searchInput.value).toBe('')
48
+ })
@@ -0,0 +1,205 @@
1
+ import { moduleMetadata, type Meta } from '@storybook/angular'
2
+ import { CommonArgs, commonArgTypes } from '../../../.storybook/default-args'
3
+ import { Button } from '../button'
4
+ import { Field } from '../field/field'
5
+ import { Input } from '../input'
6
+ import { Label } from '../label/label'
7
+ import { Search } from './search'
8
+ import { SearchButton } from './search-button'
9
+ import { SearchClear } from './search-clear'
10
+ import { SearchInput } from './search-input'
11
+
12
+ type SearchArgs = CommonArgs & {
13
+ variant?: 'primary' | 'secondary'
14
+ buttonLabel?: string
15
+ clearButtonLabel?: string
16
+ }
17
+
18
+ const meta: Meta<SearchArgs> = {
19
+ component: Search,
20
+ title: 'Komponenter/Search',
21
+ argTypes: {
22
+ ...commonArgTypes,
23
+ variant: {
24
+ options: ['primary', 'secondary'],
25
+ control: { type: 'radio' },
26
+ description: 'Velg variant for søkeknappen',
27
+ },
28
+ buttonLabel: {
29
+ control: { type: 'text' },
30
+ description: 'Label for søkeknappen',
31
+ },
32
+ clearButtonLabel: {
33
+ control: { type: 'text' },
34
+ description: 'Label for tøm-knappen',
35
+ },
36
+ },
37
+ decorators: [
38
+ moduleMetadata({
39
+ imports: [
40
+ Search,
41
+ SearchClear,
42
+ SearchButton,
43
+ SearchInput,
44
+ Button,
45
+ Input,
46
+ Field,
47
+ Label,
48
+ ],
49
+ }),
50
+ ],
51
+ }
52
+ export default meta
53
+ type Story = Meta<Search>
54
+
55
+ export const Preview: Story = {
56
+ args: {
57
+ variant: 'primary',
58
+ buttonLabel: 'Søk',
59
+ clearButtonLabel: 'Tøm',
60
+ 'data-size': '',
61
+ 'data-color': '',
62
+ },
63
+ render: (args) => ({
64
+ props: {
65
+ ...args,
66
+ dataSize: (args as SearchArgs)['data-size'],
67
+ dataColor: (args as SearchArgs)['data-color'],
68
+ },
69
+ template: `
70
+ <ksd-search role="search" [attr.data-size]="dataSize" [attr.data-color]="dataColor">
71
+ <input ksd-search-input role="searchbox" aria-label="Søkefelt" />
72
+ <button ksd-search-clear [aria-label]="clearButtonLabel"></button>
73
+ <button ksd-search-button [variant]="variant" [aria-label]="buttonLabel"></button>
74
+ </ksd-search>
75
+ `,
76
+ }),
77
+ }
78
+
79
+ export const Controlled: Story = {
80
+ render: () => {
81
+ const state = {
82
+ value: '',
83
+ }
84
+ return {
85
+ props: {
86
+ state,
87
+
88
+ setInput: (value: string) => {
89
+ state.value = value
90
+ },
91
+
92
+ setValue: (event: KeyboardEvent) => {
93
+ const input = event.target as HTMLInputElement
94
+ state.value = input.value
95
+ },
96
+
97
+ clearValue: () => {
98
+ state.value = ''
99
+ },
100
+ },
101
+ template: `
102
+ <ksd-search>
103
+ <input ksd-search-input role="searchbox" [value]="state.value" (keyup)="setValue($event)"/>
104
+ <button ksd-search-clear (clearInput)="clearValue()" ></button>
105
+ <button ksd-search-button></button>
106
+ </ksd-search>
107
+
108
+ <div>
109
+ <span>Current search value: "{{ state.value }}"</span>
110
+
111
+ <button ksd-button (click)="setInput('Calzone')">
112
+ Set input value to "Calzone"
113
+ </button>
114
+
115
+ <p>The clear button has an output <em>(clearInput)</em> that is emitted when clicked.</p>
116
+ </div>
117
+ `,
118
+ }
119
+ },
120
+ }
121
+
122
+ export const Variants: Story = {
123
+ args: {},
124
+ render: (args) => ({
125
+ props: args,
126
+ template: `
127
+ <div>
128
+ <p>Primary variant (default)</p>
129
+ <ksd-search>
130
+ <input ksd-search-input role="searchbox" />
131
+ <button ksd-search-clear></button>
132
+ <button ksd-search-button></button>
133
+ </ksd-search>
134
+ </div>
135
+
136
+ <div>
137
+ <p>Secondary variant</p>
138
+ <ksd-search>
139
+ <input ksd-search-input role="searchbox" />
140
+ <button ksd-search-clear></button>
141
+ <button ksd-search-button variant="secondary"></button>
142
+ </ksd-search>
143
+ </div>
144
+
145
+ <div>
146
+ <p>Search with icon</p>
147
+ <ksd-search>
148
+ <input ksd-search-input role="searchbox" />
149
+ <button ksd-search-clear></button>
150
+ </ksd-search>
151
+ </div>
152
+ `,
153
+ }),
154
+ }
155
+
156
+ export const WithLabel: Story = {
157
+ args: {},
158
+ render: (args) => ({
159
+ props: args,
160
+ template: `
161
+ <ksd-field>
162
+ <ksd-label>Søk etter hunder:</ksd-label>
163
+ <ksd-search>
164
+ <input ksd-search-input role="searchbox" name="dog-search" />
165
+ <button ksd-search-clear></button>
166
+ <button ksd-search-button></button>
167
+ </ksd-search>
168
+ </ksd-field>
169
+ `,
170
+ }),
171
+ }
172
+
173
+ export const Form: Story = {
174
+ render: () => {
175
+ const state = { value: '' }
176
+ return {
177
+ props: {
178
+ state,
179
+ onSubmit: (event: Event) => {
180
+ event.preventDefault()
181
+ const form = event.target as HTMLFormElement
182
+ const formData = new FormData(form)
183
+ state.value = formData.get('search') as string
184
+ },
185
+ onClear: () => {
186
+ state.value = ''
187
+ },
188
+ },
189
+ template: `
190
+ <form role="search" (submit)="onSubmit($event)">
191
+ <ksd-search>
192
+ <input ksd-search-input role="searchbox" name="search" />
193
+ <button ksd-search-clear (clearInput)="onClear()"></button>
194
+ <button ksd-search-button></button>
195
+ </ksd-search>
196
+ </form>
197
+
198
+ <p>Submitted value: "{{ state.value }}"</p>
199
+ `,
200
+ }
201
+ },
202
+ // play: async ({ canvas }) => {
203
+ // await expect(canvas.getByRole('search')).toBeTruthy()
204
+ // },
205
+ }
@@ -0,0 +1,50 @@
1
+ import { afterNextRender, Component, contentChild } from '@angular/core'
2
+ import { logIfDevMode } from '../../utils/log-if-devmode'
3
+ import { CommonInputs } from '../common-inputs'
4
+ import { SearchInput } from './search-input'
5
+
6
+ /**
7
+ * Search Component
8
+ *
9
+ * Use to contain the search input and buttons.
10
+ * Only `SearchInput` is required, while `SearchClear` and `SearchButton` are optional.
11
+ *
12
+ * @example
13
+ * <div ksd-search>
14
+ * <input ksd-search-input />
15
+ * <button ksd-search-clear></button>
16
+ * <button ksd-search-button></button>
17
+ * </div>
18
+ */
19
+ @Component({
20
+ selector: 'ksd-search',
21
+ template: `
22
+ <ng-content select="[ksd-search-input]" />
23
+ <ng-content select="[ksd-search-clear]" />
24
+ <ng-content select="[ksd-search-button]" />
25
+ `,
26
+ host: {
27
+ class: 'ds-search',
28
+ },
29
+ hostDirectives: [
30
+ {
31
+ directive: CommonInputs,
32
+ inputs: ['data-size', 'data-color'],
33
+ },
34
+ ],
35
+ })
36
+ export class Search {
37
+ private readonly input = contentChild(SearchInput)
38
+
39
+ constructor() {
40
+ afterNextRender(() => {
41
+ if (!this.input()) {
42
+ logIfDevMode({
43
+ component: 'Search',
44
+ message:
45
+ 'Missing required elements: ksd-search-input must be provided as child. Check imports and markup.',
46
+ })
47
+ }
48
+ })
49
+ }
50
+ }
@@ -0,0 +1 @@
1
+ export { Spinner } from './spinner'
@@ -0,0 +1,24 @@
1
+ import { Meta, Primary, Controls, Canvas } from '@storybook/addon-docs/blocks'
2
+
3
+ import * as SpinnerStories from './spinner.stories'
4
+
5
+ <Meta of={SpinnerStories} />
6
+
7
+ # Spinner
8
+
9
+ `Spinner` brukes for å indikere at en handling pågår. Dette kan være når vi venter på at et skjema skal sendes inn.
10
+
11
+ <Primary />
12
+ <Controls />
13
+
14
+ ## Størrelser
15
+
16
+ Du kan justere størrelsen på `Spinner` etter hvor den skal plasseres.
17
+ Størrelsen vil være mindre i knapper, men større dersom den er ved et skjemafelt.
18
+
19
+ <Canvas of={SpinnerStories.Sizes} />
20
+
21
+ ## Tilgjengelighet
22
+
23
+ Når brukeren har `prefers-reduced-motion` satt til `reduce` vil spinneren ha en animasjon på 6 sekunder.
24
+ Animasjonen er ikke dekorativ, og vi skrur den derfor ikke av.
@@ -0,0 +1,13 @@
1
+ import { render, screen } from '@testing-library/angular'
2
+ import { Spinner } from './spinner'
3
+
4
+ it('should render a spinner with title "loading"', async () => {
5
+ await render(
6
+ `
7
+ <ksd-spinner aria-label="Loading">
8
+ `,
9
+ { imports: [Spinner] },
10
+ )
11
+
12
+ expect(screen.getByLabelText('Loading')).toBeInTheDocument()
13
+ })
@@ -0,0 +1,54 @@
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 { Spinner } from './spinner'
9
+
10
+ type SpinnerArgs = CommonArgs
11
+
12
+ const meta: Meta<SpinnerArgs> = {
13
+ component: Spinner,
14
+ title: 'Komponenter/Loaders/Spinner',
15
+ decorators: [
16
+ moduleMetadata({
17
+ imports: [Spinner],
18
+ }),
19
+ ],
20
+ }
21
+ export default meta
22
+ type Story = StoryObj<SpinnerArgs>
23
+
24
+ export const Preview: Story = {
25
+ args: {
26
+ 'data-size': 'md',
27
+ 'data-color': undefined,
28
+ },
29
+
30
+ render: (args) => ({
31
+ props: args,
32
+ template: `
33
+ <ksd-spinner ${argsToTemplate(args)} data-size="xl" />
34
+ `,
35
+ }),
36
+ }
37
+
38
+ export const Sizes: Story = {
39
+ args: {
40
+ ...Preview.args,
41
+ 'data-size': undefined,
42
+ },
43
+ render: (args) => ({
44
+ props: args,
45
+ template: `
46
+ <ksd-spinner aria-label="Laster" ${argsToTemplate(args)} data-size="2xs" />
47
+ <ksd-spinner aria-label="Laster" ${argsToTemplate(args)} data-size="xs" />
48
+ <ksd-spinner aria-label="Laster" ${argsToTemplate(args)} data-size="sm" />
49
+ <ksd-spinner aria-label="Laster" ${argsToTemplate(args)} data-size="md" />
50
+ <ksd-spinner aria-label="Laster" ${argsToTemplate(args)} data-size="lg" />
51
+ <ksd-spinner aria-label="Laster" ${argsToTemplate(args)} data-size="xl" />
52
+ `,
53
+ }),
54
+ }
@@ -0,0 +1,62 @@
1
+ /* eslint-disable @angular-eslint/no-input-rename */
2
+ import { booleanAttribute, Component, input } from '@angular/core'
3
+ import { Size } from '../common-inputs'
4
+
5
+ @Component({
6
+ selector: 'ksd-spinner',
7
+ styles: `
8
+ :host {
9
+ display: contents;
10
+ }
11
+ `,
12
+ template: `
13
+ <svg
14
+ class="ds-spinner"
15
+ role="img"
16
+ viewBox="0 0 50 50"
17
+ [attr.data-size]="dataSize()"
18
+ [attr.data-color]="dataColor()"
19
+ >
20
+ <circle
21
+ class="ds-spinner__background"
22
+ cx="25"
23
+ cy="25"
24
+ r="20"
25
+ fill="none"
26
+ stroke-width="5"
27
+ />
28
+ <circle
29
+ class="ds-spinner__circle"
30
+ cx="25"
31
+ cy="25"
32
+ r="20"
33
+ fill="none"
34
+ stroke-width="5"
35
+ />
36
+ </svg>
37
+ `,
38
+ })
39
+ export class Spinner {
40
+ /**
41
+ * Aria-label for the spinner
42
+ */
43
+ readonly ariaLabel = input<string>(undefined, { alias: 'aria-label' })
44
+
45
+ /**
46
+ * Aria-label for the spinner
47
+ */
48
+ readonly dataSize = input<Size>(undefined, { alias: 'data-size' })
49
+
50
+ /**
51
+ * Aria-label for the spinner
52
+ */
53
+ readonly dataColor = input<Size>(undefined, { alias: 'data-color' })
54
+
55
+ /**
56
+ * Aria-hidden for the spinner
57
+ */
58
+ readonly ariaHidden = input(undefined, {
59
+ transform: booleanAttribute,
60
+ alias: 'aria-hidden',
61
+ })
62
+ }
@@ -0,0 +1,82 @@
1
+ import { Meta, Primary, Controls, Canvas } from '@storybook/addon-docs/blocks'
2
+
3
+ import * as SwitchStories from './switch.stories'
4
+
5
+ <Meta of={SwitchStories} />
6
+
7
+ # Switch
8
+
9
+ `Switch` brukes til å gi brukeren et valg mellom to alternativer. Bryteren kan enten slås av eller på og skal alltid være innstilt med et standardvalg
10
+
11
+ <Primary />
12
+ <Controls />
13
+
14
+ ## Bruk
15
+
16
+ ```html
17
+ <ksd-field>
18
+ <ksd-label>Min switch</ksd-label>
19
+ <input ksd-input type="checkbox" role="switch" />
20
+ </ksd-field>
21
+ ```
22
+
23
+ ## Kodeeksempler
24
+
25
+ **Gruppering**
26
+
27
+ Bruk `Fieldset`for å gruppere fler `Switch`-komponenter sammen
28
+
29
+ <Canvas of={SwitchStories.Grouped} />
30
+
31
+ **Høyre justering**
32
+
33
+ Plasser `label` etter `input` for å plassere `Switch` til høyre side av ledeteksten hvis du trenger det.
34
+
35
+ <Canvas of={SwitchStories.RightAligned} />
36
+
37
+ ## Retningslinjer
38
+
39
+ `Switch`skal være et valg mellom to alternativer. Vi bruker `Switch` til innstillinger, ikke i skjema.
40
+
41
+ **Passer til:**
42
+
43
+ - aktivere eller deaktivere funksjoner/tilstander med en gang
44
+ - slå varsler av og på
45
+
46
+ **Passer ikke til:**
47
+
48
+ - å bruke i stedet for `Checkbox` eller `Radio` i et skjema
49
+ - situasjoner der brukerne må lagre informasjon før en endring får effekt
50
+ - endre visning av innhold mellom to kategorier
51
+
52
+ **Bruk heller**
53
+
54
+ - `Checkbox` hvis du skal tilby brukerne flere valg, der de skal velge ett eller flere alternativer
55
+ - `Radio` hvis brukerne skal få flere valg, men bare skal velge ett
56
+ - `Chip til å filtrere innhold
57
+
58
+ ## Tekst
59
+
60
+ Teksten til en `Switch` må være kort og presis, ofte betyr det bare ett eller to ord. Det skal gi mening når du sier ledeteksten høyt og legger til av/på etterpå. Unngå tekst med flere ord og mellomrom.
61
+
62
+ Hvis du har flere `Switch` i en gruppe, må du passe på at du er konsekvent med hvordan du formulerer tekstene til hver av dem også, ikke formulerer dem forskjellig.
63
+
64
+ Teksten skal beskrive hva funksjonen gjelder, ikke om den er på eller av. Hvis statusen skrives inn i labelen, kan det bli forvirrende og vanskelig å vite hvilken tilstand switchen faktisk er i.
65
+
66
+ **Eksempel 1**
67
+
68
+ - Riktig: Varsle på epost (Av/på)
69
+ - Feil: Skru av varsel på epost (Av/på)
70
+
71
+ **Eksempel 2**
72
+
73
+ - Riktig: Flymodus (Av/på)
74
+ - Feil: Skru på flymodus (Av/på)
75
+
76
+ **Plassering av tekst**
77
+
78
+ Som regel er det høyre som er den beste plasseringen for tekst, unntaket er når `Switch er fast plassert på høyre side i grensesnittet er det best at teksten er på venstre side. Slik at alle knappene blir justert etter samme vertikale
79
+
80
+ ## Tilgjengelighet
81
+
82
+ Vi bruker `role="switch"` for å fortelle skjermlesere at dette er en vippebryter. Dette gjør at skjermlesere kan gi brukeren informasjon om at dette er en vippebryter og at den kan skrus av og på. Det er ikke lagt på `aria-checked` siden dette er ikke nødvendig når bryteren er en input med `type="checkbox".
@@ -0,0 +1,94 @@
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 SwitchArgs = CommonArgs & {
18
+ readonly: boolean
19
+ disabled: boolean
20
+ }
21
+
22
+ const meta: Meta<SwitchArgs> = {
23
+ component: Input,
24
+ title: 'Komponenter/Switch',
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<SwitchArgs>
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>Min switch</ksd-label>
54
+ <input ksd-input role="switch" type="checkbox" ${argsToTemplate(args)} role="switch"/>
55
+ </ksd-field>
56
+ `,
57
+ }),
58
+ }
59
+
60
+ export const Grouped: Story = {
61
+ render: () => ({
62
+ template: `
63
+ <fieldset ksd-fieldset>
64
+ <legend ksd-fieldset-legend> Skru av/på lys </legend>
65
+ <ksd-field>
66
+ <ksd-label>Stue</ksd-label>
67
+ <input ksd-input type="checkbox" role="switch" checked=${true} />
68
+ </ksd-field>
69
+ <ksd-field>
70
+ <ksd-label>Kjøkken</ksd-label>
71
+ <input ksd-input type="checkbox" role="switch"/>
72
+ </ksd-field>
73
+ <ksd-field>
74
+ <ksd-label>Bad</ksd-label>
75
+ <input ksd-input type="checkbox" role="switch"/>
76
+ </ksd-field>
77
+ <ksd-field>
78
+ <ksd-label>Soverom</ksd-label>
79
+ <input ksd-input type="checkbox" role="switch" />
80
+ </ksd-field>
81
+ </fieldset>`,
82
+ }),
83
+ }
84
+
85
+ export const RightAligned: Story = {
86
+ render: () => ({
87
+ template: `
88
+ <ksd-field>
89
+ <input ksd-input role="switch" type="checkbox" role="switch"/>
90
+ <ksd-label>Min switch</ksd-label>
91
+ </ksd-field>
92
+ `,
93
+ }),
94
+ }