pns-component-library 1.5.14 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@ A comprehensive Vue 3 component library built with modern development practices,
4
4
 
5
5
  ## 🚀 Features
6
6
 
7
- - **9 Production-Ready Components** - Form controls, layout components, and utility components
7
+ - **11 Production-Ready Components** - Form controls, layout components, and utility components
8
8
  - **2 Powerful Composables** - Reusable logic for common patterns
9
9
  - **2 Custom Directives** - Loading states and UI enhancements
10
10
  - **Vue 3 Composition API** - Modern, performant, and type-safe
@@ -12,11 +12,32 @@ A comprehensive Vue 3 component library built with modern development practices,
12
12
  - **Accessibility** - ARIA attributes and keyboard navigation support
13
13
  - **Customizable Themes** - Multiple built-in color schemes
14
14
 
15
+ > Newly released public components: `DropdownMenu`, `Badge`, `FloatingActionButton`, `DynamicColorResponsiveButton`.
16
+
17
+ ## 🆕 What's New
18
+
19
+ ### 2025-10-28
20
+
21
+ - Public release of:
22
+ - `DropdownMenu`
23
+ - `Badge`
24
+ - `FloatingActionButton`
25
+ - `DynamicColorResponsiveButton`
26
+
27
+ Import options:
28
+
29
+ - Global (plugin):
30
+ - `app.use(PnsComponentLibrary)` then use components directly in templates.
31
+ - Named import:
32
+ - `import { DropdownMenu, Badge, FloatingActionButton, DynamicColorResponsiveButton } from 'pns-component-library'`
33
+
15
34
  ## 📦 Installation
16
35
 
17
36
  ```bash
18
37
  npm install pns-component-library
19
38
  ```
39
+
40
+
20
41
  or
21
42
 
22
43
  ```bash
@@ -52,7 +73,15 @@ app.mount('#app')
52
73
  <!-- Phone input with country selection -->
53
74
  <AreaCodePhoneInput v-model="phoneData" />
54
75
 
55
- <!-- Responsive button -->
76
+ <!-- Dynamic color responsive button (Recommended) -->
77
+ <DynamicColorResponsiveButton
78
+ display_name="Click me"
79
+ button_type="filled"
80
+ built_in_theme="primary"
81
+ @click-button="handleClick"
82
+ />
83
+
84
+ <!-- Legacy button (Deprecated but still works; not maintained). New projects should migrate to DynamicColorResponsiveButton. -->
56
85
  <ResponsiveButton @click="handleClick">
57
86
  Click me
58
87
  </ResponsiveButton>
@@ -118,104 +147,197 @@ International phone number input with country selection and area code handling.
118
147
  </template>
119
148
  ```
120
149
 
150
+ ### Deprecated Components
151
+
152
+ > Note: Deprecated components still work and are supported for backward compatibility, but they are no longer maintained. For new development, please migrate to the recommended replacements.
153
+
154
+ #### ResponsiveButton (Deprecated, still works)
155
+ Legacy feature-rich button component superseded by `DynamicColorResponsiveButton`.
156
+
157
+ ##### Attributes
158
+
159
+ | Attribute | Description | Type | Default |
160
+ |-----------|-------------|------|---------|
161
+ | display_name | button text | `string` | `'button'` |
162
+ | size | button size | `'xsmall' \| 'small' \| 'medium' \| 'large'` | `'small'` |
163
+ | width_type | button width type | `'fill-whole' \| 'fit-content'` | `'fit-content'` |
164
+ | build_in_theme | color theme | `'outlined-primary' \| 'filled-primary' \| 'text-primary' \| 'outlined-secondary' \| 'filled-secondary' \| 'text-secondary'` | `'outlined-primary'` |
165
+ | customized_class | custom CSS class | `string` | — |
166
+ | hold | whether button is in hold state | `boolean` | `false` |
167
+ | disabled | whether button is disabled | `boolean` | `false` |
168
+ | dropdown_options | dropdown menu options | `array` | `[]` |
169
+ | is_dropdown_option | whether this is a dropdown item | `boolean` | `false` |
170
+
171
+ ##### Events
172
+
173
+ | Event | Description | Parameters |
174
+ |-------|-------------|------------|
175
+ | click | triggers when button is clicked | `(event: MouseEvent)` |
176
+ | update-currently-hovered-dropdown-option | triggers when dropdown option is hovered | `(option: object)` |
177
+ | add-nested-dropdown-history-stack | triggers when nested dropdown is navigated | `(option: object)` |
178
+ | remove-nested-dropdown-history-stack | triggers when navigating back in nested dropdown | — |
179
+
180
+ ##### Slots
181
+
182
+ | Name | Description |
183
+ |------|-------------|
184
+ | prefix | content before button text |
185
+ | suffix | content after button text |
186
+
187
+ ```vue
188
+ <template>
189
+ <ResponsiveButton
190
+ display_name="Save Changes"
191
+ size="medium"
192
+ build_in_theme="filled-primary"
193
+ @click="handleSave"
194
+ >
195
+ <template #prefix>
196
+ <img :src="iconsMap['eye_icon']" alt="eye" />
197
+ </template>
198
+ </ResponsiveButton>
199
+ </template>
200
+ ```
201
+
121
202
  #### InputBox
