@redseed/redseed-ui-vue3 8.61.0 → 8.63.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.
@@ -7,7 +7,7 @@ A comprehensive Vue 3 component library designed for building modern, accessible
7
7
  ### 1. [Layout Components](./01-layout-components.md)
8
8
  Structural components for organizing page content, including layouts, headers, sections, and page structure.
9
9
 
10
- **Key Components:** SingleColumnLayout, TwoColumnLayout, PageHeader, SectionHeader, HeroSection
10
+ **Key Components:** SingleColumnLayout, TwoColumnLayout, PageHeader, SectionHeader
11
11
 
12
12
  ### 2. [Button Components](./02-button-components.md)
13
13
  Interactive elements for user actions, with various styles and grouping options.
@@ -116,34 +116,6 @@ Layout components provide the structural foundation for organizing content on pa
116
116
  </template>
117
117
  ```
118
118
 
119
- ### HeroSection
120
- **When to use:** For landing pages, marketing pages, or any page that needs a prominent introduction section with header content and optional button cards.
121
- **Implementation:**
122
- ```vue
123
- <template>
124
- <HeroSection>
125
- <template #header>
126
- <SectionHeader>
127
- Section header
128
- <template #subtitle>
129
- Section header subtitle
130
- </template>
131
- </SectionHeader>
132
- </template>
133
-
134
- <ButtonCard>
135
- <template #icon>
136
- <FaceSmileIcon />
137
- </template>
138
- Button card title
139
- <template #supporting-text>
140
- Supporting text
141
- </template>
142
- </ButtonCard>
143
- </HeroSection>
144
- </template>
145
- ```
146
-
147
119
  ## Best Practices
148
120
 
149
121
  1. **Consistent Structure:** Use layout components consistently across your application to maintain visual hierarchy and user experience.
@@ -147,19 +147,24 @@ Media components provide consistent ways to display images, logos, and other vis
147
147
 
148
148
  ## Common Use Cases
149
149
 
150
- ### Hero Section with Image
150
+ ### Banner Section with Image
151
151
  ```vue
152
152
  <template>
153
- <HeroSection
154
- title="Welcome to Our Platform"
155
- subtitle="The best solution for your needs"
156
- >
157
- <Image16By9
158
- :src="heroImageUrl"
159
- :alt="heroImageAlt"
160
- class="hero-image"
153
+ <Section>
154
+ <template #header>
155
+ <SectionHeader>
156
+ Welcome to Our Platform
157
+ <template #subtitle>
158
+ The best solution for your needs
159
+ </template>
160
+ </SectionHeader>
161
+ </template>
162
+
163
+ <Image16By9
164
+ :src="bannerImageUrl"
165
+ :alt="bannerImageAlt"
161
166
  />
162
- </HeroSection>
167
+ </Section>
163
168
  </template>
