pns-component-library 1.5.14 → 1.6.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.
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` |
194
314
 
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.
315
+ Notes:
316
+
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,249 @@ 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
+ | built_in_theme | color theme | `'primary' \| 'secondary'` | `'primary'` |
598
+ | customized_theme_color | custom background color (any valid CSS color) | `string` | `''` |
599
+ | customized_class | custom CSS class | `string` | `''` |
600
+
601
+ ##### Size Specifications
602
+
603
+ | Size | Web (px) | Mobile ≤767px (px) | With Content |
604
+ |------|----------|-------------------|--------------|
605
+ | small | 6×6 | 6×6 | auto-sized |
606
+ | medium | 8×8 | 6×6 | auto-sized |
607
+ | large | 16×16 | 16×16 | auto-sized |
608
+
609
+ > **Note**: When badge has content, it automatically becomes large with padding (min-width: 20px, height: 20px).
610
+
611
+ ##### Position Types
612
+
613
+ | Position | Behavior |
614
+ |----------|----------|
615
+ | top-right | Positioned absolutely at top-right corner (0, 0) |
616
+ | center-right | Inline-flex aligned at right side with 4px gap |
617
+
618
+ > **Note**: Use padding on the wrapper div to control badge position. The badge positions relative to the wrapper's content.
619
+
620
+ ##### Built-in Themes
621
+
622
+ | Theme | Background | Text Color |
623
+ |-------|-----------|------------|
624
+ | primary | #AF251D (red) | #FFFFFF (white) |
625
+ | secondary | #CFE0FC (light blue) | #083D91 (dark blue) |
626
+
627
+ ##### Custom Theme Color
628
+
629
+ When using `customized_theme_color`, the text color is automatically calculated based on the background lightness:
630
+ - Light backgrounds (lightness > 50%) → dark text
631
+ - Dark backgrounds (lightness ≤ 50%) → light text
632
+
633
+ The algorithm adjusts lightness by ±60 while preserving hue and saturation for optimal contrast and color harmony.
634
+
635
+ ```vue
636
+ <template>
637
+ <!-- Basic badge with sizes -->
638
+ <Badge size="small">
639
+ <div style="padding: 4px;">
640
+ <div>Icon</div>
641
+ </div>
642
+ </Badge>
643
+
644
+ <!-- Badge with content -->
645
+ <Badge content="5">
646
+ <div style="padding: 8px;">
647
+ <div>Icon</div>
648
+ </div>
649
+ </Badge>
650
+
651
+ <!-- Badge with position types -->
652
+ <Badge position_type="top-right" content="3">
653
+ <div style="padding: 8px;">
654
+ <div>Icon</div>
655
+ </div>
656
+ </Badge>
657
+
658
+ <!-- Badge with custom color -->
659
+ <Badge customized_theme_color="#10B981" content="New">
660
+ <div style="padding: 8px;">
661
+ <div>Icon</div>
662
+ </div>
663
+ </Badge>
664
+
665
+ <!-- Dark background example -->
666
+ <Badge customized_theme_color="hsl(220, 70%, 25%)" content="Dark">
667
+ <div style="padding: 8px;">
668
+ <div>Icon</div>
669
+ </div>
670
+ </Badge>
671
+ </template>
672
+ ```
673
+
352
674
  ### Interactive Components
353
675
 
354
- #### ResponsiveButton
355
- Feature-rich button component with responsive behavior and multiple states.
676
+ #### DynamicColorResponsiveButton
677
+ Modern button with dynamic color logic and responsive behavior.
678
+
679
+ ##### Attributes
680
+
681
+ | Attribute | Description | Type | Default |
682
+ |-----------|-------------|------|---------|
683
+ | button_type | button style variant | `'filled' \| 'outlined' \| 'text' \| 'elevated'` | `'filled'` |
684
+ | button_border_radius | CSS border-radius value | `string` | `'100px'` |
685
+ | built_in_theme | built-in color theme | `'primary' \| 'secondary' \| 'tertiary' \| 'primary-container' \| 'secondary-container' \| 'tertiary-container'` | `'primary'` |
686
+ | customized_theme_color | overrides built_in_theme with any CSS color (e.g., `#3d5c8f`, `rgb(...)`, `hsl(...)`, `var(--Schemes-primary)`) | `string` | `''` |
687
+ | customized_class | custom CSS class for outer container | `string` | `''` |
688
+ | width_type | width mode | `'fill-whole' \| 'fit-content'` (auto-handles icon-only with `narrow`/`wide`) | `'fit-content'` |
689
+ | button_id | explicit id used in emitted event; falls back to `display_name` | `string` | `''` |
690
+ | display_name | button text (used when default slot empty) | `string` | `'button name'` |
691
+ | prefix | image url for prefix when not using slot | `string` | `''` |
692
+ | suffix | image url for suffix when not using slot | `string` | `''` |
693
+ | prefix_slot_raw_html_content | raw html for prefix (when not using named slot) | `string` | `''` |
694
+ | suffix_slot_raw_html_content | raw html for suffix (when not using named slot) | `string` | `''` |
695
+ | disabled | disable interaction | `boolean` | `false` |
696
+ | 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` |
697
+ | size | size (Web/Tablet enum) | `'small' \| 'medium' \| 'large' \| 'xlarge'` | `'small'` |
698
+
699
+ ##### Events
700
+
701
+ | Event | Description | Parameters |
702
+ |-------|-------------|------------|
703
+ | click-button | emitted on button click | `(button_id: string, display_name: string)` |
704
+
705
+ ##### Exposes
706
+
707
+ | Method | Description | Type |
708
+ |--------|-------------|------|
709
+ | handleFocusButton | programmatically apply focus style | `() => void` |
710
+ | handleBlurButton | remove focus style | `() => void` |
711
+
712
+ ##### Slots
713
+
714
+ | Name | Description |
715
+ |------|-------------|
716
+ | prefix | content before the label (e.g., icon) |
717
+ | default | label content; falls back to `display_name` |
718
+ | suffix | content after the label (e.g., icon) |
719
+
720
+ ```vue
721
+ <template>
722
+ <DynamicColorResponsiveButton
723
+ button_id="save-changes"
724
+ display_name="Save Changes"
725
+ button_type="filled"
726
+ built_in_theme="primary"
727
+ :has_interaction_effect="true"
728
+ width_type="fit-content"
729
+ @click-button="onSave"
730
+ >
731
+ <template #prefix>
732
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M5 12h14v2H5z"/></svg>
733
+ </template>
734
+ </DynamicColorResponsiveButton>
735
+ </template>
736
+
737
+ <script setup>
738
+ function onSave(id, name){
739
+ console.log('clicked:', id, name)
740
+ }
741
+ </script>
742
+ ```
743
+
744
+ #### FloatingActionButton
745
+ 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
746
 
357
747
  ##### Attributes
358
748
 
359
749
  | Attribute | Description | Type | Default |
360
750
  |-----------|-------------|------|---------|
361
751
  | 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` |