122
- Versatile input field component with validation and styling options.
203
+ Versatile input field with clearable icon, inside/outside label, styled themes, and validation helpers. The component dynamically adjusts border/background/label colors based on state (hover, focus, disabled, alert, error) and supports prefix/suffix slots.
123
204
 
124
205
  ##### Attributes
125
206
 
126
207
  | Attribute | Description | Type | Default |
127
208
  |-----------|-------------|------|---------|
128
- | v-model / modelValue | binding value - the current input text value | `string` | |
129
- | type | input type | `string` | `'text'` |
209
+ | v-model / modelValue | bound input value | `string` | `''` |
210
+ | label | label text (optional) | `string` | `''` |
211
+ | label_type | where to render the label | `'inside' \| 'outside'` | `'outside'` |
212
+ | theme_type | visual styling theme | `'filled' \| 'outlined'` | `'outlined'` |
213
+ | customized_theme_color | focus/brand color used for focus border or underline; any valid CSS color or CSS var | `string` | `''` |
214
+ | type | native input type | `string` | `'text'` |
130
215
  | placeholder | placeholder text | `string` | `'Enter'` |
131
- | clearable | whether to show clear button | `boolean` | `false` |
132
- | disabled | whether input is disabled | `boolean` | `false` |
133
- | autocomplete | autocomplete attribute for input, raw input tag values work | `string` | `'off'` |
216
+ | clearable | show a clear icon when focused/hovered and value is non-empty | `boolean` | `false` |
217
+ | required | show a required asterisk on the label. Visual-only; does not enforce validation | `boolean` | `false` |
218
+ | disabled | disable the input | `boolean` | `false` |
219
+ | autocomplete | native `autocomplete` | `string` | `'off'` |
220
+
221
+ Behavior notes:
222
+
223
+ - In `filled` theme, a bottom inset shadow emulates the underline. In `outlined` theme, an inset border is used. Colors are derived from:
224
+ - Focus: `customized_theme_color` (or `var(--Schemes-primary)` fallback)
225
+ - Hover: `#17181C`
226
+ - Disabled: `var(--disabled-color--)` and `var(--disabled-button-background-color--)`
227
+ - Alert/Error: `var(--semantic-alert-color--)` / `var(--semantic-error-color--)`
228
+ - Inside label floats above the input content area and inherits a state color similar to the border color.
229
+ - A message region appears below the field when either `#supportingText` slot has content or when alert/error is active with a message.
230
+ - `#supportingText` is neutral and does not adopt alert/error colors; alert/error messages are colored separately.
231
+ - Alert/Error messages are set via exposes: `alert(message)` / `error(message)`; call `removeAlertOrErrorEffect()` to clear.
232
+ - Disabled state down-weights message colors.
134
233
 
135
234
  ##### Events
136
235
 
137
236
  | Event | Description | Parameters |
138
237
  |-------|-------------|------------|
139
- | input | triggers when input value changes | `(value: string)` |
140
- | change | triggers when input value changes and loses focus | `(value: string)` |
141
- | focus | triggers when input is focused | `(event: FocusEvent)` |
142
- | blur | triggers when input is blurred | `(event: FocusEvent)` |
143
- | clear | triggers when clear button is clicked | |
238
+ | input | fires on each keystroke | `(value: string)` |
239
+ | change | fires on Enter or blur | `(value: string)` |
240
+ | focus | input focused | `()` |
241
+ | blur | input blurred | `()` |
242
+ | clear | clear button clicked | `()` |
144
243
 
145
244
  ##### Exposes
146
245
 
147
- | Method | Description | Type |
148
- |--------|-------------|------|
149
- | focus | focus the input | `() => void` |
150
- | blur | blur the input | `() => void` |
151
- | alert | show alert message | `(message: string) => void` |
152
- | error | show error message | `(message: string) => void` |
153
- | removeAlertOrErrorEffect | clear alert/error state | `() => void` |
246
+ | Method | Description |
247
+ |--------|-------------|
248
+ | focus | programmatically focus the input |
249
+ | blur | programmatically blur the input |
250
+ | alert | show alert state with a message (yellow) |
251
+ | error | show error state with a message (red) |
252
+ | removeAlertOrErrorEffect | clear alert/error state |
154
253
 
155
254
  ##### Slots
156
255
 
157
256
  | Name | Description |
158
257
  |------|-------------|
159
- | prefix | content before input |
160
- | suffix | content after input |
258
+ | prefix | content rendered before the input (e.g., an icon) |
259
+ | suffix | content rendered after the input (e.g., an icon) |
260
+ | supportingText | optional helper text rendered below the field (e.g., character counter like `0/100`). This is distinct from alert/error messages and uses a neutral color. When alert/error is active, their messages render alongside (with their own colors) |
261
+
262
+ > Tip: For prefix/suffix icons, prefer inline SVG that uses `fill="currentColor"`. This way the icon color automatically follows the component state (hover/focus/disabled/alert/error). If you use `<img>` sources, they won't inherit color.
263
+
264
+ ##### Usage
161
265
 
