@redseed/redseed-ui-vue3 8.53.0 → 8.55.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/index.js CHANGED
@@ -36,6 +36,7 @@ export * from './src/components/MessageBox'
36
36
  export * from './src/components/MetaInfo'
37
37
  export * from './src/components/Modal'
38
38
  export * from './src/components/Pagination'
39
+ export * from './src/components/Pill'
39
40
  export * from './src/components/Progress'
40
41
  export * from './src/components/Section'
41
42
  export * from './src/components/Skeleton'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redseed/redseed-ui-vue3",
3
- "version": "8.53.0",
3
+ "version": "8.55.0",
4
4
  "description": "RedSeed UI Vue 3 components",
5
5
  "main": "index.js",
6
6
  "repository": "https://github.com/redseedtraining/redseed-ui",
@@ -0,0 +1,137 @@
1
+ <script setup>
2
+ import { ref, computed, onMounted, watchEffect } from 'vue'
3
+
4
+ const props = defineProps({
5
+ selected: {
6
+ type: Boolean,
7
+ default: false,
8
+ },
9
+ disabled: {
10
+ type: Boolean,
11
+ default: false,
12
+ },
13
+ // The name of a RUNTIME CSS custom property to use as the accent — applied as
14
+ // `var(--<color>, <neutral>)`, driving the icon-badge fill and the selected
15
+ // ring/tint. Pass a real runtime theme variable, e.g. the consuming app's
16
+ // palette (`color7` → `var(--color7)`). NOTE: RSUI's own Tailwind `@theme`
17
+ // color tokens (e.g. `--color-brand-500`) are inlined into utilities, not
18
+ // exposed as runtime vars, so they will NOT resolve here. An UNRESOLVABLE
19
+ // (undefined) token degrades to a neutral grey rather than an invisible pill;
20
+ // a token that *resolves* to `transparent`/empty is not caught — passing a
21
+ // real colour is the consumer's responsibility. Optional — falls back to a
22
+ // neutral accent when omitted, so RSUI stays theme-agnostic.
23
+ color: {
24
+ type: String,
25
+ default: '',
26
+ },
27
+ // Size ramp (dimensions from the RDS Refresh Figma). Booleans, only one
28
+ // applies at a time — matches the RSUI size-prop convention. Defaults to md.
29
+ xs: {
30
+ type: Boolean,
31
+ default: false,
32
+ },
33
+ sm: {
34
+ type: Boolean,
35
+ default: false,
36
+ },
37
+ md: {
38
+ type: Boolean,
39
+ default: false,
40
+ },
41
+ lg: {
42
+ type: Boolean,
43
+ default: false,
44
+ },
45
+ xl: {
46
+ type: Boolean,
47
+ default: false,
48
+ },
49
+ })
50
+
51
+ const emit = defineEmits(['click'])
52
+
53
+ // Resolve the single active size. Largest flag wins so a consumer passing more
54
+ // than one still gets a defined result, and an unsized pill defaults to md.
55
+ const activeSize = computed(() => {
56
+ if (props.xl) return 'xl'
57
+ if (props.lg) return 'lg'
58
+ if (props.md) return 'md'
59
+ if (props.sm) return 'sm'
60
+ if (props.xs) return 'xs'
61
+ return 'md'
62
+ })
63
+
64
+ // The single accent token colours both the icon badge and the selected
65
+ // treatment. When no color is supplied we leave the property unset so the CSS
66
+ // neutral fallback applies. When a color IS supplied it carries an inline
67
+ // fallback to the neutral grey, so an UNRESOLVABLE (undefined) token degrades to
68
+ // grey rather than an invisible badge/selection. Note: var()'s fallback does not
69
+ // fire for a token that *resolves* to transparent/empty — passing a real colour
70
+ // is the consumer's responsibility.
71
+ const accentStyle = computed(() =>
72
+ props.color
73
+ ? { '--rsui-pill-accent': `var(--${props.color}, var(--Colors-Grey-500))` }
74
+ : {},
75
+ )
76
+
77
+ const pillClass = computed(() => [
78
+ 'rsui-pill',
79
+ `rsui-pill--${activeSize.value}`,
80
+ {
81
+ 'rsui-pill--selected': props.selected && !props.disabled,
82
+ 'rsui-pill--disabled': props.disabled,
83
+ },
84
+ ])
85
+
86
+ function handleClick(event) {
87
+ // Guarded — a disabled pill emits nothing even if a click reaches it.
88
+ if (props.disabled) return
89
+
90
+ emit('click', event)
91
+ }
92
+
93
+ // button element ref, used for the icon-only accessibility check
94
+ const pillElementRef = ref(null)
95
+
96
+ // an icon-only pill has no visible label text
97
+ const iconOnly = computed(
98
+ () => pillElementRef.value && !pillElementRef.value.textContent.trim(),
99
+ )
100
+
101
+ // warn in development when an icon-only pill lacks an accessible name
102
+ onMounted(() => {
103
+ watchEffect(() => {
104
+ if (
105
+ process.env.NODE_ENV !== 'production' &&
106
+ iconOnly.value &&
107
+ !pillElementRef.value.getAttribute('aria-label') &&
108
+ !pillElementRef.value.getAttribute('aria-labelledby')
109
+ ) {
110
+ console.warn(
111
+ '[RSUI] Icon-only Pill detected without aria-label. Add aria-label for accessibility.',
112
+ )
113
+ }
114
+ })
115
+ })
116
+
117
+ defineExpose({
118
+ pillElementRef,
119
+ })
120
+ </script>
121
+ <template>
122
+ <button
123
+ ref="pillElementRef"
124
+ :class="pillClass"
125
+ :style="accentStyle"
126
+ :disabled="props.disabled"
127
+ :aria-pressed="props.selected && !props.disabled"
128
+ type="button"
129
+ @click="handleClick"
130
+ >
131
+ <span v-if="$slots.icon" class="rsui-pill__icon" aria-hidden="true">
132
+ <slot name="icon"></slot>
133
+ </span>
134
+
135
+ <slot></slot>
136
+ </button>
137
+ </template>
@@ -0,0 +1,5 @@
1
+ import Pill from './Pill.vue'
2
+
3
+ export {
4
+ Pill,
5
+ }