@redseed/redseed-ui-vue3 8.67.0 → 8.69.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redseed/redseed-ui-vue3",
3
- "version": "8.67.0",
3
+ "version": "8.69.0",
4
4
  "description": "RedSeed UI Vue 3 components",
5
5
  "main": "index.js",
6
6
  "repository": "https://github.com/redseedtraining/redseed-ui",
@@ -66,6 +66,39 @@ const props = defineProps({
66
66
  type: Boolean,
67
67
  default: false,
68
68
  },
69
+ // CATEGORY colour, which is a different job from the semantic variants above.
70
+ // `brand`/`success`/`error` and friends say what STATE a thing is in; they
71
+ // cannot say which taxonomy a thing BELONGS to (a capability domain, a team, a
72
+ // location, a pathway), and there are always more categories than the semantic
73
+ // set has slots.
74
+ //
75
+ // Takes the name of a RUNTIME CSS custom property, applied as
76
+ // `var(--<color>, <neutral>)` — the same convention and the same neutral
77
+ // degrade as Pill's `color` prop, so the two components stay consistent and
78
+ // RSUI stays theme-agnostic. Pass a real runtime theme variable, e.g. the
79
+ // consuming app's palette (`color7` -> `var(--color7)`). NOTE: RSUI's own
80
+ // Tailwind `@theme` tokens (e.g. `--color-brand-500`) are inlined into
81
+ // utilities, not exposed as runtime vars, so they will NOT resolve here. An
82
+ // UNRESOLVABLE (undefined) token degrades to a neutral grey rather than an
83
+ // invisible bar; a token that *resolves* to `transparent`/empty is not caught.
84
+ //
85
+ // On its own this prop paints nothing — it needs `accent` to say which edge
86
+ // carries it.
87
+ color: {
88
+ type: String,
89
+ default: '',
90
+ },
91
+ // Which edge carries the category accent bar. Default 'none', so nothing
92
+ // changes for any existing consumer. Thickness is NOT a consumer decision —
93
+ // it comes from a token (see --rsui-card-accent-w in card.css). That is the
94
+ // whole point of the prop: the same concept was hand-rolled twice on one page
95
+ // at two different weights (4px and 3px) precisely because nothing told the
96
+ // author what the weight should be.
97
+ accent: {
98
+ type: String,
99
+ default: 'none',
100
+ validator: (value) => ['left', 'top', 'none'].includes(value),
101
+ },
69
102
  ariaLabel: {
70
103
  type: String,
71
104
  default: undefined,
@@ -110,6 +143,16 @@ const ariaCurrent = computed(() => {
110
143
  // conveyed by colour alone (WCAG 1.4.1).
111
144
  const isActive = computed(() => props.pressed === true || Boolean(props.current))
112
145
 
146
+ // The accent token is only set when a color is supplied; otherwise the property
147
+ // is left unset so the CSS neutral fallback applies. When it IS supplied it
148
+ // carries an inline fallback to the neutral grey, so an unresolvable token
149
+ // degrades to grey rather than to an invisible edge. Mirrors Pill exactly.
150
+ const accentStyle = computed(() =>
151
+ props.color
152
+ ? { '--rsui-card-accent': `var(--${props.color}, var(--Colors-Grey-500))` }
153
+ : {},
154
+ )
155
+
113
156
  const slots = useSlots()
114
157
 
115
158
  const cardElement = ref(null)
@@ -130,6 +173,8 @@ const cardClass = computed(() => [
130
173
  'rsui-card--warning': props.warning,
131
174
  'rsui-card--error': props.error,
132
175
  'rsui-card--draft': props.draft,
176
+ 'rsui-card--accent-left': props.accent === 'left',
177
+ 'rsui-card--accent-top': props.accent === 'top',
133
178
  'rsui-card--2xs': responsiveWidth.value['2xs'],
134
179
  'rsui-card--xs': responsiveWidth.value['xs'],
135
180
  'rsui-card--sm': responsiveWidth.value['sm'],
@@ -170,7 +215,7 @@ function onClick() {
170
215
  }
171
216
  </script>
172
217
  <template>
173
- <div ref="cardElement" :class="cardClass" data-testid="card">
218
+ <div ref="cardElement" :class="cardClass" :style="accentStyle" data-testid="card">
174
219
  <svg
175
220
  v-if="draft"
176
221
  class="rsui-card__draft-border"
@@ -1,5 +1,5 @@
1
1
  <script setup>
2
- import { ref, watch } from 'vue'
2
+ import { computed, ref, useSlots, watch } from 'vue'
3
3
  import Card from './Card.vue'
4
4
  import CardHeader from './CardHeader.vue'
5
5
  import FormFieldCheckbox from '../FormField/FormFieldCheckbox.vue'
@@ -31,6 +31,14 @@ const props = defineProps({
31
31
 
32
32
  const emit = defineEmits(['check', 'uncheck', 'change'])
33
33
 
34
+ const slots = useSlots()
35
+
36
+ // Whether anything renders BELOW the header. When nothing does, the card is header-only
37
+ // and the header has to pay its own bottom padding — it is styled `pb-0` and normally
38
+ // borrows its bottom edge from the body's padding, so without this a title-only card sits
39
+ // flush against its own border.
40
+ const hasBody = computed(() => Boolean(slots.default || slots.meta))
41
+
34
42
  const isChecked = ref(props.checked)
35
43
 
36
44
  watch(() => props.checked, (val) => {
@@ -66,6 +74,7 @@ function onFieldInput() {
66
74
  :showMoreActions="showMoreActions"
67
75
  :showDivider="false"
68
76
  :singleLine="false"
77
+ :headerOnlyCard="!hasBody"
69
78
  >
70
79
  <div class="rsui-checkbox-card__title-row">
71
80
  <div class="rsui-checkbox-card__field">
@@ -102,7 +111,13 @@ function onFieldInput() {
102
111
  </CardHeader>
103
112
  </template>
104
113
 
105
- <slot></slot>
114
+ <!-- v-if, not a bare <slot>. Card already guards its body with
115
+ `v-if="$slots.default || $slots.meta"`, but a bare <slot> here counts as a default
116
+ slot whether or not the consumer filled it, so that guard could never fire and a
117
+ title-only task card still rendered an empty padded body. -->
118
+ <template #default v-if="$slots.default">
119
+ <slot></slot>
120
+ </template>
106
121
 
107
122
  <template #meta v-if="$slots.meta">
108
123
  <slot name="meta"></slot>