162
266
  ```vue
163
- <InputBox
164
- v-model="value"
165
- type="text"
166
- placeholder="Enter text"
167
- clearable
168
- >
169
- <template #prefix>
170
- <Icon name="search" />
171
- </template>
172
- </InputBox>
173
- ```
267
+ <template>
268
+ <InputBox
269
+ v-model="value"
270
+ label="Email"
271
+ label_type="inside"
272
+ theme_type="outlined"
273
+ customized_theme_color="var(--Schemes-primary)"
274
+ type="email"
275
+ placeholder="name@example.com"
276
+ clearable
277
+ autocomplete="email"
278
+ >
279
+ <template #prefix>
280
+ <img src="/icons/mail.svg" style="width:100%;height:100%"/>
281
+ </template>
282
+ </InputBox>
283
+ </template>
284
+
285
+ <script setup>
286
+ import { ref } from 'vue'
287
+ const value = ref('')
288
+ </script>
174
289
 
175
290
  #### SingleSelector
176
- Dropdown selector component with filtering and search capabilities.
291
+ Dropdown selector with inside/outside label, themed styling, filtering, and optional remote search. Mirrors `InputBox` visual behavior (hover/focus/disabled/alert/error), and supports prefix/suffix slots plus a neutral `supportingText` message region.
177
292
 
178
293
  ##### Attributes
179
294
 
180
295
  | Attribute | Description | Type | Default |
181
296
  |-----------|-------------|------|---------|
