@swr-data-lab/components 1.6.0 → 1.7.0

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 (41) hide show
  1. package/.storybook/main.ts +13 -16
  2. package/.storybook/preview.ts +3 -0
  3. package/.storybook/vitest.setup.ts +9 -0
  4. package/package.json +8 -5
  5. package/src/Autocomplete/Autocomplete.stories.svelte +61 -0
  6. package/src/Autocomplete/Autocomplete.svelte +9 -7
  7. package/src/Button/Button.svelte +14 -10
  8. package/src/Card/Card.stories.svelte +5 -20
  9. package/src/Card/Card.svelte +17 -4
  10. package/src/ChartFooter/ChartFooter.mdx +17 -0
  11. package/src/ChartFooter/ChartFooter.stories.svelte +12 -20
  12. package/src/ChartFooter/ChartFooter.svelte +30 -22
  13. package/src/ChartHeader/ChartHeader.mdx +15 -0
  14. package/src/ChartHeader/ChartHeader.stories.svelte +14 -11
  15. package/src/ChartHeader/ChartHeader.svelte +19 -13
  16. package/src/DesignTokens/DesignTokens.mdx +15 -9
  17. package/src/DesignTokens/DesignTokens.svelte +2 -1
  18. package/src/DesignTokens/index.js +8 -1
  19. package/src/HighlightCard/HighlightCard.stories.svelte +5 -20
  20. package/src/HighlightCard/HighlightCard.svelte +15 -11
  21. package/src/Input/Input.svelte +1 -1
  22. package/src/Intro.mdx +2 -2
  23. package/src/Logotype/Logotype.stories.svelte +16 -0
  24. package/src/Logotype/Logotype.svelte +1 -0
  25. package/src/Middot/Middot.stories.svelte +16 -0
  26. package/src/Select/Select.mdx +25 -0
  27. package/src/Select/Select.stories.svelte +87 -147
  28. package/src/Select/Select.svelte +65 -73
  29. package/src/Select/Select.types.ts +8 -0
  30. package/src/Select/SelectStoriesTemplate.svelte +35 -0
  31. package/src/Switcher/Switcher.mdx +13 -0
  32. package/src/Switcher/Switcher.stories.svelte +36 -41
  33. package/src/Switcher/Switcher.svelte +29 -20
  34. package/src/index.js +1 -0
  35. package/src/styles/base.scss +52 -5
  36. package/vitest.workspace.ts +32 -0
  37. package/src/Autocomplete/Autocomplete.stories.js +0 -61
  38. package/src/Container/Container.svelte +0 -12
  39. package/src/Container/index.js +0 -2
  40. package/src/styles/_typography.scss +0 -49
  41. package/src/styles/_vars.scss +0 -30
@@ -16,12 +16,13 @@
16
16
  'display: contents',
17
17
  '--fast: 150ms',
18
18
  '--slow: 300ms',
19
+ '--app-max-width: 40rem',
19
20
  '--br-large: 8px',
20
21
  '--br-small: 4px',
21
- '--ratio: 1.15',
22
22
  '--input-height: 2.5rem',
23
23
  '--swr-sans: "SWR-VAR-Sans", sans-serif',
24
24
  '--swr-text: "SWR-VAR-Text", sans-serif',
25
+ '--ratio: 1.15',
25
26
  '--fs-small-3: calc(var(--fs-small-2) / var(--ratio))',
26
27
  '--fs-small-2: calc(var(--fs-small-1) / var(--ratio))',
27
28
  '--fs-small-1: calc(var(--fs-base) / var(--ratio))',
@@ -1,2 +1,9 @@
1
1
  import DesignTokens from './DesignTokens.svelte';
2
- export default DesignTokens;
2
+ import { shades, scales } from './Tokens';
3
+
4
+ const tokens = {
5
+ shades,
6
+ scales
7
+ };
8
+
9
+ export { DesignTokens, tokens };
@@ -1,26 +1,12 @@
1
- <script context="module">
1
+ <script context="module" lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
2
3
  import HighlightCard from './HighlightCard.svelte';
3
4
  import DesignTokens from '../DesignTokens/DesignTokens.svelte';
4
5
 
