@conduction/nextcloud-vue 2.3.0 → 2.4.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 +18 -2
- package/scripts/check-public-safe.js +115 -0
- package/src/public/__tests__/publicBlocks.spec.js +307 -0
- package/src/public/components/CnSiteCard.vue +178 -0
- package/src/public/components/CnSiteCardGrid.vue +98 -0
- package/src/public/components/CnSiteEmptyState.vue +172 -0
- package/src/public/components/CnSiteGlossary.vue +240 -0
- package/src/public/components/CnSiteHero.vue +239 -0
- package/src/public/components/CnSiteIcon.vue +90 -0
- package/src/public/components/CnSiteSearch.vue +173 -0
- package/src/public/components/CnSiteSection.vue +120 -0
- package/src/public/index.js +133 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
- SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
- SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<div class="ac-grid" :style="gridStyle">
|
|
8
|
+
<CnSiteCard
|
|
9
|
+
v-for="(card, index) in cards"
|
|
10
|
+
:key="card.id || card.title || index"
|
|
11
|
+
:title="card.title"
|
|
12
|
+
:description="card.description"
|
|
13
|
+
:link="card.link"
|
|
14
|
+
:link-label="card.linkLabel"
|
|
15
|
+
:icon="card.icon"
|
|
16
|
+
:heading-level="headingLevel"
|
|
17
|
+
:variant="card.variant || variant"
|
|
18
|
+
:padding="padding" />
|
|
19
|
+
<slot />
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script>
|
|
24
|
+
import CnSiteCard from './CnSiteCard.vue'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A responsive row of cards.
|
|
28
|
+
*
|
|
29
|
+
* Columns are expressed with `repeat(auto-fit, minmax(...))` rather than a
|
|
30
|
+
* fixed count, so the grid reflows on a narrow viewport without a media query
|
|
31
|
+
* per breakpoint. A fixed three-column grid is the usual cause of a card row
|
|
32
|
+
* that forces a phone to scroll horizontally.
|
|
33
|
+
*
|
|
34
|
+
* NOTE ON THE NAME: nc-vue already exports a `CnCardGrid`, and this is
|
|
35
|
+
* deliberately NOT that component. That one imports `@nextcloud/vue`,
|
|
36
|
+
* `@nextcloud/auth` and `@nextcloud/event-bus` (measured transitively), so it
|
|
37
|
+
* cannot run at a public origin. Re-exporting it under a "public" entry point
|
|
38
|
+
* would have made the entry point a lie.
|
|
39
|
+
*
|
|
40
|
+
* PUBLIC-SAFE (see ../index.js): no `@nextcloud/*` import.
|
|
41
|
+
*/
|
|
42
|
+
export default {
|
|
43
|
+
name: 'CnSiteCardGrid',
|
|
44
|
+
|
|
45
|
+
components: { CnSiteCard },
|
|
46
|
+
|
|
47
|
+
props: {
|
|
48
|
+
/** The cards to render. */
|
|
49
|
+
cards: {
|
|
50
|
+
type: Array,
|
|
51
|
+
default: () => [],
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
/** Minimum column width before the grid reflows. */
|
|
55
|
+
minColumnWidth: {
|
|
56
|
+
type: String,
|
|
57
|
+
default: '18rem',
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
/** Gap between cards. */
|
|
61
|
+
gap: {
|
|
62
|
+
type: String,
|
|
63
|
+
default: '1.75rem',
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
/** Heading level for every card, so the outline stays intact. */
|
|
67
|
+
headingLevel: {
|
|
68
|
+
type: Number,
|
|
69
|
+
default: 3,
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
/** Default variant for cards that do not name one. */
|
|
73
|
+
variant: {
|
|
74
|
+
type: String,
|
|
75
|
+
default: 'category',
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
/** Internal padding step for every card. */
|
|
79
|
+
padding: {
|
|
80
|
+
type: String,
|
|
81
|
+
default: 'md',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
computed: {
|
|
86
|
+
/**
|
|
87
|
+
* @return {object} Inline grid geometry.
|
|
88
|
+
*/
|
|
89
|
+
gridStyle() {
|
|
90
|
+
return {
|
|
91
|
+
display: 'grid',
|
|
92
|
+
gridTemplateColumns: `repeat(auto-fit, minmax(${this.minColumnWidth}, 1fr))`,
|
|
93
|
+
gap: this.gap,
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
</script>
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
- SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
- SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<div
|
|
8
|
+
:class="['ac-empty-state', `ac-empty-state--${variant}`]"
|
|
9
|
+
:role="variant === 'error' ? 'alert' : undefined"
|
|
10
|
+
:aria-busy="variant === 'loading' ? 'true' : undefined"
|
|
11
|
+
:aria-live="variant === 'loading' ? 'polite' : undefined">
|
|
12
|
+
<CnSiteIcon v-if="icon" :name="icon" :size="32" />
|
|
13
|
+
|
|
14
|
+
<component :is="headingTag" v-if="title" :class="headingClass">
|
|
15
|
+
{{ title }}
|
|
16
|
+
</component>
|
|
17
|
+
|
|
18
|
+
<p v-if="description" class="utrecht-paragraph">
|
|
19
|
+
{{ description }}
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
<a v-if="link" class="utrecht-link utrecht-link--html-a" :href="link">
|
|
23
|
+
{{ linkLabel || link }}
|
|
24
|
+
</a>
|
|
25
|
+
|
|
26
|
+
<slot />
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script>
|
|
31
|
+
import CnSiteIcon from './CnSiteIcon.vue'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The state a page shows when there is nothing to show yet, nothing to show at
|
|
35
|
+
* all, or nothing showable because something failed.
|
|
36
|
+
*
|
|
37
|
+
* WHY THIS IS NOT `NcEmptyContent`
|
|
38
|
+
*
|
|
39
|
+
* Nextcloud's own empty-state component is the obvious reuse and it cannot be
|
|
40
|
+
* used here: it comes from `@nextcloud/vue`, which is exactly the runtime
|
|
41
|
+
* coupling this entry point exists to keep out. A portal served from a
|
|
42
|
+
* municipality's own domain has no `OC` global, no session and no translation
|
|
43
|
+
* bundle, and a component reaching for them fails in a browser rather than at
|
|
44
|
+
* build time. The transitive-import guard in this package's CI would reject the
|
|
45
|
+
* import outright.
|
|
46
|
+
*
|
|
47
|
+
* WHY THE THREE VARIANTS ARE ONE COMPONENT
|
|
48
|
+
*
|
|
49
|
+
* `loading`, `empty` and `error` differ in what they ANNOUNCE, not in what they
|
|
50
|
+
* look like, and getting the announcement right is the entire point of having a
|
|
51
|
+
* component rather than a paragraph:
|
|
52
|
+
*
|
|
53
|
+
* loading aria-busy + aria-live="polite" — a screen reader is told the
|
|
54
|
+
* region is working and will be told again when it settles, rather
|
|
55
|
+
* than being handed a silent blank area
|
|
56
|
+
* error role="alert" — announced immediately, because a visitor who
|
|
57
|
+
* cannot see the page must not sit waiting for content that will
|
|
58
|
+
* never arrive
|
|
59
|
+
* empty neither. "There is nothing here" is ordinary content, and
|
|
60
|
+
* announcing it as an alert cries wolf.
|
|
61
|
+
*
|
|
62
|
+
* Splitting them into three components would let a caller pick the wrong
|
|
63
|
+
* announcement for the right visual, which is precisely the mistake this
|
|
64
|
+
* prevents.
|
|
65
|
+
*
|
|
66
|
+
* IT REPLACES A BARE SENTENCE. The portal previously rendered `<p>Bezig met
|
|
67
|
+
* laden…</p>` — no landmark, no live region, no icon, unstyled, and identical
|
|
68
|
+
* in markup to an error. A sighted visitor saw a stray line under the header; a
|
|
69
|
+
* screen-reader user was told nothing at all.
|
|
70
|
+
*
|
|
71
|
+
* PUBLIC-SAFE (see ../index.js): no `@nextcloud/*` import. All copy arrives as
|
|
72
|
+
* props, because `t()` is the dependency that makes a component unusable
|
|
73
|
+
* outside Nextcloud.
|
|
74
|
+
*/
|
|
75
|
+
export default {
|
|
76
|
+
name: 'CnSiteEmptyState',
|
|
77
|
+
|
|
78
|
+
components: { CnSiteIcon },
|
|
79
|
+
|
|
80
|
+
props: {
|
|
81
|
+
/**
|
|
82
|
+
* Which state this is. Decides the ARIA posture, not the styling.
|
|
83
|
+
*/
|
|
84
|
+
variant: {
|
|
85
|
+
type: String,
|
|
86
|
+
default: 'empty',
|
|
87
|
+
validator: (v) => ['loading', 'empty', 'error'].includes(v),
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
/** Headline, e.g. "Bezig met laden…" or "Pagina niet gevonden". */
|
|
91
|
+
title: {
|
|
92
|
+
type: String,
|
|
93
|
+
default: '',
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
/** Supporting line under the headline. */
|
|
97
|
+
description: {
|
|
98
|
+
type: String,
|
|
99
|
+
default: '',
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
/** Icon name from the closed vocabulary; '' renders none. */
|
|
103
|
+
icon: {
|
|
104
|
+
type: String,
|
|
105
|
+
default: '',
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
/** Optional way out — a link back to somewhere that works. */
|
|
109
|
+
link: {
|
|
110
|
+
type: String,
|
|
111
|
+
default: '',
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
/** Visible text for that link. */
|
|
115
|
+
linkLabel: {
|
|
116
|
+
type: String,
|
|
117
|
+
default: '',
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Heading level, so the page outline stays intact.
|
|
122
|
+
*
|
|
123
|
+
* An empty state usually replaces a page's main content, so it usually
|
|
124
|
+
* wants the level that content would have had — not a fixed one.
|
|
125
|
+
*/
|
|
126
|
+
headingLevel: {
|
|
127
|
+
type: Number,
|
|
128
|
+
default: 2,
|
|
129
|
+
validator: (v) => v >= 1 && v <= 6,
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
computed: {
|
|
134
|
+
/**
|
|
135
|
+
* @return {string} The heading element to render.
|
|
136
|
+
*/
|
|
137
|
+
headingTag() {
|
|
138
|
+
return `h${this.headingLevel}`
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The heading's class, tracking its level — the design system styles
|
|
143
|
+
* the class, not the tag.
|
|
144
|
+
*
|
|
145
|
+
* @return {string} e.g. `utrecht-heading-2`.
|
|
146
|
+
*/
|
|
147
|
+
headingClass() {
|
|
148
|
+
return `utrecht-heading-${this.headingLevel}`
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
}
|
|
152
|
+
</script>
|
|
153
|
+
|
|
154
|
+
<style scoped>
|
|
155
|
+
/*
|
|
156
|
+
* Layout only, and deliberately token-driven.
|
|
157
|
+
*
|
|
158
|
+
* No colours are set here: an empty state can land on any surface a host puts
|
|
159
|
+
* it on, and this codebase has three separate bugs on record from rules that
|
|
160
|
+
* coloured text without knowing its background. The design system colours the
|
|
161
|
+
* heading, paragraph and link classes already.
|
|
162
|
+
*/
|
|
163
|
+
.ac-empty-state {
|
|
164
|
+
display: flex;
|
|
165
|
+
flex-direction: column;
|
|
166
|
+
align-items: center;
|
|
167
|
+
justify-content: center;
|
|
168
|
+
text-align: center;
|
|
169
|
+
gap: var(--utrecht-space-block-md, 0.75rem);
|
|
170
|
+
padding-block: var(--utrecht-space-block-2xl, 3rem);
|
|
171
|
+
}
|
|
172
|
+
</style>
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
- SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
- SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<div class="ac-glossary">
|
|
8
|
+
<component :is="headingTag" v-if="title" :class="headingClass">
|
|
9
|
+
{{ title }}
|
|
10
|
+
</component>
|
|
11
|
+
|
|
12
|
+
<p v-if="description" class="utrecht-paragraph">
|
|
13
|
+
{{ description }}
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<!--
|
|
17
|
+
A DESCRIPTION LIST, not a stack of divs.
|
|
18
|
+
|
|
19
|
+
`<dl>`/`<dt>`/`<dd>` is what a glossary IS, and it is the difference
|
|
20
|
+
between a screen reader announcing "term, definition" pairs and
|
|
21
|
+
announcing an undifferentiated run of text. The visual result is
|
|
22
|
+
identical either way, which is why this is easy to get wrong.
|
|
23
|
+
-->
|
|
24
|
+
<dl v-if="terms.length" class="ac-glossary__list">
|
|
25
|
+
<div
|
|
26
|
+
v-for="(entry, index) in terms"
|
|
27
|
+
:key="entry.term || index"
|
|
28
|
+
class="ac-glossary__entry">
|
|
29
|
+
<dt class="ac-glossary__term utrecht-data-list__item-key">
|
|
30
|
+
{{ entry.term }}
|
|
31
|
+
</dt>
|
|
32
|
+
<dd class="ac-glossary__definition">
|
|
33
|
+
<p class="utrecht-paragraph">
|
|
34
|
+
{{ entry.definition }}
|
|
35
|
+
</p>
|
|
36
|
+
|
|
37
|
+
<!--
|
|
38
|
+
SYNONYMS ARE PART OF THE DEFINITION, not a footnote.
|
|
39
|
+
|
|
40
|
+
A visitor searching for the word they know ("Wob-verzoek")
|
|
41
|
+
finds nothing if only the current term is rendered, and
|
|
42
|
+
concludes the concept is absent rather than renamed. The
|
|
43
|
+
old name is often the only one they have.
|
|
44
|
+
-->
|
|
45
|
+
<p
|
|
46
|
+
v-if="synonymsOf(entry).length"
|
|
47
|
+
class="utrecht-paragraph ac-glossary__synonyms">
|
|
48
|
+
{{ synonymsLabel }} {{ synonymsOf(entry).join(', ') }}
|
|
49
|
+
</p>
|
|
50
|
+
|
|
51
|
+
<p v-if="entry.source" class="utrecht-paragraph ac-glossary__source">
|
|
52
|
+
{{ sourceLabel }} {{ entry.source }}
|
|
53
|
+
</p>
|
|
54
|
+
</dd>
|
|
55
|
+
</div>
|
|
56
|
+
</dl>
|
|
57
|
+
|
|
58
|
+
<p v-else class="utrecht-paragraph ac-glossary__empty">
|
|
59
|
+
{{ emptyLabel }}
|
|
60
|
+
</p>
|
|
61
|
+
</div>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<script>
|
|
65
|
+
/**
|
|
66
|
+
* A glossary: the terms a portal uses, with what each one means.
|
|
67
|
+
*
|
|
68
|
+
* WHY THIS IS A BLOCK AND NOT A PAGE
|
|
69
|
+
*
|
|
70
|
+
* The consuming portal used to render its glossary from a hard-coded
|
|
71
|
+
* `<section>` carrying a literal `<h2>Begrippenlijst</h2>`, on every page that
|
|
72
|
+
* satisfied a route check. A municipality could not move it, rename it,
|
|
73
|
+
* translate it, reorder it or leave it out, and it could only ever live at one
|
|
74
|
+
* URL. As a block it is content an author places — which is what it always
|
|
75
|
+
* was.
|
|
76
|
+
*
|
|
77
|
+
* WHY THE TERMS ARE A PROP
|
|
78
|
+
*
|
|
79
|
+
* Every string and every row arrives from the host. This entry point exists for
|
|
80
|
+
* pages served from a municipality's own domain, where there is no `OC` global,
|
|
81
|
+
* no session and no translation bundle; a component that fetched its own data
|
|
82
|
+
* or called `t()` would fail in a browser, on a live page, rather than at build
|
|
83
|
+
* time. The host already holds these rows — it fetched them over its own public
|
|
84
|
+
* contract — so passing them down costs nothing and keeps this component
|
|
85
|
+
* portable.
|
|
86
|
+
*
|
|
87
|
+
* THE LABELS ARE PROPS FOR THE SAME REASON. "Synoniemen" is not a word this
|
|
88
|
+
* library is entitled to choose on behalf of a Dutch government portal, and
|
|
89
|
+
* hard-coding an English default would put the wrong language on the page for
|
|
90
|
+
* every consumer this entry point was built for.
|
|
91
|
+
*
|
|
92
|
+
* PUBLIC-SAFE (see ../index.js): no `@nextcloud/*` import.
|
|
93
|
+
*/
|
|
94
|
+
export default {
|
|
95
|
+
name: 'CnSiteGlossary',
|
|
96
|
+
|
|
97
|
+
props: {
|
|
98
|
+
/**
|
|
99
|
+
* The glossary rows.
|
|
100
|
+
*
|
|
101
|
+
* Each entry is `{ term, definition, synonyms?, source? }`. `synonyms`
|
|
102
|
+
* tolerates a string or an array, because a single synonym arrives as
|
|
103
|
+
* either depending on the store that produced it, and a component that
|
|
104
|
+
* rendered `W,o,b` one character per row would be technically correct.
|
|
105
|
+
*/
|
|
106
|
+
terms: {
|
|
107
|
+
type: Array,
|
|
108
|
+
default: () => [],
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
/** Heading above the list; '' renders none. */
|
|
112
|
+
title: {
|
|
113
|
+
type: String,
|
|
114
|
+
default: '',
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
/** Supporting line under the heading. */
|
|
118
|
+
description: {
|
|
119
|
+
type: String,
|
|
120
|
+
default: '',
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Heading level, so the page outline stays intact.
|
|
125
|
+
*
|
|
126
|
+
* The design system styles `.utrecht-heading-2`, not `h2`, so the class
|
|
127
|
+
* tracks the level too — a host changing the level to keep an outline
|
|
128
|
+
* intact must not silently lose the styling with it.
|
|
129
|
+
*/
|
|
130
|
+
headingLevel: {
|
|
131
|
+
type: Number,
|
|
132
|
+
default: 2,
|
|
133
|
+
validator: (v) => v >= 1 && v <= 6,
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
/** Prefix for the synonyms line, e.g. 'Ook bekend als:'. */
|
|
137
|
+
synonymsLabel: {
|
|
138
|
+
type: String,
|
|
139
|
+
default: '',
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
/** Prefix for the source line, e.g. 'Bron:'. */
|
|
143
|
+
sourceLabel: {
|
|
144
|
+
type: String,
|
|
145
|
+
default: '',
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* What to say when there are no terms.
|
|
150
|
+
*
|
|
151
|
+
* An empty glossary renders this sentence rather than a bare heading
|
|
152
|
+
* over nothing, which reads as a page that failed to load.
|
|
153
|
+
*/
|
|
154
|
+
emptyLabel: {
|
|
155
|
+
type: String,
|
|
156
|
+
default: '',
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
computed: {
|
|
161
|
+
/**
|
|
162
|
+
* @return {string} The heading element to render.
|
|
163
|
+
*/
|
|
164
|
+
headingTag() {
|
|
165
|
+
return `h${this.headingLevel}`
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @return {string} The heading's class, tracking its level.
|
|
170
|
+
*/
|
|
171
|
+
headingClass() {
|
|
172
|
+
return `utrecht-heading-${this.headingLevel}`
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
methods: {
|
|
177
|
+
/**
|
|
178
|
+
* A term's synonyms, as a list, whatever shape they arrived in.
|
|
179
|
+
*
|
|
180
|
+
* @param {object} entry One glossary row.
|
|
181
|
+
* @return {string[]} The synonyms, empty when there are none.
|
|
182
|
+
*/
|
|
183
|
+
synonymsOf(entry) {
|
|
184
|
+
const raw = entry && entry.synonyms
|
|
185
|
+
if (!raw) {
|
|
186
|
+
return []
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// A bare string is ONE synonym. Spreading it would render each
|
|
190
|
+
// character as its own entry.
|
|
191
|
+
if (typeof raw === 'string') {
|
|
192
|
+
return raw.trim() ? [raw.trim()] : []
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return Array.isArray(raw) ? raw.filter(Boolean) : []
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
}
|
|
199
|
+
</script>
|
|
200
|
+
|
|
201
|
+
<style scoped>
|
|
202
|
+
/*
|
|
203
|
+
* Layout and rhythm only — no colours.
|
|
204
|
+
*
|
|
205
|
+
* A block lands on whatever surface a host puts it on, and this codebase has
|
|
206
|
+
* three separate defects on record from rules that coloured text without
|
|
207
|
+
* reference to its background. The design system colours
|
|
208
|
+
* `.utrecht-heading-*` and `.utrecht-paragraph` already.
|
|
209
|
+
*/
|
|
210
|
+
.ac-glossary__list {
|
|
211
|
+
margin-block-start: var(--utrecht-space-block-lg, 1.5rem);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.ac-glossary__entry + .ac-glossary__entry {
|
|
215
|
+
margin-block-start: var(--utrecht-space-block-lg, 1.5rem);
|
|
216
|
+
padding-block-start: var(--utrecht-space-block-lg, 1.5rem);
|
|
217
|
+
/*
|
|
218
|
+
* `currentColor` at low alpha rather than a colour token: the rule has to
|
|
219
|
+
* be visible on a light band and on a dark one, and it cannot know which
|
|
220
|
+
* it is on.
|
|
221
|
+
*/
|
|
222
|
+
border-block-start: 1px solid color-mix(in srgb, currentColor 15%, transparent);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.ac-glossary__term {
|
|
226
|
+
font-weight: 700;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.ac-glossary__definition {
|
|
230
|
+
/* `<dd>` carries a UA indent that misaligns the definition with its term. */
|
|
231
|
+
margin-inline-start: 0;
|
|
232
|
+
margin-block-start: var(--utrecht-space-block-sm, 0.5rem);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.ac-glossary__synonyms,
|
|
236
|
+
.ac-glossary__source {
|
|
237
|
+
margin-block-start: var(--utrecht-space-block-sm, 0.5rem);
|
|
238
|
+
font-size: 0.875em;
|
|
239
|
+
}
|
|
240
|
+
</style>
|