182
- | v-model / modelValue | binding value - the `value` property of the selected option from the options array | `string \| number \| boolean \| object \| array` | — |
183
- | options | options array | `Array<{value: any, label: string}>` | `[]` |
184
- | filterable | whether to enable filtering | `boolean` | `false` |
185
- | remote_search | whether to enable remote search | `boolean` | `false` |
186
- | disabled | whether selector is disabled | `boolean` | `false` |
187
- | placeholder | placeholder text | `string` | `'Select'` |
188
- | clearable | whether to show clear button | `boolean` | `false` |
189
- | loading | loading state | `boolean` | `false` |
297
+ | v-model / modelValue | binding value (selected option's `value`) | `string \| number \| boolean \| object \| array` |
298
+ | label | label text | `string` | `''` |
299
+ | label_type | label position | `'inside' \| 'outside'` | `'outside'` |
300
+ | theme_type | visual theme | `'filled' \| 'outlined'` | `'outlined'` |
301
+ | customized_theme_color | focus/brand color; any CSS color or CSS var | `string` | `''` |
302
+ | required | show red asterisk next to label | `boolean` | `false` |
303
+ | options | options array | `Array<{ value: string \| number \| boolean \| object, label: string, prefix?: string, suffix?: string, prefix_slot_raw_html_content?: string, suffix_slot_raw_html_content?: string, options?: Option[], has_divider?: boolean, disabled?: boolean, is_selected?: boolean }>` | `[]` |
304
+ | filterable | enable client-side filter input | `boolean` | `false` |
305
+ | remote_search | emit query outward instead of local filtering | `boolean` | `false` |
306
+ | disabled | disable selector | `boolean` | `false` |
307
+ | placeholder | placeholder when no value | `string` | `'Select'` |
308
+ | clearable | show clear icon when there is a value and focused/hovered | `boolean` | `false` |
309
+ | loading | loading state (mirrors dropdown's `is_loading`) | `boolean` | `false` |
190
310
  | loading_text | loading text | `string` | `'Loading'` |
191
- | no_data_text | no data text | `string` | `'No data'` |
192
- | filter_only_among_options_value | filter only among option values | `boolean` | `false` |
193
- | filter_only_among_options_label | filter only among option labels | `boolean` | `false` |
311
+ | no_data_text | empty-state text | `string` | `'No data'` |
312
+ | filter_only_among_options_value | filter against serialized `value` only | `boolean` | `false` |
313
+ | filter_only_among_options_label | filter against `label` only | `boolean` | `false` |
314
+
315
+ Notes:
194
316
 
195
- > **Note**: `filter_only_among_options_value` and `filter_only_among_options_label` cannot be true at the same time. If both are true, `filter_only_among_options_value=true` will override `filter_only_among_options_label`'s logic.
317
+ - `filter_only_among_options_value` and `filter_only_among_options_label` cannot both be `true`. If both are `true`, filtering uses `value`.
318
+ - Option `value` may be primitive or object; selection compares by `value` plus `label` for marking `is_selected` in lists.
196
319
 
197
320
  ##### Events
198
321
 
199
322
  | Event | Description | Parameters |
200
323
  |-------|-------------|------------|
201
324
  | change | triggers when the binding value changes | `(value: any)` |
202
- | remote-search | triggers when remote search is performed | `(query: string)` |
203
- | filter-change | triggers when filter text changes | `(query: string)` |
204
- | visible-change | triggers when dropdown visibility changes | `(visible: boolean)` |
205
- | focus | triggers when selector is focused | `(event: FocusEvent)` |
206
- | blur | triggers when selector is blurred | `(event: FocusEvent)` |
207
- | clear | triggers when clear button is clicked | |
325
+ | remote-search | triggers when `remote_search=true` and user types | `(query: string)` |
326
+ | filter-change | triggers whenever filter text changes | `(query: string)` |
327
+ | visible-change | dropdown visibility changes | `(visible: boolean)` |
328
+ | focus | selector focused | `()` |
329
+ | blur | selector blurred | `()` |
330
+ | clear | clear button clicked | `()` |
208
331
 
209
332
  ##### Exposes
210
333
 
211
- | Method | Description | Type |
212
- |--------|-------------|------|
213
- | focus | focus the selector | `() => void` |
214
- | blur | blur the selector | `() => void` |
215
- | alert | show alert message | `(message: string) => void` |
216
- | error | show error message | `(message: string) => void` |
217
- | removeAlertOrErrorEffect | clear alert/error state | `() => void` |
218
- | setDropdownContentLoading | set loading state | `(loading: boolean) => void` |
334
+ | Method | Description |
335
+ |--------|-------------|
336
+ | focus | programmatically focus the selector |
337
+ | blur | programmatically blur the selector |
338
+ | alert | show alert state with a message (yellow) |
339
+ | error | show error state with a message (red) |
340
+ | removeAlertOrErrorEffect | clear alert/error state |
219
341
 
220
342
  ##### Slots
221
343
 
@@ -223,11 +345,13 @@ Dropdown selector component with filtering and search capabilities.
223
345
  |------|-------------|
224
346
  | prefix | content before selector |
225
347
  | suffix | content after selector |
348
+ | supportingText | neutral helper text under the field; alert/error messages render alongside in their own colors |
226
349
 
227
350
  ```vue
228
351
  <SingleSelector
229
352
  v-model="selected"
230
353
  :options="options"
354
+ {{ ... }}
231
355
  filterable
232
356
  clearable
233
357
  placeholder="Choose option"
@@ -235,9 +359,25 @@ Dropdown selector component with filtering and search capabilities.
235
359
  <template #prefix>
236
360
  <Icon name="search" />
237
361
  </template>
362
+ <template #supportingText>
363
+ <!-- e.g. instruction or character counter; neutral color -->
364
+ Pick one option
365
+ </template>
238
366
  </SingleSelector>
239
367
  ```
240
368
 
369
+ ##### DropdownMenu (dev-only)
370
+
371
+ Used internally by components to render options lists.
372
+
373
+ - Props: `width_type: 'fill-whole'|'fit-content'`, `options` (same schema as above), `size: 'small'|'medium'|'large'`, `with_box_shadow`, `is_loading`, `loading_text`, `no_data_text`.
374
+ - Events:
375
+ - `select-dropdown-option(value, label, in_dropdown_level)`
376
+ - `add-new-shown-nested-dropdown(target_item_props, trigger)`
377
+ - `remove-shown-nested-dropdown(target_item_props)`
378
+ - `update-shown-nested-dropdown(target_item_props)`
379
+ - `click-outside-dropdown-menu(event)`
380
+
241
381
  #### Checkbox
242
382
  Customizable checkbox with multiple themes and validation support.
243
383
 
@@ -248,7 +388,8 @@ Customizable checkbox with multiple themes and validation support.
248
388
  | v-model / modelValue | binding value - `true` when checked, `false` when unchecked | `boolean` | `false` |
249
389
  | label | checkbox label | `string` | — |
250
390
  | size | checkbox size | `'small' \| 'medium'` | `'medium'` |
251
- | built_in_theme | color theme | `'green' \| 'primary-blue' \| 'secondary-blue' \| 'red'` | `'green'` |
391
+ | built_in_theme | built-in color theme | `'green' \| 'primary-blue' \| 'secondary-blue'` | `'green'` |
392
+ | customized_theme_color | custom theme color (overrides built_in_theme) | `string`(any valid CSS color value) | `''` |
252
393
  | customized_class | custom CSS class | `string` | — |
253
394
  | disabled | whether checkbox is disabled | `boolean` | `false` |
254
395
  | autofocus | whether to auto focus | `boolean` | `false` |
@@ -266,15 +407,31 @@ Customizable checkbox with multiple themes and validation support.
266
407
  |--------|-------------|------|
267
408
  | focus | focus the checkbox | `() => void` |
268
409
  | blur | blur the checkbox | `() => void` |
269
- | alert | show error message | `(message: string) => void` |
410
+ | error | show error message, will automatically use the error theme | `(message: string) => void` |
270
411
  | removeAlertOrErrorEffect | clear error state | `() => void` |
271
412
 
272
413
  ```vue
273
414
  <template>
415
+ <!-- the #FF0000 will override the built-in theme color -->
274
416
  <Checkbox
275
417
  v-model="agreed"
276
418
  label="I agree to terms"
277
419
  built_in_theme="primary-blue"
420
+ customized_theme_color="#FF0000"
421
+ @change="handleChange"
422
+ />
423
+ <!-- if want to use global defined color as the theme color, can use var() -->
424
+ <Checkbox
425
+ v-model="agreed"
426
+ label="I agree to terms"
427
+ customized_theme_color="var(--Schemes-primary)"
428
+ @change="handleChange"
429
+ />
430
+ <!-- Theme color can be any form of CSS color value. Since if the theme color isn't empty, it will override the built-in theme color. The background color of the checked and indeterminate state, and the mask color of the interaction effects of the checkbox will be the theme color, while the border color of unchecked checkbox will always be hsl(226, 8%, 10%). But when the checkbox is in error state, the theme will be error theme (the border color of unchecked checkbox, the background color of the checked and indeterminate state, and the mask color of the interaction effects of the checkbox will always be --semantic-error-color--) -->
431
+ <Checkbox
432
+ v-model="agreed"
433
+ label="I agree to terms"
434
+ customized_theme_color="hsl(3, 71%, 40%)"
278
435
  @change="handleChange"
279
436
  />
280
437
  </template>
@@ -306,8 +463,86 @@ Container for managing multiple related checkboxes as a group.
306
463
  const selectedItems = ref(['option1'])
307
464
  const options = ['option1', 'option2', 'option3']
308
465
  </script>
466
+
467
+ #### DropdownMenu
468
+ Public dropdown menu primitive with nested dropdown support and responsive behavior (web vs. mobile). The companion `DropdownMenuItem` remains internal and is not exposed publically.
469
+
470
+ ##### Attributes
471
+
472
+ | Attribute | Description | Type | Default |
473
+ |-----------|-------------|------|---------|
474
+ | options | menu options tree (see schema below) | `Array<Option>` | `[]` |
475
+ | size | menu item sizing | `'small' \| 'medium' \| 'large'` | `'small'` |
476
+ | width_type | width behavior | `'fill-whole' \| 'fit-content'` | `'fill-whole'` |
477
+ | with_box_shadow | whether the menu container has a shadow | `boolean` | `true` |
478
+
479
+ Option schema (recursive):
480
+
481
+ ```
482
+ type Option = {
483
+ value: string,
484
+ label: string,
485
+ // Optional visuals (priority: slot > raw HTML > image URL handled inside items)
486
+ prefix?: string,
487
+ suffix?: string,
488
+ prefix_slot_raw_html_content?: string,
489
+ suffix_slot_raw_html_content?: string,
490
+ // Nesting
491
+ options?: Option[],
492
+ // Visual divider hint for the following item
493
+ has_divider?: boolean,
494
+ // Disabled state
495
+ disabled?: boolean,
496
+ }
309
497
  ```
310
498
 
499
+ Behavior notes:
500
+
501
+ - On wide screens (width > 1024), nested dropdowns open as side menus on hover/click with smart positioning.
502
+ - On small screens (≤ 1024), a single column flow is used; nested levels replace the list view using an internal history stack and a back affordance.
503
+ - The component emits a click-outside event when the user clicks outside of the root dropdown.
504
+
505
+ ##### Events
506
+
507
+ | Event | Description | Parameters |
508
+ |-------|-------------|------------|
509
+ | select-dropdown-option | emitted when an option without further nesting is selected | `(value: string, in_dropdown_level: number)` |
510
+ | click-outside-dropdown-menu | emitted when clicking outside the root menu | `—` |
511
+
512
+ ##### Usage
513
+
514
+ ```vue
515
+ <template>
516
+ <DropdownMenu
517
+ :options="options"
518
+ :size="'small'"
519
+ :width_type="'fill-whole'"
520
+ @click-outside-dropdown-menu="onOutside"
521
+ @select-dropdown-option="onSelect"
522
+ />
523
+ </template>
524
+
525
+ <script setup>
526
+ import { DropdownMenu } from 'pns-component-library'
527
+
528
+ const options = [
529
+ {
530
+ value: 'option1',
531
+ label: 'Option 1',
532
+ options: [
533
+ { value: 'option1.1', label: 'Option 1.1' },
534
+ { value: 'option1.2', label: 'Option 1.2', options: [
535
+ { value: 'option1.2.1', label: 'Option 1.2.1' },
536
+ ]},
537
+ ],
538
+ },
539
+ { value: 'option2', label: 'Option 2' },
540
+ ]
541
+
542
+ function onOutside(){ /* handle outside click */ }
543
+ function onSelect(value, level){ /* handle select */ }
544
+ </script>
545
+
311
546
  #### Radio
312
547
  Radio button component for single selection from multiple options.
313
548
 
@@ -349,56 +584,265 @@ Radio button component for single selection from multiple options.
349
584
  <Radio v-model="single" value="yes" label="Agree to terms" />
350
585
  ```
351
586
 
587
+ #### Badge
588
+ Notification badge component with customizable size, position, themes, and content display. Features automatic text color calculation for optimal contrast.
589
+
590
+ ##### Attributes
591
+
592
+ | Attribute | Description | Type | Default |
593
+ |-----------|-------------|------|---------|
594
+ | content | badge content (number or text) | `string \| number` | `''` |
595
+ | size | badge size | `'small' \| 'medium' \| 'large'` | `'small'` |
596
+ | position_type | badge position relative to content | `'top-right' \| 'center-right'` | `'top-right'` |
597
+ | customized_top_distance | top offset for top-right position (CSS length) | `string` | `''` |
598
+ | customized_right_distance | right offset for top-right position (CSS length) | `string` | `''` |
599
+ | built_in_theme | color theme | `'primary' \| 'secondary'` | `'primary'` |
600
+ | customized_theme_color | custom background color (any valid CSS color) | `string` | `''` |
601
+ | customized_class | custom CSS class | `string` | `''` |
602
+ | disabled | hide the badge without affecting the wrapped content | `boolean` | `false` |
603
+
604
+ ##### Size Specifications
605
+
606
+ | Size | Web (px) | Mobile ≤767px (px) | With Content |
607
+ |------|----------|-------------------|--------------|
608
+ | small | 6×6 | 6×6 | auto-sized |
609
+ | medium | 8×8 | 6×6 | auto-sized |
610
+ | large | 16×16 | 16×16 | auto-sized |
611
+
612
+ > **Note**: When badge has content, it automatically becomes large with padding (min-width: 20px, height: 20px).
613
+
614
+ ##### Position Types
615
+
616
+ | Position | Behavior |
617
+ |----------|----------|
618
+ | top-right | Positioned absolutely at top-right corner (0, 0) |
619
+ | center-right | Inline-flex aligned at right side with 4px gap |
620
+
621
+ > **Note**: Use padding on the wrapper div to control badge position. The badge positions relative to the wrapper's content.
622
+
623
+ Position customization (top-right only):
624
+
625
+ - You can fine-tune the distance of a top-right badge using `customized_top_distance` and `customized_right_distance`. These map to internal CSS variables and only apply when `position_type="top-right"`.
626
+
627
+ ```vue
628
+ <Badge position_type="top-right" customized_top_distance="8px" customized_right_distance="12px" content="3">
629
+ <div style="padding: 8px;">
630
+ <div>Icon</div>
631
+ </div>
632
+
633
+ </Badge>
634
+ ```
635
+
636
+ ##### Built-in Themes
637
+
638
+ | Theme | Background | Text Color |
639
+ |-------|-----------|------------|
640
+ | primary | #AF251D (red) | #FFFFFF (white) |
641
+ | secondary | #CFE0FC (light blue) | #083D91 (dark blue) |
642
+
643
+ ##### Custom Theme Color
644
+
645
+ When using `customized_theme_color`, the text color is automatically calculated based on the background lightness:
646
+ - Light backgrounds (lightness > 50%) → dark text
647
+ - Dark backgrounds (lightness ≤ 50%) → light text
648
+
649
+ The algorithm adjusts lightness by ±60 while preserving hue and saturation for optimal contrast and color harmony.
650
+
651
+ ```vue
652
+ <template>
653
+ <!-- Basic badge with sizes -->
654
+ <Badge size="small">
655
+ <div style="padding: 4px;">
656
+ <div>Icon</div>
657
+ </div>
658
+ </Badge>
659
+
660
+ <!-- Badge with content -->
661
+ <Badge content="5">
662
+ <div style="padding: 8px;">
663
+ <div>Icon</div>
664
+ </div>
665
+ </Badge>
666
+
667
+ <!-- Badge with position types -->
668
+ <Badge position_type="top-right" content="3">
669
+ <div style="padding: 8px;">
670
+ <div>Icon</div>
671
+ </div>
672
+ </Badge>
673
+
674
+ <!-- Badge with custom color -->
675
+ <Badge customized_theme_color="#10B981" content="New">
676
+ <div style="padding: 8px;">
677
+ <div>Icon</div>
678
+ </div>
679
+ </Badge>
680
+
681
+ <!-- Dark background example -->
682
+ <Badge customized_theme_color="hsl(220, 70%, 25%)" content="Dark">
683
+ <div style="padding: 8px;">
684
+ <div>Icon</div>
685
+ </div>
686
+ </Badge>
687
+ </template>
688
+ ```
689
+
352
690
  ### Interactive Components
353
691
 
354
- #### ResponsiveButton
355
- Feature-rich button component with responsive behavior and multiple states.
692
+ #### DynamicColorResponsiveButton
693
+ Modern button with dynamic color logic and responsive behavior.
694
+
695
+ ##### Attributes
696
+
697
+ | Attribute | Description | Type | Default |
698
+ |-----------|-------------|------|---------|
699
+ | button_type | button style variant | `'filled' \| 'outlined' \| 'text' \| 'elevated'` | `'filled'` |
700
+ | button_border_radius | CSS border-radius value | `string` | `'100px'` |
701
+ | built_in_theme | built-in color theme | `'primary' \| 'secondary' \| 'tertiary' \| 'primary-container' \| 'secondary-container' \| 'tertiary-container'` | `'primary'` |
702
+ | customized_theme_color | overrides built_in_theme with any CSS color (e.g., `#3d5c8f`, `rgb(...)`, `hsl(...)`, `var(--Schemes-primary)`) | `string` | `''` |
703
+ | customized_class | custom CSS class for outer container | `string` | `''` |
704
+ | width_type | width mode | `'fill-whole' \| 'fit-content'` (auto-handles icon-only with `narrow`/`wide`) | `'fit-content'` |
705
+ | button_id | explicit id used in emitted event; falls back to `display_name` | `string` | `''` |
706
+ | display_name | button text (used when default slot empty) | `string` | `'button name'` |
707
+ | prefix | image url for prefix when not using slot | `string` | `''` |
708
+ | suffix | image url for suffix when not using slot | `string` | `''` |
709
+ | prefix_slot_raw_html_content | raw html for prefix (when not using named slot) | `string` | `''` |
710
+ | suffix_slot_raw_html_content | raw html for suffix (when not using named slot) | `string` | `''` |
711
+ | disabled | disable interaction | `boolean` | `false` |
712
+ | has_interaction_effect | enable hover/focus/active visual effects (mask and slight radius changes). Set to `false` to keep the button static (useful when embedding purely for coloring icons/slots) | `boolean` | `true` |
713
+ | size | size (Web/Tablet enum) | `'small' \| 'medium' \| 'large' \| 'xlarge'` | `'small'` |
714
+
715
+ ##### Events
716
+
717
+ | Event | Description | Parameters |
718
+ |-------|-------------|------------|
719
+ | click-button | emitted on button click | `(button_id: string, display_name: string)` |
720
+
721
+ ##### Exposes
722
+
723
+ | Method | Description | Type |
724
+ |--------|-------------|------|
725
+ | handleFocusButton | programmatically apply focus style | `() => void` |
726
+ | handleBlurButton | remove focus style | `() => void` |
727
+
728
+ ##### Slots
729
+
730
+ | Name | Description |
731
+ |------|-------------|
732
+ | prefix | content before the label (e.g., icon) |
733
+ | default | label content; falls back to `display_name` |
734
+ | suffix | content after the label (e.g., icon) |
735
+
736
+ ```vue
737
+ <template>
738
+ <DynamicColorResponsiveButton
739
+ button_id="save-changes"
740
+ display_name="Save Changes"
741
+ button_type="filled"
742
+ built_in_theme="primary"
743
+ :has_interaction_effect="true"
744
+ width_type="fit-content"
745
+ @click-button="onSave"
746
+ >
747
+ <template #prefix>
748
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M5 12h14v2H5z"/></svg>
749
+ </template>
750
+ </DynamicColorResponsiveButton>
751
+ </template>
752
+
753
+ <script setup>
754
+ function onSave(id, name){
755
+ console.log('clicked:', id, name)
756
+ }
757
+ </script>
758
+ ```
759
+
760
+ #### FloatingActionButton
761
+ Mobile-only floating action button that anchors to the bottom-right of the viewport on small screens. It supports three sizes, built-in themes or a custom theme color, named slots for icons, and an optional one-level options menu. The button is automatically hidden on wide screens (when `window.width > 1024`).
356
762
 
357
763
  ##### Attributes
358
764
 
359
765
  | Attribute | Description | Type | Default |
360
766
  |-----------|-------------|------|---------|
361
767
  | display_name | button text | `string` | `'button'` |
362
- | size | button size | `'xsmall' \| 'small' \| 'medium' \| 'large'` | `'small'` |
363
- | width_type | button width type | `'fill-whole' \| 'fit-content'` | `'fit-content'` |
364
- | build_in_theme | color theme | `'outlined-primary' \| 'filled-primary' \| 'text-primary' \| 'outlined-secondary' \| 'filled-secondary' \| 'text-secondary'` | `'outlined-primary'` |
365
- | customized_class | custom CSS class | `string` | |
366
- | hold | whether button is in hold state | `boolean` | `false` |
367
- | disabled | whether button is disabled | `boolean` | `false` |
368
- | dropdown_options | dropdown menu options | `array` | `[]` |
369
- | is_dropdown_option | whether this is a dropdown item | `boolean` | `false` |
768
+ | size | button size | `'small' \| 'medium' \| 'large'` | `'small'` |
769
+ | build_in_theme | built-in color theme | `'primary' \| 'secondary' \| 'tertiary' \| 'primary-container' \| 'secondary-container' \| 'tertiary-container'` | `'primary'` |
770
+ | customized_theme_color | custom theme color (overrides built_in_theme). Accepts any valid CSS color string (e.g., `#3d5c8f`, `rgb(61, 92, 143)`, `hsl(217, 40%, 40%)`, or `var(--Schemes-primary)`). | `string` | `''` |
771
+ | customized_class | custom CSS class for the outer container | `string` | `''` |
772
+ | options | optional menu options (one level). Each item is `{ value: any, label: string, prefix?: string, suffix?: string }`. `prefix`/`suffix` are icon URLs for the option. | `array` | `[]` |
773
+
774
+ > Notes
775
+ > - When `options` is non-empty, clicking the FAB toggles the options menu. A cross button is provided to close the menu.
776
+ > - The component is hidden on wide screens (`width > 1024`) by design to target mobile usage.
777
+ > - `customized_theme_color` overrides `build_in_theme`. If it is not provided (empty string), the `build_in_theme` color will be used instead.
778
+ > - `customized_theme_color` accepts any valid CSS color value: e.g. `hsl(217, 40%, 40%)`, `#ff0000`, `rgb(255, 0, 0)`, `rgba(255, 0, 0, 0.5)`, `hsla(217, 40%, 40%, 0.5)`, or `var(--Schemes-primary)`.
779
+ > - When using `customized_theme_color`:
780
+ > - The FAB background color uses the provided value directly.
781
+ > - Related colors (text color, inline SVG color via currentColor, menu cross button colors, menu child button colors, and interaction masks) are dynamically calculated based on the `customized_theme_color` to ensure contrast and visual harmony.
370
782
 
371
783
  ##### Events
372
784
 
373
785
  | Event | Description | Parameters |
374
786
  |-------|-------------|------------|
375
- | click | triggers when button is clicked | `(event: MouseEvent)` |
376
- | update-currently-hovered-dropdown-option | triggers when dropdown option is hovered | `(option: object)` |
377
- | add-nested-dropdown-history-stack | triggers when nested dropdown is navigated | `(option: object)` |
378
- | remove-nested-dropdown-history-stack | triggers when navigating back in nested dropdown | — |
787
+ | floating-action-button-click | emitted when the floating action button is clicked. If `options` is provided, this also toggles the menu visibility. | — |
788
+ | option-click | emitted when a menu option is clicked | `(value: any)` |
379
789
 
380
790
  ##### Slots
381
791
 
382
792
  | Name | Description |
383
793
  |------|-------------|
384
- | prefix | content before button text |
385
- | suffix | content after button text |
794
+ | prefix | content rendered before the button text. Commonly used for an icon. |
795
+ | default | default slot for button label content. Falls back to `display_name` when empty. |
796
+ | suffix | content rendered after the button text. Commonly used for an icon. |
386
797
 
387
798
  ```vue
388
799
  <script setup>
389
- import { iconsMap } from 'pns-component-library';
800
+ const quickCreateOptions = [
801
+ { value: 'photo', label: 'New Photo' },
802
+ { value: 'video', label: 'New Video' },
803
+ { value: 'doc', label: 'New Doc' },
804
+ ]
805
+
806
+ const handleFabClick = () => {
807
+ console.log('FAB clicked')
808
+ }
809
+
810
+ const handleOptionClick = (val) => {
811
+ console.log('Option clicked:', val)
812
+ }
390
813
  </script>
814
+
391
815
  <template>
392
- <ResponsiveButton
393
- display_name="Save Changes"
394
- size="medium"
395
- build_in_theme="filled-primary"
396
- @click="handleSave"
816
+ <!-- Will only be visible on small screens (hidden on width > 1024) -->
817
+ <FloatingActionButton
818
+ display_name="New"
819
+ size="small"
820
+ build_in_theme="tertiary"
821
+ :options="quickCreateOptions"
822
+ @floating-action-button-click="handleFabClick"
823
+ @option-click="handleOptionClick"
397
824
  >
398
825
  <template #prefix>
399
- <img :src="iconsMap['eye_icon']" alt="eye" />
826
+ <!-- Example icon (uses currentColor to adapt) -->
827
+ <!-- Pros of using inline SVG:
828
+ - Inherits currentColor automatically, so it adapts to theme/text color without extra CSS
829
+ - Reacts naturally to hover/active color-mix effects used by the component
830
+ - No additional network request for external assets
831
+ Using a regular <img src="..."> is also fine if you prefer image assets;
832
+ just note that <img> won't inherit currentColor by default (you'd need pre-colored assets or additional CSS). -->
833
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
834
+ <path d="M6 16L10 12.95L14 16L12.5 11.05L16.5 8.2H11.6L10 3L8.4 8.2H3.5L7.5 11.05L6 16ZM10 20C8.61667 20 7.31667 19.7375 6.1 19.2125C4.88333 18.6875 3.825 17.975 2.925 17.075C2.025 16.175 1.3125 15.1167 0.7875 13.9C0.2625 12.6833 0 11.3833 0 10C0 8.61667 0.2625 7.31667 0.7875 6.1C1.3125 4.88333 2.025 3.825 2.925 2.925C3.825 2.025 4.88333 1.3125 6.1 0.7875C7.31667 0.2625 8.61667 0 10 0C11.3833 0 12.6833 0.2625 13.9 0.7875C15.1167 1.3125 16.175 2.025 17.075 2.925C17.975 3.825 18.6875 4.88333 19.2125 6.1C19.7375 7.31667 20 8.61667 20 10C20 11.3833 19.7375 12.6833 19.2125 13.9C18.6875 15.1167 17.975 16.175 17.075 17.075C16.175 17.975 15.1167 18.6875 13.9 19.2125C12.6833 19.7375 11.3833 20 10 20Z" fill="currentColor"/>
835
+ </svg>
400
836
  </template>
401
- </ResponsiveButton>
837
+ </FloatingActionButton>
838
+
839
+ <!-- Custom theme color overrides built-in theme -->
840
+ <FloatingActionButton
841
+ display_name="Custom"
842
+ size="medium"
843
+ customized_theme_color="hsl(217, 40%, 40%)"
844
+ @floating-action-button-click="handleFabClick"
845
+ />
402
846
  </template>
403
847
  ```
404
848
 
@@ -599,7 +1043,7 @@ The library provides multiple built-in themes:
599
1043
 
600
1044
  ```vue
601
1045
  <Checkbox built_in_theme="primary-blue" />
602
- <ResponsiveButton built_in_theme="outlined-primary" />
1046
+ <DynamicColorResponsiveButton button_type="filled" built_in_theme="primary" />
603
1047
  ```
604
1048
 
605
1049
  ## 📱 Responsive Design