@redseed/redseed-ui-vue3 11.0.0 → 11.0.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/package.json
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { computed, provide, ref } from 'vue'
|
|
3
|
+
import { useResponsiveWidth } from '../../helpers'
|
|
4
|
+
|
|
2
5
|
const props = defineProps({
|
|
3
6
|
topAside: {
|
|
4
7
|
type: Boolean,
|
|
@@ -14,9 +17,41 @@ const props = defineProps({
|
|
|
14
17
|
default: false,
|
|
15
18
|
},
|
|
16
19
|
})
|
|
20
|
+
|
|
21
|
+
const layoutElement = ref(null)
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 896, because that is `@4xl` — the container query in `two_column_layout.css` that puts
|
|
25
|
+
* the columns side by side. Measured here as well as queried there so components INSIDE a
|
|
26
|
+
* column can ask the same question, which CSS alone cannot tell them.
|
|
27
|
+
*
|
|
28
|
+
* Kept honest by a test asserting the stylesheet still breaks at `@4xl`. Two numbers that
|
|
29
|
+
* must agree is a drift risk; an unasserted one is a certainty.
|
|
30
|
+
*/
|
|
31
|
+
const SIDE_BY_SIDE_WIDTH = 896
|
|
32
|
+
|
|
33
|
+
const { responsiveWidth } = useResponsiveWidth(layoutElement, SIDE_BY_SIDE_WIDTH)
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Whether the layout is showing two columns right now.
|
|
37
|
+
*
|
|
38
|
+
* Provided because a descendant cannot work this out for itself. `SectionHeader` needs it:
|
|
39
|
+
* where its actions go depends on whether it is a COLUMN on a page or the page itself, and
|
|
40
|
+
* a 330px aside and a 330px phone are the same measurement with opposite answers. Its own
|
|
41
|
+
* container cannot tell them apart, and neither can a viewport breakpoint — this collapses
|
|
42
|
+
* on its own width, so the viewport it collapses at moves with whatever chrome is around
|
|
43
|
+
* it. In Storybook that is 32px of padding and the columns hold down to a 928px viewport.
|
|
44
|
+
*
|
|
45
|
+
* A ref rather than a value, through a slot boundary, same as `Card` provides
|
|
46
|
+
* `rsuiCardHasBody` — slot content is compiled in the parent's scope but mounted with this
|
|
47
|
+
* component as its parent instance, so inject resolves here.
|
|
48
|
+
*/
|
|
49
|
+
provide('rsuiTwoColumnLayout', {
|
|
50
|
+
isSideBySide: computed(() => responsiveWidth.value.specific),
|
|
51
|
+
})
|
|
17
52
|
</script>
|
|
18
53
|
<template>
|
|
19
|
-
<div class="rsui-two-column-layout">
|
|
54
|
+
<div ref="layoutElement" class="rsui-two-column-layout">
|
|
20
55
|
<div
|
|
21
56
|
:class="[
|
|
22
57
|
'rsui-two-column-layout__container',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { ref, computed, useAttrs, useSlots } from 'vue'
|
|
2
|
+
import { ref, computed, inject, useAttrs, useSlots } from 'vue'
|
|
3
3
|
import { rendersContent } from '../../helpers/slots'
|
|
4
4
|
import ButtonTertiary from '../Button/ButtonTertiary.vue'
|
|
5
5
|
import Icon from '../Icon/Icon.vue'
|
|
@@ -125,6 +125,43 @@ const sectionHeaderElement = ref(null)
|
|
|
125
125
|
|
|
126
126
|
const { responsiveWidth } = useResponsiveWidth(sectionHeaderElement, 640)
|
|
127
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Whether the enclosing `TwoColumnLayout` is currently showing two columns, or null when
|
|
130
|
+
* there is no layout around this header.
|
|
131
|
+
*
|
|
132
|
+
* This is the question `responsiveWidth` cannot answer. The container says how much room
|
|
133
|
+
* the header has — truncation, whether a badge fits — and that is the right question for
|
|
134
|
+
* everything inside it. Where the ACTIONS go is a different question: a 330px aside on a
|
|
135
|
+
* desktop wants its single "View all" beside the title, and the same 330px on a phone
|
|
136
|
+
* wants it underneath. Identical container, opposite answers, so the container cannot
|
|
137
|
+
* decide it.
|
|
138
|
+
*
|
|
139
|
+
* A viewport breakpoint cannot decide it either, which is what the first cut of this got
|
|
140
|
+
* wrong. `TwoColumnLayout` collapses on a CONTAINER query — 896px of its own width — so
|
|
141
|
+
* the viewport it collapses at moves with whatever chrome surrounds it. In Storybook that
|
|
142
|
+
* is 32px of padding and the columns hold down to a 928px viewport, and the 64rem constant
|
|
143
|
+
* stacked the actions across the whole 928–1023 band while the aside was still an aside.
|
|
144
|
+
* More page furniture moves the boundary the other way. No constant fits.
|
|
145
|
+
*
|
|
146
|
+
* So the header rides on the layout's own answer rather than approximating it, and the
|
|
147
|
+
* two can never disagree.
|
|
148
|
+
*/
|
|
149
|
+
const twoColumnLayout = inject('rsuiTwoColumnLayout', null)
|
|
150
|
+
|
|
151
|
+
const isColumnBesideAnother = computed(() => twoColumnLayout?.isSideBySide.value ?? false)
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* The actions stay in the header row while this header is one of two columns, however
|
|
155
|
+
* narrow the column is. A single "View all" beside a wrapped title reads better in a 330px
|
|
156
|
+
* aside than the same link dropped into a left-aligned block below it — and a view-all is
|
|
157
|
+
* the only thing in this slot in practice: of 17 lms1 headers using `#actions`, 12 hold
|
|
158
|
+
* exactly that and none renders two controls at once.
|
|
159
|
+
*
|
|
160
|
+
* Once the layout collapses, every column is the full width of the page and the container
|
|
161
|
+
* measurement answers it correctly on its own, which is what it did before any of this.
|
|
162
|
+
*/
|
|
163
|
+
const keepsActionsInline = computed(() => responsiveWidth.value.specific || isColumnBesideAnother.value)
|
|
164
|
+
|
|
128
165
|
const emit = defineEmits(['click:more-actions'])
|
|
129
166
|
|
|
130
167
|
function handleMoreActionsClick() {
|
|
@@ -225,7 +262,11 @@ const titleTag = computed(() => HEADING_LEVELS.includes(String(props.headingLeve
|
|
|
225
262
|
// It used to come from `md:` — a viewport query at 768 — while the icon
|
|
226
263
|
// gate, the actions swap and the badge stacking all read the container at
|
|
227
264
|
// 640, so a narrow column on a wide screen got both layouts at once. #420.
|
|
228
|
-
'rsui-section-header--narrow': !
|
|
265
|
+
'rsui-section-header--narrow': !keepsActionsInline,
|
|
266
|
+
// Container only, deliberately NOT the one above. Where the actions go is a
|
|
267
|
+
// question about the page; whether the title has room is a question about this
|
|
268
|
+
// column, and the two want different answers in an aside on a wide screen.
|
|
269
|
+
'rsui-section-header--narrow-column': !responsiveWidth.specific,
|
|
229
270
|
}
|
|
230
271
|
]"
|
|
231
272
|
:role="isClickable ? 'button' : undefined"
|
|
@@ -300,7 +341,7 @@ const titleTag = computed(() => HEADING_LEVELS.includes(String(props.headingLeve
|
|
|
300
341
|
>
|
|
301
342
|
<!-- Desktop actions slot, optional -->
|
|
302
343
|
<div class="rsui-section-header__actions-desktop"
|
|
303
|
-
v-if="
|
|
344
|
+
v-if="keepsActionsInline && $slots.actions"
|
|
304
345
|
>
|
|
305
346
|
<slot name="actions"></slot>
|
|
306
347
|
</div>
|
|
@@ -329,7 +370,7 @@ const titleTag = computed(() => HEADING_LEVELS.includes(String(props.headingLeve
|
|
|
329
370
|
|
|
330
371
|
<!-- Mobile actions slot, optional -->
|
|
331
372
|
<div class="rsui-section-header__actions-mobile"
|
|
332
|
-
v-if="showMobileActions && !
|
|
373
|
+
v-if="showMobileActions && !keepsActionsInline && $slots.actions"
|
|
333
374
|
>
|
|
334
375
|
<slot name="actions"></slot>
|
|
335
376
|
</div>
|