5
- export const meta = {
6
- title: 'Input Components/HighlightCard',
6
+ const { Story } = defineMeta({
7
+ title: 'Display/Card/HighlightCard',
7
8
  component: HighlightCard
8
- };
9
- </script>
10
-
11
- <script>
12
- import { Story, Template } from '@storybook/addon-svelte-csf';
13
- import {
14
- userEvent,
15
- within,
16
- expect,
17
- getByTestId,
18
- getAllByLabelText,
19
- getByText
20
- } from '@storybook/test';
21
- import { hasContext } from 'svelte';
22
-
23
- let component;
9
+ });
24
10
  </script>
25
11
 
26
12
  <Story name="Basic HighlightCard">
@@ -63,7 +49,6 @@
63
49
 
64
50
  :global(.highlight-cards > *) {
65
51
  margin: 1rem 0;
66
-
67
52
  @media (min-width: 900px) {
68
53
  margin: 2rem 1rem;
69
54
  flex: 1;
@@ -1,10 +1,14 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import Card from '../Card/Card.svelte';
3
3
 
4
- export let topline = '';
5
- export let value = '';
6
- export let unit = '';
7
- export let subline = '';
4
+ interface HighlightCardProps {
5
+ topline?: string;
6
+ value?: string;
7
+ unit?: string;
8
+ subline?: string;
9
+ }
10
+
11
+ let { topline, value, unit, subline }: HighlightCardProps = $props();
8
12
  </script>
9
13
 
10
14
  <Card>
@@ -20,15 +24,15 @@
20
24
  </span>
21
25
  {unit}
22
26
  </div>
23
- <div class="bottom">
24
- {@html subline}
25
- </div>
27
+ {#if subline}
28
+ <div class="bottom">
29
+ {@html subline}
30
+ </div>
31
+ {/if}
26
32
  </div>
27
33
  </Card>
28
34
 
29
35
  <style lang="scss">
30
- @import '../styles/base.scss';
31
-
32
36
  .card-wrapper {
33
37
  display: flex;
34
38
  flex-direction: column;
@@ -43,7 +47,7 @@
43
47
  }
44
48
 
45
49
  .middle {
46
- font-family: $swr-sans;
50
+ font-family: var(--swr-sans);
47
51
  font-size: 54px;
48
52
  font-weight: 700;
49
53
  padding-bottom: 0.2em;
@@ -11,7 +11,7 @@
11
11
  </div>
12
12
 
13
13
  <style lang="scss">
14
- @import '../styles/base.scss';
14
+ @use '../styles/base.scss';
15
15
 
16
16
  label {
17
17
  @extend %form-label;
package/src/Intro.mdx CHANGED
@@ -2,6 +2,6 @@ import { Meta } from "@storybook/blocks";
2
2
 
3
3
  <Meta title="About" />
4
4
 
5
- # SWR Components
5
+ # SWR Data Lab Components
6
6
 
7
- Experimental component library for SWR Data interactives.
7
+ Experimental component library for SWR Data Lab interactives.
@@ -0,0 +1,16 @@
1
+ <script context="module" lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import Logotype from './Logotype.svelte';
4
+ import DesignTokens from '../DesignTokens/DesignTokens.svelte';
5
+
6
+ const { Story } = defineMeta({
7
+ title: 'Chart/Logotype',
8
+ component: Logotype
9
+ });
10
+ </script>
11
+
12
+ <Story name="Basic">
13
+ <DesignTokens>
14
+ <Logotype></Logotype>
15
+ </DesignTokens>
16
+ </Story>
@@ -1,4 +1,5 @@
1
1
  <svg viewBox="0 0 127 10" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <title>SWR Data Lab</title>
2
3
  <path
3
4
  fill-rule="evenodd"
4
5
  clip-rule="evenodd"
@@ -0,0 +1,16 @@
1
+ <script context="module" lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import Middot from './Middot.svelte';
4
+ import DesignTokens from '../DesignTokens/DesignTokens.svelte';
5
+
6
+ const { Story } = defineMeta({
7
+ title: 'Chart/Middot',
8
+ component: Middot
9
+ });
10
+ </script>
11
+
12
+ <Story name="Basic">
13
+ <DesignTokens>
14
+ <Middot></Middot>
15
+ </DesignTokens>
16
+ </Story>
@@ -0,0 +1,25 @@
1
+ import { Story, Meta, Primary, Controls, Stories} from '@storybook/blocks';
2
+
3
+ import * as SelectStories from './Select.stories.svelte';
4
+
5
+ <Meta of={SelectStories}/>
6
+
7
+ # Select
8
+
9
+ This component is a select input with a search feature and various options such as grouped items, multi-select etc. based on [svelte-select](https://github.com/rob-balfre/svelte-select).
10
+
11
+ Select items passed to the `items` prop must be of type `SelectItem`:
12
+
13
+ ```ts
14
+ interface SelectItem {
15
+ value: string;
16
+ label: string;
17
+ group?: string;
18
+ details?: any;
19
+ }
20
+ ```
21
+
22
+ <Controls/>
23
+
24
+ <Stories/>
25
+
@@ -1,91 +1,23 @@
1
- <script context="module">
1
+ <script context="module" lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
2
3
  import Select from './Select.svelte';
4
+ import StoryTemplate from './SelectStoriesTemplate.svelte';
3
5
  import jobsData from './mock_data/jobs.json';
4
6
 
5
- export const meta = {
6
- title: 'Input Components/Select',
7
- component: Select,
8
-
9
- // FIXME: remove this manual documentation when autodocs are fixed
10
- parameters: {
11
- docs: {
12
- description: {
13
- component:
14
- 'This component is a select input with a search feature and various options such as grouped items, multi-select etc. based on https://github.com/rob-balfre/svelte-select'
15
- }
16
- }
17
- },
18
- argTypes: {
19
- inputId: {
20
- description: "The input field's ID",
21
- control: 'text',
22
- type: { name: 'string', required: false },
23
- defaultValue: 'select'
24
- },
25
- placeholder: {
26
- description: "The input field's placeholder text",
27
- control: 'text',
28
- type: { name: 'string', required: false },
29
- defaultValue: 'Bitte auswählen'
30
- },
31
- groupHeaderSelectable: {
32
- description: 'Whether the group names should be selectable as well',
33
- type: { name: 'boolean', required: false },
34
- defaultValue: 'false'
35
- },
36
- clearable: {
37
- description: 'Show a button to clear the input',
38
- type: { name: 'boolean', required: false },
39
- defaultValue: 'false'
40
- },
41
- value: {
42
- description: 'The currently selected option',
43
- type: { name: 'any', required: false }
44
- }
45
- }
46
- };
7
+ const { Story } = defineMeta({
8
+ title: 'Form/Select',
9
+ component: Select
10
+ });
47
11
  </script>
48
12
 
49
- <script>
50
- import { Story, Template } from '@storybook/addon-svelte-csf';
51
- import {
52
- userEvent,
53
- within,
54
- expect,
55
- getByTestId,
56
- getAllByLabelText,
57
- getByText
58
- } from '@storybook/test';
59
- import { hasContext } from 'svelte';
60
- import Input from '../Input/Input.svelte';
61
-
62
- let selectedItem;
63
- let component;
13
+ <script lang="ts">
14
+ import { userEvent, within, expect } from '@storybook/test';
15
+ import { type SelectItem } from './Select.types';
16
+ let selectedItem: SelectItem | undefined = undefined;
64
17
  </script>
65
18
 
66
- <Template let:args>
67
- <label for={args.inputId}>Select</label>
68
-
69
- <Select {...args} bind:this={component} bind:value={selectedItem} />
70
-
71
- {#if selectedItem}
72
- <code class="output">
73
- {JSON.stringify(selectedItem)}
74
- </code>
75
- {/if}
76
- </Template>
77
-
78
19
  <Story
79
20
  name="Simple"
80
- args={{
81
- inputId: 'select',
82
- clearable: true,
83
- items: [
84
- { value: 'chocolate', label: 'Chocolate' },
85
- { value: 'cake', label: 'Cake' },
86
- { value: 'ice-cream', label: 'Ice Cream' }
87
- ]
88
- }}
89
21
  play={async ({ canvasElement, step }) => {
90
22
  const canvas = within(canvasElement);
91
23
  const select = canvas.getByLabelText('Select');
@@ -112,35 +44,38 @@
112
44
  expect(selectedItem).toEqual(undefined);
113
45
  });
114
46
  }}
115
- />
47
+ >
48
+ <StoryTemplate
49
+ bind:selectedItem
50
+ args={{
51
+ inputId: 'select',
52
+ clearable: true,
53
+ items: [
54
+ { value: 'chocolate', label: 'Chocolate' },
55
+ { value: 'cake', label: 'Cake' },
56
+ { value: 'ice-cream', label: 'Ice Cream' }
57
+ ]
58
+ }}
59
+ />
60
+ </Story>
116
61
 
117
- <Story
118
- name="Grouped"
119
- args={{
120
- items: [
121
- { value: 'chocolate', label: 'Chocolate', group: 'Sweet' },
122
- { value: 'pizza', label: 'Pizza', group: 'Savory' },
123
- { value: 'cake', label: 'Cake', group: 'Sweet' },
124
- { value: 'chips', label: 'Chips', group: 'Savory' },
125
- { value: 'ice-cream', label: 'Ice Cream', group: 'Sweet' }
126
- ]
127
- }}
128
- />
62
+ <Story name="Grouped">
63
+ <StoryTemplate
64
+ bind:selectedItem
65
+ args={{
66
+ items: [
67
+ { value: 'chocolate', label: 'Chocolate', group: 'Sweet' },
68
+ { value: 'pizza', label: 'Pizza', group: 'Savory' },
69
+ { value: 'cake', label: 'Cake', group: 'Sweet' },
70
+ { value: 'chips', label: 'Chips', group: 'Savory' },
71
+ { value: 'ice-cream', label: 'Ice Cream', group: 'Sweet' }
72
+ ]
73
+ }}
74
+ />
75
+ </Story>
129
76
 
130
77
  <Story
131
78
  name="Grouped (group header selectable)"
132
- args={{
133
- inputId: 'job-select',
134
- placeholder: 'Ihr Beruf',
135
- groupHeaderSelectable: true,
136
- items: [
137
- { value: 'chocolate', label: 'Chocolate', group: 'Sweet' },
138
- { value: 'pizza', label: 'Pizza', group: 'Savory' },
139
- { value: 'cake', label: 'Cake', group: 'Sweet' },
140
- { value: 'chips', label: 'Chips', group: 'Savory' },
141
- { value: 'ice-cream', label: 'Ice Cream', group: 'Sweet' }
142
- ]
143
- }}
144
79
  play={async ({ canvasElement, step }) => {
145
80
  const canvas = within(canvasElement);
146
81
  const select = canvas.getByLabelText('Select');
@@ -168,7 +103,23 @@
168
103
  });
169
104
  });
170
105
  }}
171
- />
106
+ >
107
+ <StoryTemplate
108
+ bind:selectedItem
109
+ args={{
110
+ inputId: 'job-select',
111
+ placeholder: 'Ihr Beruf',
112
+ groupHeaderSelectable: true,
113
+ items: [
114
+ { value: 'chocolate', label: 'Chocolate', group: 'Sweet' },
115
+ { value: 'pizza', label: 'Pizza', group: 'Savory' },
116
+ { value: 'cake', label: 'Cake', group: 'Sweet' },
117
+ { value: 'chips', label: 'Chips', group: 'Savory' },
118
+ { value: 'ice-cream', label: 'Ice Cream', group: 'Sweet' }
119
+ ]
120
+ }}
121
+ />
122
+ </Story>
172
123
 
173
124
  <Story
174
125
  name="Custom items"
@@ -195,52 +146,41 @@
195
146
  });
