@ouestfrance/sipa-bms-ui 8.20.0 → 8.21.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 (34) hide show
  1. package/dist/components/form/BmsFilePicker.vue.d.ts +4 -0
  2. package/dist/components/form/BmsMultiSelect.vue.d.ts +4 -0
  3. package/dist/components/form/RawAutocomplete.vue.d.ts +8 -0
  4. package/dist/mockServiceWorker.js +1 -1
  5. package/dist/sipa-bms-ui.css +54 -368
  6. package/dist/sipa-bms-ui.es.js +113 -44
  7. package/dist/sipa-bms-ui.es.js.map +1 -1
  8. package/dist/sipa-bms-ui.umd.js +112 -43
  9. package/dist/sipa-bms-ui.umd.js.map +1 -1
  10. package/package.json +12 -12
  11. package/src/assets/scss/_conf.scss +0 -1
  12. package/src/assets/scss/app.scss +0 -1
  13. package/src/components/button/BmsAllButtons.stories.js +50 -23
  14. package/src/components/button/BmsButton.stories.js +153 -59
  15. package/src/components/form/BmsFilePicker.stories.js +6 -1
  16. package/src/components/form/BmsFilePicker.vue +10 -5
  17. package/src/components/form/BmsMultiSelect.vue +32 -25
  18. package/src/components/form/BmsSelect.vue +18 -16
  19. package/src/components/form/RawAutocomplete.vue +16 -4
  20. package/src/components/form/RawInputText.vue +1 -0
  21. package/src/components/layout/BmsModal.stories.js +2 -1
  22. package/src/components/layout/BmsSplitWindow.vue +0 -1
  23. package/src/components/table/BmsTableFilters.vue +1 -1
  24. package/src/documentation/button/primaryButton.mdx +142 -0
  25. package/src/documentation/{secondaryButton.mdx → button/secondaryButton.mdx} +2 -2
  26. package/src/documentation/foundation/contributing.mdx +72 -0
  27. package/src/documentation/foundation/gettingstarted.mdx +7 -0
  28. package/src/documentation/{principles.mdx → foundation/principles.mdx} +9 -9
  29. package/src/documentation/icons.mdx +43 -0
  30. package/src/showroom/pages/forms.vue +10 -1
  31. package/src/assets/scss/_formkit.scss +0 -353
  32. package/src/components/form/Form.stories.js +0 -35
  33. package/src/documentation/primaryButton.mdx +0 -20
  34. /package/src/documentation/{button.mdx → button/button.mdx} +0 -0
@@ -10,7 +10,8 @@
10
10
  :required="required"
11
11
  :small="small"
12
12
  @input="onInput"
13
- @focus="onFocus"
13
+ @click="onClick"
14
+ @keyup.down="openDatalist"
14
15
  >
15
16
  <template #icon-start>
16
17
  <slot name="icon-start"></slot>
@@ -58,7 +59,7 @@ import RawInputText from '@/components/form/RawInputText.vue';
58
59
  import { InputOption, InputType } from '@/models';
59
60
  import { ChevronDown, ChevronUp, X } from 'lucide-vue-next';
60
61
  import { FieldComponentProps } from '@/plugins/field/field-component.model';
61
- import { MaybeElementRef, onClickOutside } from '@vueuse/core';
62
+ import { MaybeElementRef, onClickOutside, onKeyUp } from '@vueuse/core';
62
63
 
63
64
  export interface Props extends FieldComponentProps {
64
65
  options: InputOption[];
@@ -82,6 +83,10 @@ const rawInput: Ref<HTMLElement | null> = ref(null);
82
83
  const emits = defineEmits<{
83
84
  addNewOption: [newOption: string];
84
85
  select: [option: InputOption];
86
+ blur: [];
87
+ focus: [];
88
+ click: [];
89
+ input: [e: InputEvent];
85
90
  }>();
86
91
 
87
92
  const getValidOptionByLabel = (label: string) =>
@@ -102,6 +107,13 @@ onClickOutside(rawInput as MaybeElementRef, closeDatalist, {
102
107
  ignore: ['.datalist-option', '.icon-toggle-button', '.icon-clear'],
103
108
  });
104
109
 
110
+ const onBlur = () => {
111
+ emits('blur');
112
+ closeDatalist();
113
+ };
114
+ onKeyUp('Escape', onBlur);
115
+ onKeyUp('Tab', onBlur);
116
+
105
117
  const classes = computed(() => {
106
118
  return { 'is-error': props.errors?.length, 'is-disabled': props.disabled };
107
119
  });
@@ -148,8 +160,8 @@ watch(
148
160
  },
149
161
  );
