@conduction/nextcloud-vue 2.3.0-beta.1 → 2.3.0-beta.2
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 +233 -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/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 +130 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
- SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
- SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<section :class="sectionClass" :style="sectionStyle">
|
|
8
|
+
<div class="container">
|
|
9
|
+
<slot />
|
|
10
|
+
</div>
|
|
11
|
+
</section>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script>
|
|
15
|
+
/**
|
|
16
|
+
* A full-bleed band with a constrained reading column inside it.
|
|
17
|
+
*
|
|
18
|
+
* The band paints edge to edge and the `.container` holds the content at the
|
|
19
|
+
* design system's column width. That split is not cosmetic: every band in the
|
|
20
|
+
* NL Design System reference works this way, and a region that emits its
|
|
21
|
+
* content WITHOUT a container renders body copy hard against the viewport edge
|
|
22
|
+
* while the navigation above and the footer below start inset.
|
|
23
|
+
*
|
|
24
|
+
* PUBLIC-SAFE (see ../index.js): no `@nextcloud/*` import, directly or
|
|
25
|
+
* transitively. This component must render at a public origin where there is
|
|
26
|
+
* no Nextcloud, no `OC` global and no translation bundle.
|
|
27
|
+
*/
|
|
28
|
+
export default {
|
|
29
|
+
name: 'CnSiteSection',
|
|
30
|
+
|
|
31
|
+
props: {
|
|
32
|
+
/**
|
|
33
|
+
* Vertical rhythm. `spacing` adds the design system's block padding;
|
|
34
|
+
* `hero` is the taller leading band.
|
|
35
|
+
*/
|
|
36
|
+
variant: {
|
|
37
|
+
type: String,
|
|
38
|
+
default: 'spacing',
|
|
39
|
+
validator: (v) => ['spacing', 'hero', 'flush'].includes(v),
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
/** Optional background image URL, layered over the band's colour. */
|
|
43
|
+
backgroundImage: {
|
|
44
|
+
type: String,
|
|
45
|
+
default: '',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
computed: {
|
|
50
|
+
/**
|
|
51
|
+
* Inline background, when the host supplies an image.
|
|
52
|
+
*
|
|
53
|
+
* Inline rather than a class because the URL is per-portal CONTENT; a
|
|
54
|
+
* stylesheet cannot enumerate them. `background-image` is layered OVER
|
|
55
|
+
* the band's own colour rather than replacing it, so a slow or blocked
|
|
56
|
+
* image leaves the blue behind it and the text stays readable — an
|
|
57
|
+
* image that fails is not allowed to become white-on-white.
|
|
58
|
+
*
|
|
59
|
+
* @return {object|null} Style bindings, or null.
|
|
60
|
+
*/
|
|
61
|
+
sectionStyle() {
|
|
62
|
+
if (this.backgroundImage === '') {
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
backgroundImage: `url(${JSON.stringify(this.backgroundImage)})`,
|
|
68
|
+
backgroundSize: 'cover',
|
|
69
|
+
backgroundPosition: '50% 50%',
|
|
70
|
+
backgroundRepeat: 'no-repeat',
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @return {Array} The band's classes.
|
|
76
|
+
*/
|
|
77
|
+
sectionClass() {
|
|
78
|
+
return [
|
|
79
|
+
'ac-section',
|
|
80
|
+
this.variant === 'hero' ? 'ac-hero' : null,
|
|
81
|
+
this.variant === 'spacing' ? 'ac-section--spacing' : null,
|
|
82
|
+
].filter(Boolean)
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<style scoped>
|
|
89
|
+
/*
|
|
90
|
+
* ONLY THE TEXT THAT ACTUALLY SITS ON THE BAND IS RECOLOURED.
|
|
91
|
+
*
|
|
92
|
+
* `.ac-hero` paints itself `--tilburg-color-blue-500` — dark — and defines no
|
|
93
|
+
* colour for text placed directly on it, because the reference implementation
|
|
94
|
+
* never puts any there: its hero holds a white input and a blue button, both
|
|
95
|
+
* of which bring their own surface. So a heading or paragraph dropped onto the
|
|
96
|
+
* band inherits the document's dark body colour and lands dark-on-dark.
|
|
97
|
+
*
|
|
98
|
+
* THIS RULE WAS FIRST WRITTEN ON `.ac-hero` ITSELF, AND THAT WAS WRONG — it is
|
|
99
|
+
* the very mistake the rest of this codebase keeps recording. Colour set on an
|
|
100
|
+
* ancestor cannot know that a descendant paints its own background:
|
|
101
|
+
* `.ac-search-box` has one, `rgb(230, 246, 255)`, and inheriting white into it
|
|
102
|
+
* put the label and button at contrast 1.11 and the input's text at 1.0.
|
|
103
|
+
*
|
|
104
|
+
* Worse, the "bug" that prompted it was a MEASUREMENT ERROR. The label
|
|
105
|
+
* computed `rgb(51, 51, 51)`, which was compared against the BAND and scored
|
|
106
|
+
* 1.06 — but the label does not sit on the band, it sits on the search box's
|
|
107
|
+
* own light surface, where that colour is entirely correct. The contrast probe
|
|
108
|
+
* has to walk up to the first ancestor that actually paints a background;
|
|
109
|
+
* against the nearest NAMED band it reports a failure that is not there, and
|
|
110
|
+
* then invites a fix that breaks something real.
|
|
111
|
+
*
|
|
112
|
+
* So: the hero's own title and subtitle, and nothing else. The system's
|
|
113
|
+
* inverse token rather than a literal, so a theme with a light hero corrects
|
|
114
|
+
* both together.
|
|
115
|
+
*/
|
|
116
|
+
.ac-hero__title,
|
|
117
|
+
.ac-hero__subtitle {
|
|
118
|
+
color: var(--utrecht-document-inverse-color, #ffffff);
|
|
119
|
+
}
|
|
120
|
+
</style>
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* PUBLIC-SAFE SITE BLOCKS — the entry point a portal renderer can import when
|
|
6
|
+
* it runs at a public origin with NO Nextcloud behind it.
|
|
7
|
+
*
|
|
8
|
+
* WHY THIS ENTRY POINT EXISTS AT ALL
|
|
9
|
+
*
|
|
10
|
+
* nc-vue's main entry is deeply coupled to the Nextcloud runtime: measured on
|
|
11
|
+
* the published build, 154 files reference `@nextcloud/axios` and 157
|
|
12
|
+
* reference `@nextcloud/router`. That is correct for an app rendering inside
|
|
13
|
+
* Nextcloud and fatal for one rendering at `https://portaal.gemeente.nl`,
|
|
14
|
+
* where there is no `OC` global, no session and no translation bundle.
|
|
15
|
+
*
|
|
16
|
+
* WHY THESE ARE NEW COMPONENTS RATHER THAN RE-EXPORTS
|
|
17
|
+
*
|
|
18
|
+
* The first plan was to re-export the existing widgets that "looked clean".
|
|
19
|
+
* A DIRECT import check agreed: 12 of 13 candidates had no `@nextcloud/*`
|
|
20
|
+
* import in their own file. A TRANSITIVE check — following relative imports
|
|
21
|
+
* through the tree — inverted that result completely:
|
|
22
|
+
*
|
|
23
|
+
* CnTextWidget -> @nextcloud/l10n
|
|
24
|
+
* CnHeaderWidget -> @nextcloud/l10n, @nextcloud/router
|
|
25
|
+
* CnCardGrid -> @nextcloud/l10n, @nextcloud/vue,
|
|
26
|
+
* @nextcloud/auth, @nextcloud/event-bus
|
|
27
|
+
* ... 12 of 13 unsafe
|
|
28
|
+
*
|
|
29
|
+
* Only `CnCard` was clean all the way down. Re-exporting the rest under a
|
|
30
|
+
* name promising public safety would have shipped a guarantee that the first
|
|
31
|
+
* public deployment disproved — and it would have failed at RUNTIME, in a
|
|
32
|
+
* browser, on a government portal, rather than at build time here.
|
|
33
|
+
*
|
|
34
|
+
* So these blocks take their strings as PROPS instead of calling `t()`. That
|
|
35
|
+
* single decision is what keeps them portable: `@nextcloud/l10n` is the
|
|
36
|
+
* dependency almost every existing widget trips over.
|
|
37
|
+
*
|
|
38
|
+
* THE GUARANTEE IS CHECKED, NOT ASSERTED. `npm run check:public-safe` walks
|
|
39
|
+
* this entry's transitive imports and fails on any `@nextcloud/*`. A comment
|
|
40
|
+
* claiming purity is worth nothing; the check is in CI.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import CnSiteCard from './components/CnSiteCard.vue'
|
|
44
|
+
import CnSiteCardGrid from './components/CnSiteCardGrid.vue'
|
|
45
|
+
import CnSiteEmptyState from './components/CnSiteEmptyState.vue'
|
|
46
|
+
import CnSiteHero from './components/CnSiteHero.vue'
|
|
47
|
+
import CnSiteIcon from './components/CnSiteIcon.vue'
|
|
48
|
+
import CnSiteSearch from './components/CnSiteSearch.vue'
|
|
49
|
+
import CnSiteSection from './components/CnSiteSection.vue'
|
|
50
|
+
|
|
51
|
+
export {
|
|
52
|
+
CnSiteCard,
|
|
53
|
+
CnSiteCardGrid,
|
|
54
|
+
CnSiteEmptyState,
|
|
55
|
+
CnSiteHero,
|
|
56
|
+
CnSiteIcon,
|
|
57
|
+
CnSiteSearch,
|
|
58
|
+
CnSiteSection,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The block vocabulary, keyed by the `widgetKey` a page body declares.
|
|
63
|
+
*
|
|
64
|
+
* A REGISTRY rather than a switch in the consumer. A consumer that resolves
|
|
65
|
+
* blocks with `if (key === 'markdown')` cannot be extended without editing the
|
|
66
|
+
* consumer, which is how portaliq ended up supporting exactly one block type
|
|
67
|
+
* while this library already shipped thirty-four.
|
|
68
|
+
*
|
|
69
|
+
* Anything not in this map is UNKNOWN, and a renderer must say so visibly
|
|
70
|
+
* rather than render nothing — a page silently missing a block looks identical
|
|
71
|
+
* to a page that was authored empty.
|
|
72
|
+
*
|
|
73
|
+
* @type {Record<string, object>}
|
|
74
|
+
*/
|
|
75
|
+
export const siteBlockRegistry = {
|
|
76
|
+
hero: CnSiteHero,
|
|
77
|
+
search: CnSiteSearch,
|
|
78
|
+
section: CnSiteSection,
|
|
79
|
+
cardGrid: CnSiteCardGrid,
|
|
80
|
+
card: CnSiteCard,
|
|
81
|
+
emptyState: CnSiteEmptyState,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Blocks that are FULL-BLEED BANDS and carry their own container.
|
|
86
|
+
*
|
|
87
|
+
* A band paints edge to edge and constrains its content itself. Rendering one
|
|
88
|
+
* inside a host's content column silently shrinks it: measured against the NL
|
|
89
|
+
* Design System reference, a hero nested in the page container came out
|
|
90
|
+
* 1168px wide against the design's 1280, and no amount of styling inside the
|
|
91
|
+
* hero could recover the missing width because the clamp was an ancestor.
|
|
92
|
+
*
|
|
93
|
+
* The reference's own structure is the model — `main` is full-bleed and every
|
|
94
|
+
* `section` brings a `.container` — so a host must ask this before deciding
|
|
95
|
+
* whether to wrap a block.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} key The block key.
|
|
98
|
+
* @return {boolean} True when the block must NOT be wrapped in a container.
|
|
99
|
+
*/
|
|
100
|
+
export function siteBlockIsBand(key) {
|
|
101
|
+
return SITE_BAND_BLOCKS.includes(key)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @type {Array<string>} The band block keys.
|
|
106
|
+
*/
|
|
107
|
+
export const SITE_BAND_BLOCKS = ['hero', 'section']
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Resolve a block component by key.
|
|
111
|
+
*
|
|
112
|
+
* @param {string} key The `widgetKey` from a page body.
|
|
113
|
+
* @return {object|null} The component, or null when the key is unknown.
|
|
114
|
+
*/
|
|
115
|
+
export function siteBlockFor(key) {
|
|
116
|
+
return siteBlockRegistry[key] || null
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The keys this entry point can render.
|
|
121
|
+
*
|
|
122
|
+
* Exported so a consumer — or a test — can assert the vocabulary it depends on
|
|
123
|
+
* is actually present, rather than discovering a missing block as a blank area
|
|
124
|
+
* on a live page.
|
|
125
|
+
*
|
|
126
|
+
* @return {Array<string>} The known block keys.
|
|
127
|
+
*/
|
|
128
|
+
export function listSiteBlocks() {
|
|
129
|
+
return Object.keys(siteBlockRegistry)
|
|
130
|
+
}
|