196
147
  }}
197
148
  >
198
- <label for="job-select">Berufe</label>
199
-
200
- <Select
201
- bind:value={selectedItem}
202
- inputId="job-select"
203
- placeholder="z.B. Taxifahrer/in"
204
- items={jobsData
205
- .sort((a, b) => a.label.localeCompare(b.label))
206
- .map((item) => ({
207
- value: item.value,
208
- label: `${item.label}: ${item.add_on}`, // used for filtering
209
- details: {
210
- title: item.label, // used for display
211
- addon: item.add_on // used for display
212
- }
213
- }))}
214
- groupHeaderSelectable={false}
215
- >
216
- <div slot="item" let:item class="custom-item">
217
- <h4 class="custom-item-title" data-testid="custom-item-title">
218
- {item.details.title}
219
- </h4>
220
- <p class="custom-item-addon" data-testid="custom-item-addon">{item.details.addon}</p>
221
- </div>
222
- <div slot="selection" let:selection class="selection">
223
- {selection.details.title}
224
- </div>
225
- </Select>
226
-
227
- {#if selectedItem}
228
- <code class="output">
229
- {JSON.stringify(selectedItem)}
230
- </code>
231
- {/if}
149
+ <StoryTemplate>
150
+ {#snippet demoComponent()}
151
+ <label for="job-select">Berufe</label>
152
+ <Select
153
+ bind:value={selectedItem}
154
+ inputId="job-select"
155
+ placeholder="z.B. Taxifahrer/in"
156
+ items={jobsData
157
+ .sort((a, b) => a.label.localeCompare(b.label))
158
+ .map((item) => ({
159
+ value: item.value,
160
+ label: `${item.label}: ${item.add_on}`, // used for filtering
161
+ details: {
162
+ title: item.label, // used for display
163
+ addon: item.add_on // used for display
164
+ }
165
+ }))}
166
+ groupHeaderSelectable={false}
167
+ >
168
+ <div slot="item" let:item class="custom-item">
169
+ <h4 class="custom-item-title" data-testid="custom-item-title">
170
+ {item.details.title}
171
+ </h4>
172
+ <p class="custom-item-addon" data-testid="custom-item-addon">{item.details.addon}</p>
173
+ </div>
174
+ <div slot="selection" let:selection class="selection">
175
+ {selection.details.title}
176
+ </div>
177
+ </Select>
178
+ {/snippet}
179
+ </StoryTemplate>
232
180
  </Story>
233
181
 
234
182
  <style>
235
- .output {
236
- display: block;
237
- margin-top: 1rem;
238
- padding: 1rem;
239
- background: #dadada;
240
- }
241
-
242
183
  .custom-item {
243
- font-family: sans-serif;
244
184
  font-size: 0.9rem;
245
185
  margin-top: 0.2rem;
246
186
  }
@@ -1,84 +1,76 @@
1
1
  <script lang="ts">
2
2
  import Select from 'svelte-select';
3
+ import { type SelectItem } from './Select.types';
3
4
 
4
- // FUTURE: is it possible to highlight match in select options?
5
-
6
- /** FIXME:
7
- * NOTE: Storybook autodocs are not working for this component
8
- * because of the Select import. Set the docs manually instead. */
9
-
10
- /**
11
- * The interface for select options
12
- */
13
- interface Option {
14
- value: string;
15
- label: string;
16
- group?: string;
17
- details?: any;
5
+ interface SelectProps {
6
+ /**
7
+ * The input field's ID. Should be unique across the page.
8
+ */
9
+ inputId: string;
10
+ /**
11
+ * The input field's placeholder text
12
+ */
13
+ placeholder: string;
14
+ /**
15
+ * The list of select options
16
+ */
17
+ items: SelectItem[];
18
+ /**
19
+ * Define custom item groupings. By default items are grouped by their `group` key
20
+ */
21
+ groupBy?: ((item: SelectItem) => string) | undefined;
22
+ /**
23
+ * Whether group names should be selectable
24
+ */
25
+ groupHeaderSelectable?: boolean;
26
+ clearable?: boolean;
27
+ value: SelectItem | undefined;
18
28
  }
19
29
 
20
- /**
21
- * The input field's ID
22
- */
23
- export let inputId: string = 'select';
24
-
25
- /**
26
- * The input field's placeholder text
27
- */
28
- export let placeholder: string = 'Bitte auswählen';
29
-
30
- /**
31
- * The list of select options
32
- */
33
- export let items: Option[] = [];
34
-
35
- let groupBy: ((item: Option) => string) | undefined;
36
- $: groupBy =
37
- items.length > 0 && items.every((item) => item.group)
38
- ? (item: Option) => item.group as string
39
- : undefined;
40
-
41
- /**
42
- * Whether the group names should be selectable as well
43
- */
44
- export let groupHeaderSelectable: boolean = false;
30
+ let {
31
+ inputId = 'select',
32
+ placeholder = 'Bitte auswählen',
33
+ items = [],
34
+ groupBy,
35
+ groupHeaderSelectable = false,
36
+ clearable = true,
37
+ value = $bindable(undefined)
38
+ }: SelectProps = $props();
45
39
 
46
- /**
47
- * Enable clearing the input
48
- */
49
- export let clearable: boolean = true;
50
-
51
- export let value: Option | undefined;
40
+ const groupByFn = groupBy || ((item: SelectItem) => item.group as string);
52
41
  </script>
53
42
 
54
- <!--
55
- This component is a select input with search feature
56
- and various options such as grouped items, multi-select etc.
57
- based on https://github.com/rob-balfre/svelte-select
58
- @component
59
- -->
60
-
61
- <Select {items} {groupBy} id={inputId} {placeholder} {groupHeaderSelectable} {clearable} bind:value>
62
- <svelte:fragment slot="item" let:item>
63
- <slot name="item" {item}>
64
- {item.label}
65
- </slot>
66
- </svelte:fragment>
67
- <svelte:fragment slot="selection" let:selection>
68
- <slot name="selection" {selection}>
69
- {selection.label}
70
- </slot>
71
- </svelte:fragment>
72
- </Select>
43
+ <div class="container">
44
+ <Select
45
+ {items}
46
+ groupBy={items.length > 0 && items.every((item) => item.group) ? groupByFn : undefined}
47
+ id={inputId}
48
+ {placeholder}
49
+ {groupHeaderSelectable}
50
+ {clearable}
51
+ class="container"
52
+ bind:value
53
+ >
54
+ <div class="item" slot="item" let:item>
55
+ <slot name="item" {item}>
56
+ {item.label}
57
+ </slot>
58
+ </div>
59
+ <div class="selection" slot="selection" let:selection>
60
+ <slot name="selection" {selection}>
61
+ {selection.label}
62
+ </slot>
63
+ </div>
64
+ </Select>
65
+ </div>
73
66
 
74
67
  <style lang="scss">
75
- @import '../styles/base.scss';
76
-
77
- // TODO: styling (font etc.)
78
- // .svelte-select, .svelte-select-list {
79
- // @extend %copy;
80
- // }
81
- // --border: 3px solid blue;
82
- // --border-radius: 10px;
83
- // --placeholder-color: blue;
68
+ .item {
69
+ font-family: var(--swr-sans);
70
+ color: var(--violet-dark-5);
71
+ }
72
+ .selection {
73
+ font-family: var(--swr-sans);
74
+ color: var(--violet-dark-5);
75
+ }
84
76
  </style>
@@ -0,0 +1,8 @@
1
+ interface SelectItem {
2
+ value: string;
3
+ label: string;
4
+ group?: string;
5
+ details?: any;
6
+ }
7
+
8
+ export { type SelectItem }
@@ -0,0 +1,35 @@
1
+ <script>
2
+ import Select from './Select.svelte';
3
+ import DesignTokens from '../DesignTokens/DesignTokens.svelte';
4
+
5
+ let { args = null, selectedItem = $bindable(), demoComponent = null } = $props();
6
+ </script>
7
+
8
+ <DesignTokens>
9
+ {#if demoComponent}
10
+ {@render demoComponent()}
11
+ {:else}
12
+ <label for={args.inputId}>Select</label>
13
+ <Select {...args} bind:value={selectedItem} />
14
+ {/if}
15
+ </DesignTokens>
16
+
17
+ {#if selectedItem}
18
+ <pre class="output">
19
+ value = {JSON.stringify(selectedItem, null, ' ')}</pre>
20
+ {/if}
21
+
22
+ <style>
23
+ label {
24
+ display: block;
25
+ font-family: var(--swr-sans);
26
+ margin-bottom: 0.25em;
27
+ }
28
+ .output {
29
+ display: block;
30
+ margin-top: 1rem;
31
+ padding: 1rem;
32
+ background: #fcfaff;
33
+ color: rgb(18, 69, 139);
34
+ }
35
+ </style>