752
+ | size | button size | `'small' \| 'medium' \| 'large'` | `'small'` |
753
+ | build_in_theme | built-in color theme | `'primary' \| 'secondary' \| 'tertiary' \| 'primary-container' \| 'secondary-container' \| 'tertiary-container'` | `'primary'` |
754
+ | 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` | `''` |
755
+ | customized_class | custom CSS class for the outer container | `string` | `''` |
756
+ | 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` | `[]` |
757
+
758
+ > Notes
759
+ > - When `options` is non-empty, clicking the FAB toggles the options menu. A cross button is provided to close the menu.
760
+ > - The component is hidden on wide screens (`width > 1024`) by design to target mobile usage.
761
+ > - `customized_theme_color` overrides `build_in_theme`. If it is not provided (empty string), the `build_in_theme` color will be used instead.
762
+ > - `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)`.
763
+ > - When using `customized_theme_color`:
764
+ > - The FAB background color uses the provided value directly.
765
+ > - 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
766
 
371
767
  ##### Events
372
768
 
373
769
  | Event | Description | Parameters |
374
770
  |-------|-------------|------------|
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 | — |
771
+ | floating-action-button-click | emitted when the floating action button is clicked. If `options` is provided, this also toggles the menu visibility. | — |
772
+ | option-click | emitted when a menu option is clicked | `(value: any)` |
379
773
 
380
774
  ##### Slots
381
775
 
382
776
  | Name | Description |
383
777
  |------|-------------|
384
- | prefix | content before button text |
385
- | suffix | content after button text |
778
+ | prefix | content rendered before the button text. Commonly used for an icon. |
779
+ | default | default slot for button label content. Falls back to `display_name` when empty. |
780
+ | suffix | content rendered after the button text. Commonly used for an icon. |
386
781
 
387
782
  ```vue
388
783
  <script setup>
389
- import { iconsMap } from 'pns-component-library';
784
+ const quickCreateOptions = [
785
+ { value: 'photo', label: 'New Photo' },
786
+ { value: 'video', label: 'New Video' },
787
+ { value: 'doc', label: 'New Doc' },
788
+ ]
789
+
790
+ const handleFabClick = () => {
791
+ console.log('FAB clicked')
792
+ }
793
+
794
+ const handleOptionClick = (val) => {
795
+ console.log('Option clicked:', val)
796
+ }
390
797
  </script>
798
+
391
799
  <template>
392
- <ResponsiveButton
393
- display_name="Save Changes"
394
- size="medium"
395
- build_in_theme="filled-primary"
396
- @click="handleSave"
800
+ <!-- Will only be visible on small screens (hidden on width > 1024) -->
801
+ <FloatingActionButton
802
+ display_name="New"
803
+ size="small"
804
+ build_in_theme="tertiary"
805
+ :options="quickCreateOptions"
806
+ @floating-action-button-click="handleFabClick"
807
+ @option-click="handleOptionClick"
397
808
  >
398
809
  <template #prefix>
399
- <img :src="iconsMap['eye_icon']" alt="eye" />
810
+ <!-- Example icon (uses currentColor to adapt) -->
811
+ <!-- Pros of using inline SVG:
812
+ - Inherits currentColor automatically, so it adapts to theme/text color without extra CSS
813
+ - Reacts naturally to hover/active color-mix effects used by the component
814
+ - No additional network request for external assets
815
+ Using a regular <img src="..."> is also fine if you prefer image assets;
816
+ just note that <img> won't inherit currentColor by default (you'd need pre-colored assets or additional CSS). -->
817
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
818
+ <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"/>
819
+ </svg>
400
820
  </template>
401
- </ResponsiveButton>
821
+ </FloatingActionButton>
822
+
823
+ <!-- Custom theme color overrides built-in theme -->
824
+ <FloatingActionButton
825
+ display_name="Custom"
826
+ size="medium"
827
+ customized_theme_color="hsl(217, 40%, 40%)"
828
+ @floating-action-button-click="handleFabClick"
829
+ />
402
830
  </template>
403
831
  ```
404
832
 
@@ -599,7 +1027,7 @@ The library provides multiple built-in themes:
599
1027
 
600
1028
  ```vue
601
1029
  <Checkbox built_in_theme="primary-blue" />
602
- <ResponsiveButton built_in_theme="outlined-primary" />
1030
+ <DynamicColorResponsiveButton button_type="filled" built_in_theme="primary" />
603
1031
  ```
604
1032
 
605
1033
  ## 📱 Responsive Design