150
162
 
151
- const onFocus = () => {
152
- openDatalist();
163
+ const onClick = () => {
164
+ isDatalistOpen.value = !isDatalistOpen.value;
153
165
  };
154
166
 
155
167
  const onInput = () => {
@@ -21,6 +21,7 @@
21
21
  @blur="$emits('blur')"
22
22
  @input="onInput"
23
23
  @focus="$emits('focus')"
24
+ @click="$emits('click')"
24
25
  />
25
26
  <span class="field__input-icon field__input-icon--end">
26
27
  <slot name="icon-end"></slot>
@@ -1,6 +1,7 @@
1
1
  import BmsModal from '@/components/layout/BmsModal.vue';
2
+ import BmsProblem from '@/components/utils/BmsProblem.vue';
3
+ import { isProblem } from '@/helpers/problem.helper.ts';
2
4
  import { StatusType } from '@/models/status-type.model';
3
- import { BmsProblem, isProblem } from '../../index';
4
5
 
5
6
  const openNotification = {
6
7
  details: 'this is a detail',
@@ -55,7 +55,6 @@ const collapsedLocal = ref(props.collapsed ?? false);
55
55
  watch(
56
56
  () => props.collapsed,
57
57
  (val) => {
58
- console.log('watch:collapsed', val);
59
58
  collapsedLocal.value = val ?? false;
60
59
  },
61
60
  );
@@ -53,7 +53,7 @@
53
53
  :autocompleteRequest="filter?.autocompleteRequest"
54
54
  :options="getFilterOptions(filter) as any"
55
55
  @input="
56
- (e: InputEvent) =>
56
+ (e: any) =>
57
57
  $emits('filterInput', {
58
58
  filterKey: filter.key,
59
59
  value: (e.target as HTMLInputElement).value,
@@ -0,0 +1,142 @@
1
+ import { Canvas, Meta, Controls } from '@storybook/addon-docs/blocks';
2
+ import {
3
+ Playground,
4
+ DoSimple,
5
+ DoDanger,
6
+ DoSmall,
7
+ DoIconStart,
8
+ DoIconEnd,
9
+ DoSubmit,
10
+ DontLongLabel,
11
+ DoShortLabel,
12
+ DontMultiplePrimary,
13
+ DoOnePrimary,
14
+ DontTooManyIcons,
15
+ DoOneIcon,
16
+ DontGenericLabel,
17
+ DoSpecificLabel,
18
+ DontPrimaryForCancel,
19
+ DoPrimaryForSave,
20
+ } from '../../components/button/BmsButton.stories.js';
21
+
22
+ <Meta title="Documentation/Buttons/Primary" />
23
+
24
+ # <img src="./BmsIcon.png" /> Primary Button
25
+
26
+ The primary button should represent the main action on a screen. There should only be one primary button on an interface. If multiple actions are possible, it is essential to create a hierarchy of actions and use a primary and several secondary or tertiary buttons.
27
+
28
+ ## Anatomy
29
+
30
+ Buttons allow users to perform an action or to navigate to another page. They have multiple styles for various needs, and are ideal for calling attention to where a user needs to do something in order to move forward in a flow.
31
+
32
+ ![](./button/PrimaryButtonRepresentation.png)
33
+
34
+ ## Component
35
+
36
+ Use the controls below to interact with the component and see how it behaves with different configurations.
37
+
38
+ <Canvas of={Playground} />
39
+
40
+ ### Props
41
+
42
+ <Controls of={Playground} />
43
+
44
+ ## Usage Examples
45
+
46
+ ### ✅ Do: Simple action button
47
+
48
+ Use a clear, concise label (maximum 3 words) for the primary action.
49
+
50
+ <Canvas of={DoSimple} />
51
+
52
+ ### ✅ Do: Button with icon at start
53
+
54
+ Icons can help clarify the action. Place them at the start of the button.
55
+
56
+ <Canvas of={DoIconStart} />
57
+
58
+ ### ✅ Do: Button with icon at end
59
+
60
+ Place icons at the end for navigation or forward actions.
61
+
62
+ <Canvas of={DoIconEnd} />
63
+
64
+ ### ✅ Do: Submit button
65
+
66
+ Use `submit` prop for form submissions.
67
+
68
+ <Canvas of={DoSubmit} />
69
+
70
+ ### ✅ Do: Danger mode
71
+
72
+ Use danger mode for destructive actions that require caution.
73
+
74
+ <Canvas of={DoDanger} />
75
+
76
+ ### ✅ Do: Small variant
77
+
78
+ Use the small variant when space is limited.
79
+
80
+ <Canvas of={DoSmall} />
81
+
82
+ ## Rules
83
+
84
+ ### ⛔ Don't: Long labels
85
+
86
+ Avoid long labels that exceed 3 words. Keep button text concise and action-oriented.
87
+
88
+ **❌ Don't:**
89
+
90
+ <Canvas of={DontLongLabel} />
91
+
92
+ **✅ Do:**
93
+
94
+ <Canvas of={DoShortLabel} />
95
+
96
+ ### ⛔ Don't: Multiple primary buttons
97
+
98
+ Never use multiple primary buttons on the same screen. Use only one primary button per interface.
99
+
100
+ **❌ Don't:**
101
+
102
+ <Canvas of={DontMultiplePrimary} />
103
+
104
+ **✅ Do:**
105
+
106
+ <Canvas of={DoOnePrimary} />
107
+
108
+ ### ⛔ Don't: Overuse of icons
109
+
110
+ Don't overload buttons with multiple icons or decorative elements that don't add value.
111
+
112
+ **❌ Don't:**
113
+
114
+ <Canvas of={DontTooManyIcons} />
115
+
116
+ **✅ Do:**
117
+
118
+ <Canvas of={DoOneIcon} />
119
+
120
+ ### ⛔ Don't: Generic labels
121
+
122
+ Avoid generic labels like "Click here" or "Submit". Use specific action verbs.
123
+
124
+ **❌ Don't:**
125
+
126
+ <Canvas of={DontGenericLabel} />
127
+
128
+ **✅ Do:**
129
+
130
+ <Canvas of={DoSpecificLabel} />
131
+
132
+ ### ⛔ Don't: Primary for secondary actions
133
+
134
+ Don't use primary buttons for secondary or cancel actions. Reserve primary buttons for the main action.
135
+
136
+ **❌ Don't:**
137
+
138
+ <Canvas of={DontPrimaryForCancel} />
139
+
140
+ **✅ Do:**
141
+
142
+ <Canvas of={DoPrimaryForSave} />
@@ -1,7 +1,7 @@
1
1
  import { Canvas, Meta, Story } from '@storybook/addon-docs/blocks';
2
- import Button from '../components/button/BmsButton.vue';
2
+ import Button from '../../components/button/BmsButton.vue';
3
3
 
4
- <Meta title="Documentation/Buttons/Secondary Button" />
4
+ <Meta title="Documentation/Buttons/Secondary" />
5
5
 
6
6
  # <img src="./BmsIcon.png" /> Secondary Button
7
7
 
@@ -0,0 +1,72 @@
1
+ import { Meta } from '@storybook/addon-docs/blocks';
2
+
3
+ <Meta title="Foundation/Contributing" />
4
+
5
+ # <img src="./BmsIcon.png" /> Contributing
6
+
7
+ We welcome contributions from the community! To keep the codebase healthy and consistent, please follow the process outlined below before submitting a Merge Request (MR).
8
+
9
+ ## Contribution – Challenging the Need for Evolution
10
+
11
+ > **Before adding or modifying a component, question the underlying need.**
12
+ > In a design system every change propagates across many products, so it’s crucial to ensure that new work truly adds value.
13
+
14
+ 1. **Define the problem** – Who experiences the pain point? What user goal does it affect?
15
+ 2. **Consider existing solutions** – Can the issue be solved with a variant, a token adjustment, or a composition of existing components?
16
+ 3. **Evaluate impact** – Think about bundle size, visual consistency, accessibility, and maintenance overhead.
17
+
18
+ Applying this disciplined approach helps the design system stay lean, cohesive, and future‑proof while preventing unnecessary churn.
19
+
20
+ ## 1️ Discuss the Change First
21
+
22
+ Before you start coding, **open a thread** on Teams describing the problem you want to solve or the feature you’d like to add. Include:
23
+
24
+ - A clear description of the motivation.
25
+ - Any relevant design sketches or screenshot.
26
+ - Links to related tickets or upstream discussions.
27
+
28
+ This early conversation helps us:
29
+
30
+ - Validate that the change aligns with the project roadmap.
31
+ - Identify potential design conflicts early.
32
+ - Agree on an implementation approach.
33
+
34
+ Only after we give a **“Go‑ahead”** comment should you begin work on the MR.
35
+
36
+ ## 2️ Follow Our Development Standards
37
+
38
+ Your contribution must meet the project's quality expectations:
39
+
40
+ (WIP)
41
+
42
+ If any of these checks fail, the MR will be returned for revision.
43
+
44
+ ## 3️ Submit Your Merge Request
45
+
46
+ When the feature is complete:
47
+
48
+ 1. (WIP)
49
+
50
+ ## 4️ Review Process
51
+
52
+ - **Automated checks**: CI will automatically reject the MR if linting, tests, or build steps fail.
53
+ - **Human review**: At least one core maintainer will review the code. They may request changes, ask clarifying questions, or suggest improvements.
54
+ - **Approval**: Once all reviewers approve and CI passes, the MR will be merged.
55
+
56
+ > **We reserve the right to reject any MR** that does not meet our development standards, conflicts with the project direction, or introduces regressions. Rejections will be accompanied by constructive feedback so you can adjust and resubmit.
57
+
58
+ ## 5️ Post‑Merge Checklist
59
+
60
+ - Verify that the change appears in the next release notes.
61
+ - Close the associated issue (or move it to _Done_).
62
+ - Celebrate 🎉 – you’ve contributed to the project!
63
+
64
+ ---
65
+
66
+ ### Additional Tips (Optional)
67
+
68
+ - **Keep PRs small** – aim for a single logical change per MR.
69
+ - **Write clear commit messages** – follow the Conventional Commits format (`feat: add …`, `fix: resolve …`).
70
+ - **Respect the Code of Conduct** – treat all contributors with respect and professionalism.
71
+
72
+ Thank you for helping make this project better! 🙏
@@ -0,0 +1,7 @@
1
+ import { Meta } from '@storybook/addon-docs/blocks';
2
+
3
+ <Meta title="Foundation/Getting started" />
4
+
5
+ # <img src="./BmsIcon.png" /> Getting started
6
+
7
+ All the information are located in the Read Me on [Gitlab](https://gitlab.ouest-france.fr/sipa-ouest-france/platform/platform-library-vuejs-bms)
@@ -2,7 +2,7 @@ import { Meta } from '@storybook/addon-docs/blocks';
2
2
 
3
3
  ![](./CoverBmsUI.png)
4
4
 
5
- <Meta title="Documentation/Principles" />
5
+ <Meta title="Foundation/Principles" />
6
6
 
7
7
  # <img src="./BmsIcon.png" /> Principles
8
8
 
@@ -16,13 +16,13 @@ bms UI focuses on the essentials, we concentrate on what is useful for interface
16
16
 
17
17
  A constraint-based design system is designed to offer a consistent user experience across all products that use it. By limiting possible interpretations, this type of design system provides a clear and consistent logic that facilitates understanding and use of the products. Constraints can be applied to visual elements such as colors, typography, and font sizes, as well as interactive elements such as buttons and menus. By using a constraint-based design system, users can interact with products with confidence, knowing that the design elements will be consistent and predictable.
18
18
 
19
- ## 📖 Documentation of the **bms UI** Design System
19
+ ## Documentation of the **bms UI** Design System
20
20
 
21
21
  > **Mission** – Provide a **single Design System** dedicated to the Sipa group’s Back‑Office tools, limiting the use of the Vue framework and offering a constraint‑driven approach to interface design and construction. Goal: reduce costs, ensure visual and functional consistency, and simplify maintenance.
22
22
 
23
23
  ---
24
24
 
25
- ### 1️⃣ Context & Objectives
25
+ ### 1 Context & Objectives
26
26
 
27
27
  - **Who?**
28
28
  The bms UI Design System is actually maintained by the frontend team of **bms**.
@@ -35,7 +35,7 @@ A constraint-based design system is designed to offer a consistent user experien
35
35
  - **Target audience**
36
36
  Front‑end developers, designers, QA engineers, project managers, and anyone involved in creating or maintaining Back‑Office interfaces.
37
37
 
38
- ### 2️⃣ Core Principles
38
+ ### 2 Core Principles
39
39
 
40
40
  #### 2.1 Focused – Essentials First
41
41
 
@@ -59,19 +59,19 @@ A constraint-based design system is designed to offer a consistent user experien
59
59
 
60
60
  These constraints are **declarative** – they’re baked directly into components (e.g., `mode="danger"` to have a red button), preventing stylistic drift.
61
61
 
62
- ### 3️⃣ Project Architecture
62
+ ### 3 Project Architecture
63
63
 
64
64
  - **Single export** – `import BmsButton from '../components/button/BmsButton.vue';`
65
65
  - **Integrated Storybook** – Each component has a `*.stories.js` file showcasing the UI and the props & slots for each component.```
66
66
  - **Typescript components** - Each component as typed props & events, and the relevant types are exported by the library (e.g. TableHeader, Caption, etc.)
67
67
 
68
- ### 4️⃣ Usage Guide
68
+ ### 4 Usage Guide
69
69
 
70
- #### 4.1 Installation
70
+ Installation
71
71
 
72
72
  Gitlab documentation https://gitlab.ouest-france.fr/sipa-ouest-france/platform/platform-library-vuejs-bms#installation
73
73
 
74
- ### 5️⃣ Quick FAQ
74
+ ### 5 Quick FAQ
75
75
 
76
76
  - **Can I add a custom color?**
77
77
  **Answer:** No – all colors must come from the defined palette. If a legitimate need arises, open a **Jira** ticket.
@@ -82,7 +82,7 @@ Gitlab documentation https://gitlab.ouest-france.fr/sipa-ouest-france/platform/p
82
82
  - **Does the design system support dark mode?**
83
83
  **Answer:** No – theming is not available at this time.
84
84
 
85
- ### 6️⃣ Contact & Support
85
+ ### 6 Contact & Support
86
86
 
87
87
  - **Teams**: [#🎨bmsUI](https://teams.microsoft.com/l/channel/19%3Au9dPXr-JjoRoaLat-EyS-QEKit1ZzUwQ7G0VrzvDkTE1%40thread.tacv2/%F0%9F%8E%A8bmsUI?groupId=677fd2f9-de86-4bf9-a00c-71817f033ad3&tenantId=a59e9cc9-4ed4-43c4-9f1e-ca78d44b0072&ngc=true&allowXTenantAccess=)
88
88
  - **Issues**: [#Jira] (⚠️ WIP)
@@ -31,3 +31,46 @@ import { ChefHat } from 'lucide-vue-next'
31
31
  <h2> <ChefHat /> My recipes </h2>
32
32
  </template>
33
33
  ```
34
+
35
+ ## Recommendation
36
+
37
+ ## 1️⃣ Keep the Visual Style as Simple as Possible
38
+
39
+ | Rule | Why it matters | Example |
40
+ | ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------ |
41
+ | **Pick the simplest icon** – a clean outline without extra adornments. | Reduces cognitive load and improves legibility on small screens. | “Edit” → `Pencil` instead of `PencilPlus`. |
42
+ | **Limit variants** – use a single style (outline, filled, or two‑tone) for a given concept. | Prevents confusion caused by multiple looks for the same action. | “Delete” → always `Trash` (outline). |
43
+ | **Maintain consistent sizing** – stick to either 16 px or 24 px depending on context, never arbitrary sizes. | Guarantees uniform alignment across components. | Action buttons → 24 px; list items → 16 px. |
44
+ | **Avoid icon + text combos** unless the text adds essential information (e.g., “Export PDF”). | The icon alone should convey the meaning. | “Download” → just the `Download` icon. |
45
+ | **Ensure sufficient contrast** – use the accent colour or black/white based on background, meeting a ≥ 4.5:1 contrast ratio. | Makes the UI accessible for colour‑blind or low‑vision users. | Dark background → white icon; light background → black icon. |
46
+
47
+ ## 3️⃣ Recommended Icon‑to‑Action Mapping
48
+
49
+ | Action | Recommended Lucide Icon | Variant (outline / fill) | Suggested Size | Usage Note |
50
+ | ---------------------- | --------------------------------------------- | ------------------------------------ | ----------------------------- | ------------------------------------------------------------- |
51
+ | **Edit** | `Pencil` | Outline | 24 px (button) / 16 px (list) | Used to modify an existing item. |
52
+ | **Delete** | `Trash` | Outline | 24 px (button) / 16 px (list) | Always paired with a confirmation modal. |
53
+ | **Confirm / Save** | `Check` | Fill (to indicate an “active” state) | 24 px | Can replace the word “Save” on primary action bars. |
54
+ | **Cancel / Close** | `X` _(alias `Close`)_ | Outline | 24 px | Closes dialogs or cancels an operation. |
55
+ | **Add / Create** | `Plus` | Outline | 24 px | Prefer the plain `Plus` over `PlusCircle` for minimalism. |
56
+ | **Download** | `Download` | Outline | 24 px | Placed next to a file‑download link. |
57
+ | **Export** | `Upload` _(or `Share2` depending on context)_ | Outline | 24 px | Use `Upload` for exporting data, `Share2` for sharing a link. |
58
+ | **Search** | `Search` | Outline | 24 px | Visible even when the input field is empty. |
59
+ | **Filter** | `Filter` | Outline | 24 px | Appears beside a “Filter” button. |
60
+ | **Preview** | `Eye` | Outline | 24 px | Lets users preview content without editing. |
61
+ | **Expand / Collapse** | `ChevronDown` / `ChevronUp` | Outline | 16 px (inside lists) | Indicates accordion or dropdown state. |
62
+ | **Open External Link** | `ExternalLink` | Outline | 16 px | Shows next to a link that opens a new tab/window. |
63
+
64
+ > **Tip:** If you ever need an icon that Lucide doesn’t provide, create one that follows the same visual language (2 px stroke, rounded corners, no fill) to stay consistent with the rest of the set.
65
+
66
+ ---
67
+
68
+ ### 📌 Quick Checklist
69
+
70
+ 1. **Choose the simplest outline icon** for the action.
71
+ 2. **Standardise size & stroke width** (`16 px` or `24 px`, `strokeWidth={2}`).
72
+ 3. **Respect colour contrast** (dark icon on light background, light icon on dark background).
73
+ 4. **Import icons centrally** and re‑export them.
74
+ 5. **Add visual regression tests** for every icon used in the UI.
75
+
76
+ Following these guidelines will keep the UI clean, professional, and accessible while leveraging the full power of the Lucide icon library. 🎨🚀
@@ -240,6 +240,7 @@
240
240
  <BmsSelect
241
241
  label="select"
242
242
  required
243
+ @select="console.log('BmsSelect - select', $event)"
243
244
  v-model="selected"
244
245
  placeholder="This is a placeholder"
245
246
  :options="[{ label: 'value1', value: 'value1' }]"
@@ -262,6 +263,8 @@
262
263
  required
263
264
  placeholder="This is a placeholder"
264
265
  v-model="multiSelect"
266
+ @select="console.log('BmsMultiSelect - select', $event)"
267
+ @input="console.log('BmsMultiSelect - input', $event)"
265
268
  :options="[
266
269
  { label: 'toto1', value: 'value1', icon: BellIcon },
267
270
  { label: 'titi2', value: 'value2', icon: Magnet },
@@ -291,7 +294,13 @@
291
294
  placeholder="This is a placeholder"
292
295
  required
293
296
  v-model="auto"
294
- :options="[{ label: 'value1', value: '1' }]"
297
+ @select="console.log('BmsAutocomplete - select', $event)"
298
+ @input="console.log('BmsAutocomplete - input', $event)"
299
+ :options="[
300
+ { label: 'value1', value: '1' },
301
+ { label: 'value2', value: '2' },
302
+ { label: 'value3', value: '3' },
303
+ ]"
295
304
  />
296
305
  <BmsInputNumber label="Chiffre" v-model="nbr" :min="5" :max="15" />
297
306