164
169
  ```
165
170
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redseed/redseed-ui-vue3",
3
- "version": "8.61.0",
3
+ "version": "8.63.0",
4
4
  "description": "RedSeed UI Vue 3 components",
5
5
  "main": "index.js",
6
6
  "repository": "https://github.com/redseedtraining/redseed-ui",
@@ -28,12 +28,27 @@ const props = defineProps({
28
28
  type: Boolean,
29
29
  default: null,
30
30
  },
31
+ // Puts the tile on a tonal surface with ink to match, using the same named set as
32
+ // PageHeader's `tone` so a tile inside a toned header can be given the matching name.
33
+ // Named tones rather than a colour because white-labelling means the component cannot
34
+ // assume light-on-dark: surface, ink, muted ink and rule move together.
35
+ //
36
+ // Not combinable with Card's semantic variants (brand, success, …) — pick one.
37
+ tone: {
38
+ type: String,
39
+ default: null,
40
+ validator: value => ['green', 'teal', 'light'].includes(value),
41
+ },
31
42
  })
32
43
  </script>
33
44
 
34
45
  <template>
35
46
  <Card class="rsui-metric-card"
36
- :class="{ 'rsui-metric-card--active': active === true }"
47
+ :class="{
48
+ 'rsui-metric-card--active': active === true,
49
+ 'rsui-metric-card--toned': !!props.tone,
50
+ [`rsui-metric-card--tone-${props.tone}`]: !!props.tone,
51
+ }"
37
52
  :clickable="props.clickable"
38
53
  :hoverable="props.hoverable"
39
54
  :pressed="active"
@@ -22,64 +22,66 @@ const props = defineProps({
22
22
  const formRef = ref(props.form)
23
23
  </script>
24
24
  <template>
25
- <slot name="top-fields" :formRef="formRef"></slot>
25
+ <div class="rsui-form-fields-login">
26
+ <slot name="top-fields" :formRef="formRef"></slot>
26
27
 
27
- <slot name="email-username" :formRef="formRef">
28
- <FormFieldEmail
29
- v-model="formRef.email"
30
- id="email"
31
- name="email"
28
+ <slot name="email-username" :formRef="formRef">
29
+ <FormFieldEmail
30
+ v-model="formRef.email"
31
+ id="email"
32
+ name="email"
33
+ >
34
+ <template #label>
35
+ <slot name="email-label">
36
+ Email
37
+ </slot>
38
+ </template>
39
+
40
+ <template #error v-if="formRef.errors.email">
41
+ {{ formRef.errors.email }}
42
+ </template>
43
+ </FormFieldEmail>
44
+ </slot>
45
+
46
+ <FormFieldPassword
47
+ v-model="formRef.password"
48
+ id="password"
49
+ name="password"
32
50
  >
33
51
  <template #label>
34
- <slot name="email-label">
35
- Email
52
+ <slot name="password-label">
53
+ Password
36
54
  </slot>
37
55
  </template>
38
56
 
39
- <template #error v-if="formRef.errors.email">
40
- {{ formRef.errors.email }}
57
+ <template #error v-if="formRef.errors.password">
58
+ {{ formRef.errors.password }}
41
59
  </template>
42
- </FormFieldEmail>
43
- </slot>
44
-
45
- <FormFieldPassword
46
- v-model="formRef.password"
47
- id="password"
48
- name="password"
49
- >
50
- <template #label>
51
- <slot name="password-label">
52
- Password
53
- </slot>
54
- </template>
60
+ </FormFieldPassword>
55
61
 
56
- <template #error v-if="formRef.errors.password">
57
- {{ formRef.errors.password }}
58
- </template>
59
- </FormFieldPassword>
62
+ <slot name="bottom-fields" :formRef="formRef"></slot>
60
63
 
61
- <slot name="bottom-fields" :formRef="formRef"></slot>
62
-
63
- <FormFieldSlot v-if="$slots.forgot"
64
- class="rsui-form-field-forgot"
65
- >
66
- <slot name="forgot"></slot>
67
- </FormFieldSlot>
64
+ <FormFieldSlot v-if="$slots.forgot"
65
+ class="rsui-form-field-forgot"
66
+ >
67
+ <slot name="forgot"></slot>
68
+ </FormFieldSlot>
68
69
 
69
- <slot name="submit"
70
- :formRef="formRef"
71
- >
72
- <ButtonPrimaryFull lg
73
- :disabled="form.processing"
74
- type="submit"
70
+ <slot name="submit"
71
+ :formRef="formRef"
75
72
  >
76
- <slot name="submit-label">
77
- Log in
78
- </slot>
79
- </ButtonPrimaryFull>
80
- </slot>
73
+ <ButtonPrimaryFull lg
74
+ :disabled="form.processing"
75
+ type="submit"
76
+ >
77
+ <slot name="submit-label">
78
+ Log in
79
+ </slot>
80
+ </ButtonPrimaryFull>
81
+ </slot>
81
82
 
82
- <div v-if="$slots.signup" class="rsui-form-login__signup">
83
- <slot name="signup"></slot>
83
+ <div v-if="$slots.signup" class="rsui-form-login__signup">
84
+ <slot name="signup"></slot>
85
+ </div>
84
86
  </div>
85
87
  </template>
@@ -25,93 +25,95 @@ const props = defineProps({
25
25
  const formRef = ref(props.form)
26
26
  </script>
27
27
  <template>
28
- <slot name="top-fields" :formRef="formRef"></slot>
28
+ <div class="rsui-form-fields-register">
29
+ <slot name="top-fields" :formRef="formRef"></slot>
29
30
 
30
- <slot name="name-fields"
31
- :formRef="formRef"
32
- >
33
- <FormFieldText
34
- v-model="formRef.name"
35
- id="name"
36
- name="name"
31
+ <slot name="name-fields"
32
+ :formRef="formRef"
33
+ >
34
+ <FormFieldText
35
+ v-model="formRef.name"
36
+ id="name"
37
+ name="name"
38
+ >
39
+ <template #label>
40
+ <slot name="name-label">
41
+ Name
42
+ </slot>
43
+ </template>
44
+
45
+ <template #error v-if="formRef.errors.name">
46
+ {{ formRef.errors.name }}
47
+ </template>
48
+ </FormFieldText>
49
+ </slot>
50
+
51
+ <FormFieldEmail
52
+ v-model="formRef.email"
53
+ id="email"
54
+ name="email"
37
55
  >
38
56
  <template #label>
39
- <slot name="name-label">
40
- Name
57
+ <slot name="email-label">
58
+ Email
41
59
  </slot>
42
60
  </template>
43
61
 
44
- <template #error v-if="formRef.errors.name">
45
- {{ formRef.errors.name }}
62
+ <template #error v-if="formRef.errors.email">
63
+ {{ formRef.errors.email }}
46
64
  </template>
47
- </FormFieldText>
48
- </slot>
49
-
50
- <FormFieldEmail
51
- v-model="formRef.email"
52
- id="email"
53
- name="email"
54
- >
55
- <template #label>
56
- <slot name="email-label">
57
- Email
58
- </slot>
59
- </template>
65
+ </FormFieldEmail>
60
66
 
61
- <template #error v-if="formRef.errors.email">
62
- {{ formRef.errors.email }}
63
- </template>
64
- </FormFieldEmail>
65
-
66
- <FormFieldPasswordToggle
67
- v-model="formRef.password"
68
- id="password"
69
- name="password"
70
- >
71
- <template #label>
72
- <slot name="password-label">
73
- Password
74
- </slot>
75
- </template>
67
+ <FormFieldPasswordToggle
68
+ v-model="formRef.password"
69
+ id="password"
70
+ name="password"
71
+ >
72
+ <template #label>
73
+ <slot name="password-label">
74
+ Password
75
+ </slot>
76
+ </template>
76
77
 
77
- <template #error v-if="formRef.errors.password">
78
- {{ formRef.errors.password }}
79
- </template>
80
- </FormFieldPasswordToggle>
78
+ <template #error v-if="formRef.errors.password">
79
+ {{ formRef.errors.password }}
80
+ </template>
81
+ </FormFieldPasswordToggle>
81
82
 
82
- <slot name="bottom-fields" :formRef="formRef"></slot>
83
+ <slot name="bottom-fields" :formRef="formRef"></slot>
83
84
 
84
- <FormFieldCheckbox
85
- v-if="$slots.terms"
86
- v-model="formRef.terms"
87
- id="terms"
88
- name="terms"
89
- >
90
- <template #label>
91
- <slot name="terms"></slot>
92
- </template>
85
+ <FormFieldCheckbox
86
+ v-if="$slots.terms"
87
+ v-model="formRef.terms"
88
+ id="terms"
89
+ name="terms"
90
+ >
91
+ <template #label>
92
+ <slot name="terms"></slot>
93
+ </template>
93
94
 
94
- <template #error v-if="formRef.errors.terms">
95
- {{ formRef.errors.terms }}
96
- </template>
97
- </FormFieldCheckbox>
95
+ <template #error v-if="formRef.errors.terms">
96
+ {{ formRef.errors.terms }}
97
+ </template>
98
+ </FormFieldCheckbox>
98
99
 
99
- <slot name="recaptcha" :formRef="formRef"></slot>
100
+ <slot name="recaptcha" :formRef="formRef"></slot>
100
101
 
101
- <slot name="submit"
102
- :formRef="formRef"
103
- >
104
- <ButtonPrimaryFull lg
105
- :disabled="form.processing"
106
- type="submit"
102
+ <slot name="submit"
103
+ :formRef="formRef"
107
104
  >
108
- <slot name="submit-label">
109
- Register
110
- </slot>
111
- </ButtonPrimaryFull>
112
- </slot>
105
+ <ButtonPrimaryFull lg
106
+ :disabled="form.processing"
107
+ type="submit"
108
+ >
109
+ <slot name="submit-label">
110
+ Register
111
+ </slot>
112
+ </ButtonPrimaryFull>
113
+ </slot>
113
114
 
114
- <div v-if="$slots.signin" class="rsui-form-register__signin">
115
- <slot name="signin"></slot>
115
+ <div v-if="$slots.signin" class="rsui-form-register__signin">
116
+ <slot name="signin"></slot>
117
+ </div>
116
118
  </div>
117
119
  </template>
@@ -90,6 +90,24 @@ const props = defineProps({
90
90
  type: Boolean,
91
91
  default: false,
92
92
  },
93
+ /**
94
+ * Expand a ring outwards from the icon a few times on arrival, to draw the
95
+ * eye to something that has just appeared.
96
+ *
97
+ * Runs a fixed number of iterations rather than looping. An animation that
98
+ * repeats indefinitely for more than five seconds needs a pause control
99
+ * (WCAG 2.2.2), and an icon has nowhere sensible to put one — a burst on
100
+ * arrival gets the attention without taking on that obligation. Suppressed
101
+ * entirely under `prefers-reduced-motion`.
102
+ *
103
+ * The ring is drawn around the icon's own box, so it suits a round glyph —
104
+ * the circle heroicons, or anything with `circle`. On a square icon it reads
105
+ * as a mistake rather than a pulse.
106
+ */
107
+ pulse: {
108
+ type: Boolean,
109
+ default: false,
110
+ },
93
111
  })
94
112
 
95
113
  /**
@@ -162,6 +180,7 @@ const iconClass = computed(() => [
162
180
  */
163
181
  'rsui-icon--background': props.background,
164
182
  'rsui-icon--invert': props.invert,
183
+ 'rsui-icon--pulse': props.pulse,
165
184
  }
166
185
  ])
167
186
  </script>
@@ -8,6 +8,18 @@ const props = defineProps({
8
8
  type: Boolean,
9
9
  default: false,
10
10
  },
11
+ // Puts the header on a tonal band and flips its ink to match. Named tones rather than a
12
+ // background colour, because white-labelling means the component cannot assume
13
+ // light-on-dark: hand it a colour alone and a client who picks a light one gets white
14
+ // text on white. Background, ink, muted ink and rule move together as a set.
15
+ //
16
+ // A toned header supplies its own padding, so place it outside the page's content
17
+ // gutter and let the band bleed. Omit the prop and nothing changes.
18
+ tone: {
19
+ type: String,
20
+ default: null,
21
+ validator: value => ['green', 'teal', 'light'].includes(value),
22
+ },
11
23
  })
12
24
 
13
25
  const showMetaModal = ref(false)
@@ -16,6 +28,8 @@ const pageHeaderClass = computed(() => [
16
28
  'rsui-page-header',
17
29
  {
18
30
  'rsui-page-header--profile': props.profile,
31
+ 'rsui-page-header--toned': !!props.tone,
32
+ [`rsui-page-header--tone-${props.tone}`]: !!props.tone,
19
33
  },
20
34
  ])
21
35
  </script>
@@ -17,6 +17,29 @@ const props = defineProps({
17
17
  type: Boolean,
18
18
  default: false,
19
19
  },
20
+ // Draws a coloured rail down the leading edge, so a list of mixed kinds can be scanned
21
+ // by colour. Semantic names rather than brand ones, matching MessageBox's vocabulary —
22
+ // the rail is decoration, so it only has to clear WCAG 1.4.11 at 3:1.
23
+ accent: {
24
+ type: String,
25
+ default: null,
26
+ validator: value => ['warning', 'info', 'success', 'error', 'neutral'].includes(value),
27
+ },
28
+ // Keeps meta and description rendered at every width. They are dropped below 480px by
29
+ // default, which is right for a dense list but wrong for a row whose second line carries
30
+ // the detail that makes it actionable.
31
+ alwaysShowBody: {
32
+ type: Boolean,
33
+ default: false,
34
+ },
35
+ // Size of the default action button. Defaults to the xs it has always used, which
36
+ // measures 34px. `lg` is the first size that clears the 44px touch target a phone
37
+ // wants — measured 46px; `md` is 42px and does not.
38
+ actionSize: {
39
+ type: String,
40
+ default: 'xs',
41
+ validator: value => ['xs', 'sm', 'md', 'lg', 'xl'].includes(value),
42
+ },
20
43
  })
21
44
 
22
45
  const listItemElement = ref(null)
@@ -28,7 +51,7 @@ const isWide = computed(() => !!responsiveWidth.value.specific)
28
51
  const slots = useSlots()
29
52
 
30
53
  const shouldShowBody = computed(() => {
31
- return isWide.value && (slots.meta || slots.description)
54
+ return (isWide.value || props.alwaysShowBody) && (slots.meta || slots.description)
32
55
  })
33
56
 
34
57
  const listItemClass = computed(() => [
@@ -37,6 +60,8 @@ const listItemClass = computed(() => [
37
60
  'rsui-list-item--clickable': props.clickable,
38
61
  'rsui-list-item--avatar': props.avatar,
39
62
  'rsui-list-item--wide': isWide.value,
63
+ 'rsui-list-item--accented': !!props.accent,
64
+ [`rsui-list-item--accent-${props.accent}`]: !!props.accent,
40
65
  }
41
66
  ])
42
67
 
@@ -75,6 +100,15 @@ function clickItem(event) {
75
100
  The peer class is used to show hover effect on the whole item with the bg div.
76
101
  -->
77
102
  <div class="rsui-list-item__header peer">
103
+ <!--
104
+ The icon sits inline at the start of the row, unlike the avatar below, which
105
+ hangs outside the row box on a negative offset. Two different jobs: an avatar
106
+ identifies a person, an icon classifies the row, so they are separate slots
107
+ and can be used together.
108
+ -->
109
+ <div v-if="$slots.icon" class="rsui-list-item__icon" aria-hidden="true">
110
+ <slot name="icon"></slot>
111
+ </div>
78
112
  <div v-if="avatar" class="rsui-list-item__avatar">
79
113
  <slot name="avatar">
80
114
  <div class="rsui-list-item__avatar-placeholder">
@@ -92,7 +126,14 @@ function clickItem(event) {
92
126
  </div>
93
127
  <div v-if="action" class="rsui-list-item__action">
94
128
  <slot name="action" :clickAction="clickAction">
95
- <ButtonTertiary xs @click="clickAction">
129
+ <ButtonTertiary
130
+ :xs="props.actionSize === 'xs'"
131
+ :sm="props.actionSize === 'sm'"
132
+ :md="props.actionSize === 'md'"
133
+ :lg="props.actionSize === 'lg'"
134
+ :xl="props.actionSize === 'xl'"
135
+ @click="clickAction"
136
+ >
96
137
  <slot name="action-label">Action</slot>
97
138
  </ButtonTertiary>
98
139
  </slot>