@jungherz-de/notionkit-elements 1.5.3 → 1.6.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/README.md +25 -16
- package/dist/components/base.js +346 -0
- package/dist/components/nk-ai-input-row.js +2 -1
- package/dist/components/nk-ai-msg.js +10 -3
- package/dist/components/nk-ai-thread.js +2 -1
- package/dist/components/nk-app.js +2 -1
- package/dist/components/nk-avatar-group.js +2 -1
- package/dist/components/nk-avatar.js +57 -0
- package/dist/components/nk-banner.js +2 -1
- package/dist/components/nk-block-host.js +2 -1
- package/dist/components/nk-board-view.js +4 -2
- package/dist/components/nk-breadcrumb.js +2 -1
- package/dist/components/nk-btn.js +2 -1
- package/dist/components/nk-callout.js +2 -1
- package/dist/components/nk-check.js +2 -1
- package/dist/components/nk-cmdk.js +2 -1
- package/dist/components/nk-code.js +2 -1
- package/dist/components/nk-comment.js +9 -6
- package/dist/components/nk-comments.js +2 -1
- package/dist/components/nk-danger-zone.js +2 -1
- package/dist/components/nk-database.js +2 -1
- package/dist/components/nk-divider.js +2 -1
- package/dist/components/nk-emoji-picker.js +2 -1
- package/dist/components/nk-empty.js +2 -1
- package/dist/components/nk-field.js +2 -1
- package/dist/components/nk-fields.js +2 -1
- package/dist/components/nk-filter-bar.js +2 -1
- package/dist/components/nk-heading.js +2 -1
- package/dist/components/nk-input.js +2 -1
- package/dist/components/nk-kbd.js +2 -1
- package/dist/components/nk-list-view.js +96 -0
- package/dist/components/nk-member-list.js +2 -1
- package/dist/components/nk-member-row.js +10 -6
- package/dist/components/nk-mention.js +2 -1
- package/dist/components/nk-menu-item.js +23 -3
- package/dist/components/nk-menu.js +2 -1
- package/dist/components/nk-modal.js +2 -1
- package/dist/components/nk-model-card.js +2 -1
- package/dist/components/nk-page-actions.js +2 -1
- package/dist/components/nk-page-cover.js +12 -11
- package/dist/components/nk-page-title.js +2 -1
- package/dist/components/nk-page.js +12 -4
- package/dist/components/nk-panel.js +61 -0
- package/dist/components/nk-panels.js +18 -0
- package/dist/components/nk-pop.js +2 -1
- package/dist/components/nk-profile-row.js +2 -1
- package/dist/components/nk-progress.js +5 -2
- package/dist/components/nk-prop.js +54 -0
- package/dist/components/nk-props.js +21 -0
- package/dist/components/nk-quote.js +2 -1
- package/dist/components/nk-radio.js +2 -1
- package/dist/components/nk-section-label.js +2 -1
- package/dist/components/nk-segmented.js +2 -1
- package/dist/components/nk-select.js +2 -1
- package/dist/components/nk-settings-pane.js +2 -1
- package/dist/components/nk-settings-user.js +2 -1
- package/dist/components/nk-sidebar.js +2 -1
- package/dist/components/nk-skeleton.js +2 -1
- package/dist/components/nk-slider.js +2 -1
- package/dist/components/nk-stat.js +2 -1
- package/dist/components/nk-stats.js +2 -1
- package/dist/components/nk-switch.js +2 -1
- package/dist/components/nk-synced.js +2 -1
- package/dist/components/nk-tab-bar-item.js +2 -1
- package/dist/components/nk-tab-bar.js +2 -1
- package/dist/components/nk-tab.js +2 -1
- package/dist/components/nk-table-view.js +6 -2
- package/dist/components/nk-tabs.js +2 -1
- package/dist/components/nk-tag.js +2 -1
- package/dist/components/nk-template-btn.js +2 -1
- package/dist/components/nk-textarea.js +2 -1
- package/dist/components/nk-theme-toggle.js +2 -1
- package/dist/components/nk-toast.js +2 -1
- package/dist/components/nk-todo.js +2 -1
- package/dist/components/nk-toggle.js +2 -1
- package/dist/components/nk-topbar.js +2 -1
- package/dist/components/nk-tree-item.js +2 -1
- package/dist/components/nk-tree.js +2 -1
- package/dist/components/nk-workspace-switcher.js +2 -1
- package/dist/components/shared/avatar-BiqCaHOt.js +19 -0
- package/dist/components/shared/{property-cell-CtXQpUjQ.js → property-cell-o7N30kAQ.js} +8 -3
- package/dist/notionkit-elements.esm.js +374 -34
- package/dist/notionkit-elements.js +380 -33
- package/dist/notionkit-elements.min.js +1 -1
- package/package.json +4 -3
- package/src/components/content/nk-progress.js +3 -1
- package/src/components/data/nk-ai-msg.js +8 -2
- package/src/components/data/nk-comment.js +7 -5
- package/src/components/data/nk-list-view.js +93 -0
- package/src/components/data/nk-table-view.js +2 -0
- package/src/components/overlays/nk-menu-item.js +21 -2
- package/src/components/page/nk-avatar.js +55 -0
- package/src/components/page/nk-member-row.js +8 -5
- package/src/components/page/nk-page-cover.js +10 -10
- package/src/components/page/nk-page.js +10 -3
- package/src/components/page/nk-panel.js +59 -0
- package/src/components/page/nk-panels.js +16 -0
- package/src/components/page/nk-prop.js +52 -0
- package/src/components/page/nk-props.js +19 -0
- package/src/index.js +14 -0
- package/src/util/avatar.js +17 -0
- package/src/util/property-cell.js +7 -3
- package/dist/components/shared/base-C3eJwHKA.js +0 -366
package/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
<h1 align="center">🧩 NotionKit Elements</h1>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<a href="https://www.npmjs.com/package/@jungherz-de/notionkit-elements"><img src="https://img.shields.io/badge/version-1.
|
|
4
|
+
<a href="https://www.npmjs.com/package/@jungherz-de/notionkit-elements"><img src="https://img.shields.io/badge/version-1.6.0-2383e2?style=flat-square" alt="Version"></a>
|
|
5
5
|
<a href="#"><img src="https://img.shields.io/badge/vanilla_JS-no_dependencies-448361?style=flat-square" alt="Vanilla JS"></a>
|
|
6
|
-
<a href="#"><img src="https://img.shields.io/badge/elements-
|
|
6
|
+
<a href="#"><img src="https://img.shields.io/badge/elements-77-529cca?style=flat-square" alt="77 Elements"></a>
|
|
7
7
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-9065b0?style=flat-square" alt="MIT License"></a>
|
|
8
|
-
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-v1.
|
|
8
|
+
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-v1.6.0-lightgrey?style=flat-square" alt="Changelog"></a>
|
|
9
9
|
<a href="https://www.npmjs.com/package/@jungherz-de/notionkit-elements"><img src="https://img.shields.io/badge/npm-%40jungherz--de%2Fnotionkit--elements-cb3837?style=flat-square&logo=npm" alt="npm"></a>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
13
|
<strong>Drop-in Web Components for <a href="https://github.com/JUNGHERZ/NotionKit">NotionKit CSS</a></strong><br>
|
|
14
|
-
|
|
14
|
+
77 vanilla JavaScript custom elements wrapping NotionKit's calm, document-centric workspace look.<br>
|
|
15
15
|
Shadow DOM · Native form participation · Pixel parity with the class markup · Zero dependencies.
|
|
16
16
|
</p>
|
|
17
17
|
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
## ✨ What is NotionKit Elements?
|
|
30
30
|
|
|
31
|
-
NotionKit Elements is the companion library to [NotionKit CSS](https://github.com/JUNGHERZ/NotionKit). It provides **
|
|
31
|
+
NotionKit Elements is the companion library to [NotionKit CSS](https://github.com/JUNGHERZ/NotionKit). It provides **77 Web Components** – from buttons and form controls over the sidebar, page tree and page shell with its properties to the settings modal, ⌘K palette, database views and AI thread – that encapsulate the class markup of the foundation into simple, declarative custom elements.
|
|
32
32
|
|
|
33
33
|
It is the **app layer** of the NotionKit family – three layers, one design language: [NotionKit](https://notionkit.jungherz.com) is the pure-CSS foundation, NotionKit Elements wraps it into web components for application UIs, and [NotionKit Web](https://notionkit-web.jungherz.com) is the Astro template for complete websites on top of the same foundation. The word stem stays the same across the layers: `.nk-callout` becomes `<nk-callout>`, a modifier class becomes an attribute, a state class becomes a boolean attribute.
|
|
34
34
|
|
|
@@ -56,11 +56,11 @@ It is the **app layer** of the NotionKit family – three layers, one design lan
|
|
|
56
56
|
|---|---|
|
|
57
57
|
| 🎯 **Pixel parity** | Every element renders identically to its class markup – verified by a Playwright pixel test in light and dark; the reference app differs by 0.00 % from the class version |
|
|
58
58
|
| 🔌 **Shadow DOM** | Each root adopts the NotionKit *components* sheet via `adoptedStyleSheets`; tokens are inherited from the document, so one `:root { --nk-accent: … }` re-brands everything |
|
|
59
|
-
| 🧩 **
|
|
59
|
+
| 🧩 **77 Elements** | Forms, content blocks, app shell & tree, page shell with properties, panels and avatars, overlays (modal, ⌘K palette, menus, popover, emoji picker, toast), database table, board & list, comments, AI thread |
|
|
60
60
|
| 🎛️ **Form participation** | Input, textarea, select, switch, check, radio, slider, segmented and model card work natively with `<form>` via `ElementInternals`; radios group across shadow roots |
|
|
61
61
|
| 🌗 **Theme sync** | One observer mirrors `data-theme` on `<html>` into every element |
|
|
62
|
-
| 🪶 **Lightweight** |
|
|
63
|
-
| 📦 **Four bundle shapes** | IIFE, minified IIFE, ESM, and per-component ESM entries
|
|
62
|
+
| 🪶 **Lightweight** | 197 KB minified / 39 KB gzipped (IIFE, foundation sheet included), no external dependencies |
|
|
63
|
+
| 📦 **Four bundle shapes** | IIFE, minified IIFE, ESM, and per-component ESM entries on a stable `base.js`, which import NotionKit's sheet instead of carrying a copy |
|
|
64
64
|
| 🤖 **AI-ready** | `SKILL.md` with copy-paste markup, six app skeletons and the rules – an agent given only that file built a working app in the release test |
|
|
65
65
|
|
|
66
66
|
---
|
|
@@ -71,8 +71,8 @@ It is the **app layer** of the NotionKit family – three layers, one design lan
|
|
|
71
71
|
<!DOCTYPE html>
|
|
72
72
|
<html lang="en" data-theme="light">
|
|
73
73
|
<head>
|
|
74
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.
|
|
75
|
-
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.
|
|
74
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.6.0/notionkit.min.css">
|
|
75
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.6.0/dist/notionkit-elements.min.js"></script>
|
|
76
76
|
</head>
|
|
77
77
|
<body class="nk-body">
|
|
78
78
|
<nk-field label="Display name" desc="Shown next to your comments">
|
|
@@ -90,10 +90,19 @@ npm install @jungherz-de/notionkit-elements @jungherz-de/notionkit
|
|
|
90
90
|
```js
|
|
91
91
|
import '@jungherz-de/notionkit/notionkit.css';
|
|
92
92
|
import '@jungherz-de/notionkit-elements'; // everything
|
|
93
|
-
import '@jungherz-de/notionkit-elements/components/nk-btn.js'; // or one element (shared code
|
|
93
|
+
import '@jungherz-de/notionkit-elements/components/nk-btn.js'; // or one element (shared code: base.js)
|
|
94
|
+
import { componentsSheet } from '@jungherz-de/notionkit-elements'; // the sheet the elements adopt, for your own views
|
|
94
95
|
```
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
The per-component files import NotionKit's sheet as `@jungherz-de/notionkit/notionkit-styles.js` rather than carrying a copy, so a project that adopts the sheet in its own views has it once. A bundler resolves it from `node_modules`; a build-free page adds one import-map entry:
|
|
98
|
+
|
|
99
|
+
```html
|
|
100
|
+
<script type="importmap">{ "imports": { "@jungherz-de/notionkit/notionkit-styles.js": "/node_modules/@jungherz-de/notionkit/notionkit-styles.js" } }</script>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Do not mix the full bundle with the per-component files: each brings its own `NkElement`. With the bundle, take `componentsSheet` and `NkElement` from the bundle (`NotionKitElements.componentsSheet` from the `<script>` build).
|
|
104
|
+
|
|
105
|
+
Two prerequisites, always: `notionkit.css` on the document (the peer dependency, `>= 1.6.0`; 1.6.0 brings page properties, page options, list view, panels, prose and the avatar, 1.5.1 the safe areas, 1.5.0 the Notion-2025 tokens, the nine tag colours and the filled inputs; from 1.5.0 on, Elements and the foundation share one version number – the elements are built and tested against the same release) and `class="nk-body"` on `<body>`. The elements ship no visual CSS of their own – every rule comes from the foundation. The bundle injects the design tokens once as `@layer notionkit-defaults`, so any plain `:root { --nk-accent: … }` of yours re-brands every element in both themes.
|
|
97
106
|
|
|
98
107
|
## Elements
|
|
99
108
|
|
|
@@ -102,9 +111,9 @@ Two prerequisites, always: `notionkit.css` on the document (the peer dependency,
|
|
|
102
111
|
| Forms & controls | `nk-btn` `nk-input` `nk-textarea` `nk-select` `nk-switch` `nk-check` `nk-radio` `nk-slider` `nk-field` `nk-fields` |
|
|
103
112
|
| Content | `nk-tag` `nk-progress` `nk-callout` `nk-divider` `nk-heading` `nk-toggle` `nk-todo` `nk-kbd` `nk-code` `nk-quote` |
|
|
104
113
|
| App shell & navigation | `nk-app` `nk-sidebar` `nk-workspace-switcher` `nk-section-label` `nk-tree` `nk-tree-item` `nk-topbar` `nk-breadcrumb` `nk-theme-toggle` `nk-tab-bar` `nk-tab-bar-item` |
|
|
105
|
-
| Page shell & blocks | `nk-page` `nk-page-cover` `nk-page-title` `nk-page-actions` `nk-block-host` `nk-banner` `nk-empty` `nk-skeleton` `nk-synced` `nk-tabs` `nk-tab` `nk-segmented` `nk-stats` `nk-stat` `nk-avatar-group` `nk-mention` `nk-template-btn` `nk-model-card` `nk-profile-row` `nk-danger-zone` `nk-member-list` `nk-member-row` |
|
|
114
|
+
| Page shell & blocks | `nk-page` `nk-page-cover` `nk-page-title` `nk-page-actions` `nk-props` `nk-prop` `nk-panels` `nk-panel` `nk-avatar` `nk-block-host` `nk-banner` `nk-empty` `nk-skeleton` `nk-synced` `nk-tabs` `nk-tab` `nk-segmented` `nk-stats` `nk-stat` `nk-avatar-group` `nk-mention` `nk-template-btn` `nk-model-card` `nk-profile-row` `nk-danger-zone` `nk-member-list` `nk-member-row` |
|
|
106
115
|
| Overlays | `nk-modal` `nk-settings-pane` `nk-settings-user` `nk-cmdk` `nk-menu` `nk-menu-item` `nk-pop` `nk-emoji-picker` `nk-toast` |
|
|
107
|
-
| Data & collaboration | `nk-database` `nk-table-view` `nk-board-view` `nk-filter-bar` `nk-comments` `nk-comment` `nk-ai-thread` `nk-ai-msg` `nk-ai-input-row` (+ the exported `renderPropertyCell()`) |
|
|
116
|
+
| Data & collaboration | `nk-database` `nk-table-view` `nk-board-view` `nk-list-view` `nk-filter-bar` `nk-comments` `nk-comment` `nk-ai-thread` `nk-ai-msg` `nk-ai-input-row` (+ the exported `renderPropertyCell()`) |
|
|
108
117
|
|
|
109
118
|
Static content goes in through attributes and slots, dynamic data through properties (`tree.data`, `database.rows`, `palette.commands`). Interactions fire custom events with fixed names: `nk-select`, `nk-change`, `nk-view-change`, `nk-command`, `nk-toggle`, `nk-submit`, `nk-action`. No fetching, no two-way binding – the elements render what they get.
|
|
110
119
|
|
|
@@ -114,7 +123,7 @@ Static content goes in through attributes and slots, dynamic data through proper
|
|
|
114
123
|
- Every host is `display: contents`: the inner `.nk-*` element sits in the parent layout exactly where the class markup would. That is what makes the pixel parity hold – and why spacing belongs on a wrapper you own, not on the host.
|
|
115
124
|
- Form controls are form-associated custom elements: `FormData`, `reset`, `required` and `<fieldset disabled>` just work. `<nk-radio>`s with the same `name` form a real group across shadow roots, with one tab stop and arrow keys.
|
|
116
125
|
- Elements that copy light-DOM children (`nk-select` options, breadcrumb crumbs) watch them; `element.refresh()` is the escape hatch. Listeners are re-armed on every connect, so a moved element keeps working.
|
|
117
|
-
- `::slotted()` matches only the assigned node: pass icons directly (`<span slot="icon">📁</span>`), never wrapped.
|
|
126
|
+
- `::slotted()` matches only the assigned node: pass icons directly (`<span slot="icon">📁</span>`), never wrapped. For the same reason there is no `<nk-prose>`: rendered Markdown goes into a light-DOM `<div class="nk-prose">`, which reads the same rules as the editor adapter.
|
|
118
127
|
- Overlays (`nk-modal`, `nk-cmdk`, `nk-toast`) go directly under `<body>`; they lock scroll, make the rest of the page `inert` and return focus.
|
|
119
128
|
- The editor stays an adapter: `nk-block-host` is the shell, `docs-editor.js` the TipTap recipe (used in the demo app). `<nk-editor>` follows in v1.1 as an optional import, never in the core bundle.
|
|
120
129
|
|
|
@@ -122,7 +131,7 @@ Static content goes in through attributes and slots, dynamic data through proper
|
|
|
122
131
|
|
|
123
132
|
```bash
|
|
124
133
|
npm install
|
|
125
|
-
npm run build # dist/: IIFE, minified IIFE, ESM, per-component ESM
|
|
134
|
+
npm run build # dist/: IIFE, minified IIFE, ESM, per-component ESM on dist/components/base.js
|
|
126
135
|
npm run build:all # + docs.html, showcase.html, index.html, de/, SKILL.md, llms.txt
|
|
127
136
|
npm test # Playwright: pixel parity vs. class markup (both themes), branding, reconnect, radio groups, light-DOM drift, forms, overlays, data views, per-component import
|
|
128
137
|
npm run check:coverage && npm run check:versions
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { tokensCss, componentsSheet } from '@jungherz-de/notionkit/notionkit-styles.js';
|
|
2
|
+
|
|
3
|
+
// ============================================================
|
|
4
|
+
// NotionKit Elements – base classes
|
|
5
|
+
//
|
|
6
|
+
// Every <nk-*> element is a thin shell around the markup notionkit.css already
|
|
7
|
+
// styles. The shadow root adopts the *components* half of that stylesheet and
|
|
8
|
+
// nothing else; the element ships no visual CSS of its own.
|
|
9
|
+
// ============================================================
|
|
10
|
+
|
|
11
|
+
// ── Design tokens ──
|
|
12
|
+
// The shadow roots deliberately adopt componentsSheet only. The full sheet
|
|
13
|
+
// would bring the :root / [data-theme] token blocks along, and those match the
|
|
14
|
+
// .nk-wrapper below — every --nk-* token would then be re-declared inside each
|
|
15
|
+
// shadow root, where a matching rule always beats an inherited value. A page's
|
|
16
|
+
// own `:root { --nk-accent: … }` would never arrive.
|
|
17
|
+
//
|
|
18
|
+
// So the token defaults go on the document once, and every shadow root
|
|
19
|
+
// inherits them like any other custom property. They sit in a cascade layer so
|
|
20
|
+
// an ordinary (unlayered) brand stylesheet — and notionkit.css itself, if the
|
|
21
|
+
// page loads it — wins over them, no matter the load order.
|
|
22
|
+
|
|
23
|
+
const TOKENS_INJECTED = '__nkDefaultTokensInjected';
|
|
24
|
+
|
|
25
|
+
function injectDefaultTokens() {
|
|
26
|
+
if (typeof document === 'undefined') return; // SSR / non-DOM
|
|
27
|
+
if (globalThis[TOKENS_INJECTED]) return; // another bundle copy did it
|
|
28
|
+
globalThis[TOKENS_INJECTED] = true;
|
|
29
|
+
|
|
30
|
+
const sheet = new CSSStyleSheet();
|
|
31
|
+
sheet.replaceSync(`@layer notionkit-defaults { ${tokensCss} }`);
|
|
32
|
+
// Append — never assign — so an app's own adopted sheets survive.
|
|
33
|
+
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
injectDefaultTokens();
|
|
37
|
+
|
|
38
|
+
// ── Global theme sync ──
|
|
39
|
+
// data-theme on <html> is the only source of truth. One MutationObserver
|
|
40
|
+
// mirrors it onto every registered instance; no element sets a theme itself.
|
|
41
|
+
|
|
42
|
+
const instances = new Set();
|
|
43
|
+
|
|
44
|
+
function getCurrentTheme() {
|
|
45
|
+
return document.documentElement.getAttribute('data-theme') || 'light';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function syncAllThemes() {
|
|
49
|
+
const theme = getCurrentTheme();
|
|
50
|
+
for (const instance of instances) instance._syncTheme(theme);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (typeof window !== 'undefined' && typeof MutationObserver !== 'undefined') {
|
|
54
|
+
new MutationObserver(syncAllThemes).observe(document.documentElement, {
|
|
55
|
+
attributes: true,
|
|
56
|
+
attributeFilter: ['data-theme']
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Host stylesheets ──
|
|
61
|
+
// Layout plumbing only: how the host box participates in the outer layout, and
|
|
62
|
+
// that `hidden` keeps working. :host([hidden]) has a higher specificity than
|
|
63
|
+
// :host, so it wins regardless of order. The wrapper is layout-transparent.
|
|
64
|
+
|
|
65
|
+
const hostSheets = new Map();
|
|
66
|
+
|
|
67
|
+
function hostSheetFor(display) {
|
|
68
|
+
let sheet = hostSheets.get(display);
|
|
69
|
+
if (!sheet) {
|
|
70
|
+
sheet = new CSSStyleSheet();
|
|
71
|
+
sheet.replaceSync(`
|
|
72
|
+
:host { display: ${display}; }
|
|
73
|
+
:host([hidden]) { display: none; }
|
|
74
|
+
.nk-wrapper { display: contents; }
|
|
75
|
+
`);
|
|
76
|
+
hostSheets.set(display, sheet);
|
|
77
|
+
}
|
|
78
|
+
return sheet;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ── Base class ──
|
|
82
|
+
|
|
83
|
+
class NkElement extends HTMLElement {
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* How the host takes part in the outer layout. Default 'contents': the host
|
|
87
|
+
* generates no box, so the inner .nk-* element sits in the parent's layout
|
|
88
|
+
* exactly where the class markup would – a button stays inline, a sidebar
|
|
89
|
+
* is a direct flex child of the app, a tree row and its children box are
|
|
90
|
+
* siblings. That is what makes the pixel parity with the class markup hold.
|
|
91
|
+
* Consequence: style the parent or the tokens, not the host; `hidden` on
|
|
92
|
+
* the host still works. Override with 'block' / 'inline-block' only when a
|
|
93
|
+
* host box is genuinely needed.
|
|
94
|
+
*/
|
|
95
|
+
static get display() { return 'contents'; }
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Optional CSSStyleSheet with component-specific host rules, adopted after
|
|
99
|
+
* the shared sheets. Positioning and sizing of the host only — never colour,
|
|
100
|
+
* spacing or typography; those belong to notionkit.css.
|
|
101
|
+
*/
|
|
102
|
+
static get hostStyles() { return null; }
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Opt in when the component *copies* light-DOM children into its shadow tree
|
|
106
|
+
* (options, breadcrumb crumbs). A MutationObserver then calls
|
|
107
|
+
* projectLightDom() again whenever those children change, so a framework
|
|
108
|
+
* that swaps them keeps the rendered element in step. Components that merely
|
|
109
|
+
* *slot* their children use `slotchange` instead.
|
|
110
|
+
*/
|
|
111
|
+
static get observesLightDom() { return false; }
|
|
112
|
+
|
|
113
|
+
/** What the light-DOM observer watches. Drop `subtree` for shallow copies. */
|
|
114
|
+
static get lightDomObserverInit() {
|
|
115
|
+
return { childList: true, subtree: true, characterData: true, attributes: true };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 'named' (default) or 'manual'. Manual assignment lets a component route
|
|
120
|
+
* light-DOM nodes itself – text nodes into a label slot, nested elements
|
|
121
|
+
* into a children slot – so <nk-tree-item icon="🏠">Home</nk-tree-item>
|
|
122
|
+
* reads naturally. With 'manual' the component calls assignSlots() on
|
|
123
|
+
* every childList change.
|
|
124
|
+
*/
|
|
125
|
+
static get slotAssignment() { return 'named'; }
|
|
126
|
+
|
|
127
|
+
static get observedAttributes() { return []; }
|
|
128
|
+
|
|
129
|
+
constructor() {
|
|
130
|
+
super();
|
|
131
|
+
this._initialized = false;
|
|
132
|
+
this._shadow = this.attachShadow({ mode: 'open', slotAssignment: this.constructor.slotAssignment });
|
|
133
|
+
const sheets = [componentsSheet, hostSheetFor(this.constructor.display)];
|
|
134
|
+
const extra = this.constructor.hostStyles;
|
|
135
|
+
if (extra) sheets.push(extra);
|
|
136
|
+
this._shadow.adoptedStyleSheets = sheets;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
connectedCallback() {
|
|
140
|
+
if (!this._initialized) {
|
|
141
|
+
// A framework may have set properties on the instance before the
|
|
142
|
+
// definition arrived (hybrids binds checked="${bool}" as a property
|
|
143
|
+
// while the element is still a plain HTMLElement). Such an own property
|
|
144
|
+
// shadows the accessor on the prototype for good; re-apply it through
|
|
145
|
+
// the setter so the attribute – and with it render() – sees the value.
|
|
146
|
+
this._upgradeOwnProperties();
|
|
147
|
+
this._initialized = true;
|
|
148
|
+
|
|
149
|
+
// The wrapper mirrors data-theme from <html> so [data-theme]-keyed
|
|
150
|
+
// component rules apply inside the root. Token *values* need no
|
|
151
|
+
// mirroring — they inherit.
|
|
152
|
+
this._wrapper = document.createElement('div');
|
|
153
|
+
this._wrapper.className = 'nk-wrapper';
|
|
154
|
+
this._wrapper.setAttribute('data-theme', getCurrentTheme());
|
|
155
|
+
this._shadow.appendChild(this._wrapper);
|
|
156
|
+
|
|
157
|
+
this.render();
|
|
158
|
+
} else {
|
|
159
|
+
this._wrapper.setAttribute('data-theme', getCurrentTheme());
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Everything below runs on every connect, not just the first. Moving an
|
|
163
|
+
// element in the DOM disconnects and reconnects it, and disconnectedCallback
|
|
164
|
+
// tears all of this down — without re-arming it here a moved element would
|
|
165
|
+
// keep its markup but silently stop reacting.
|
|
166
|
+
this.setupEvents();
|
|
167
|
+
instances.add(this);
|
|
168
|
+
|
|
169
|
+
if (this.constructor.observesLightDom) {
|
|
170
|
+
this._lightDomObserver ??= new MutationObserver(records => this.projectLightDom(records));
|
|
171
|
+
this._lightDomObserver.observe(this, this.constructor.lightDomObserverInit);
|
|
172
|
+
}
|
|
173
|
+
if (this.constructor.slotAssignment === 'manual') {
|
|
174
|
+
this.assignSlots();
|
|
175
|
+
this._slotObserver ??= new MutationObserver(() => this.assignSlots());
|
|
176
|
+
this._slotObserver.observe(this, { childList: true });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
disconnectedCallback() {
|
|
181
|
+
instances.delete(this);
|
|
182
|
+
this._lightDomObserver?.disconnect();
|
|
183
|
+
this._slotObserver?.disconnect();
|
|
184
|
+
this.teardownEvents();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
188
|
+
if (!this._initialized) return;
|
|
189
|
+
if (oldValue === newValue) return;
|
|
190
|
+
this.onAttributeChanged(name, oldValue, newValue);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
_syncTheme(theme) {
|
|
194
|
+
this._wrapper?.setAttribute('data-theme', theme);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
_upgradeOwnProperties() {
|
|
198
|
+
for (const key of Object.keys(this)) {
|
|
199
|
+
if (key.startsWith('_')) continue;
|
|
200
|
+
let proto = Object.getPrototypeOf(this), desc;
|
|
201
|
+
while (proto && proto !== HTMLElement.prototype && !(desc = Object.getOwnPropertyDescriptor(proto, key))) proto = Object.getPrototypeOf(proto);
|
|
202
|
+
if (!desc?.set) continue;
|
|
203
|
+
const value = this[key];
|
|
204
|
+
delete this[key];
|
|
205
|
+
this[key] = value;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Subclasses override to build the inner DOM inside this._wrapper. Runs once. */
|
|
210
|
+
render() {}
|
|
211
|
+
|
|
212
|
+
/** Subclasses override to attach event listeners. Runs on every connect. */
|
|
213
|
+
setupEvents() {}
|
|
214
|
+
|
|
215
|
+
/** Subclasses override to remove event listeners. Runs on every disconnect. */
|
|
216
|
+
teardownEvents() {}
|
|
217
|
+
|
|
218
|
+
/** Subclasses override to react to attribute changes after render(). */
|
|
219
|
+
onAttributeChanged(name, oldValue, newValue) {}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Subclasses that set observesLightDom override this to (re-)copy their
|
|
223
|
+
* light-DOM children into the shadow tree. Runs on every change to those
|
|
224
|
+
* children, so it has to be safe to call repeatedly.
|
|
225
|
+
*/
|
|
226
|
+
projectLightDom() {}
|
|
227
|
+
|
|
228
|
+
/** Components with manual slot assignment override this. */
|
|
229
|
+
assignSlots() {}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Escape hatch: re-copy the light-DOM children now. The observer covers the
|
|
233
|
+
* ordinary cases; this is for the ones it cannot see, so nobody has to reach
|
|
234
|
+
* into element.shadowRoot.
|
|
235
|
+
*/
|
|
236
|
+
refresh() { this.projectLightDom(); if (this.constructor.slotAssignment === 'manual') this.assignSlots(); }
|
|
237
|
+
|
|
238
|
+
// ── Utilities ──
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* For elements whose heading arrives as `title` – nk-settings-pane,
|
|
242
|
+
* nk-danger-zone, nk-empty, nk-model-card. `title` is also the global HTML
|
|
243
|
+
* attribute, and a title on the host shows as a browser tooltip over the
|
|
244
|
+
* whole element. So the value is read into this._titleText and the
|
|
245
|
+
* attribute is taken off the host; a later setAttribute('title') arrives
|
|
246
|
+
* through attributeChangedCallback and is taken the same way. The removal
|
|
247
|
+
* fires the callback with null, which is ignored – only '' clears the
|
|
248
|
+
* heading. The element's own `title` accessor answers from the stored
|
|
249
|
+
* value, so a framework that binds `title` as a property never puts an
|
|
250
|
+
* attribute on the host at all. Returns whether a value was taken.
|
|
251
|
+
*/
|
|
252
|
+
takeTitle(value = this.getAttribute('title')) {
|
|
253
|
+
if (value === null) return false;
|
|
254
|
+
this._titleText = value;
|
|
255
|
+
this.removeAttribute('title');
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
getBoolAttr(name) {
|
|
260
|
+
return this.hasAttribute(name);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
setBoolAttr(name, value) {
|
|
264
|
+
if (value) this.setAttribute(name, '');
|
|
265
|
+
else this.removeAttribute(name);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
createElement(tag, classes = [], attrs = {}) {
|
|
269
|
+
const el = document.createElement(tag);
|
|
270
|
+
if (classes.length) el.classList.add(...classes);
|
|
271
|
+
for (const [key, val] of Object.entries(attrs)) {
|
|
272
|
+
if (val !== null && val !== undefined && val !== false) el.setAttribute(key, val === true ? '' : val);
|
|
273
|
+
}
|
|
274
|
+
return el;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Dispatches a composed, bubbling CustomEvent — the nk-* event contract. */
|
|
278
|
+
emit(eventName, detail = null) {
|
|
279
|
+
return this.dispatchEvent(new CustomEvent(eventName, {
|
|
280
|
+
bubbles: true,
|
|
281
|
+
composed: true,
|
|
282
|
+
cancelable: true,
|
|
283
|
+
detail
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ── Form-associated base class ──
|
|
289
|
+
// ElementInternals gives the element a place in the surrounding <form>:
|
|
290
|
+
// FormData, reset, validation and :disabled through <fieldset>.
|
|
291
|
+
|
|
292
|
+
class NkFormElement extends NkElement {
|
|
293
|
+
|
|
294
|
+
static formAssociated = true;
|
|
295
|
+
|
|
296
|
+
constructor() {
|
|
297
|
+
super();
|
|
298
|
+
this._internals = this.attachInternals();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
get form() { return this._internals.form; }
|
|
302
|
+
get validationMessage() { return this._internals.validationMessage; }
|
|
303
|
+
get validity() { return this._internals.validity; }
|
|
304
|
+
get willValidate() { return this._internals.willValidate; }
|
|
305
|
+
|
|
306
|
+
checkValidity() { return this._internals.checkValidity(); }
|
|
307
|
+
reportValidity() { return this._internals.reportValidity(); }
|
|
308
|
+
|
|
309
|
+
connectedCallback() {
|
|
310
|
+
const first = !this._initialized;
|
|
311
|
+
super.connectedCallback();
|
|
312
|
+
// A `disabled` attribute in the markup fires formDisabledCallback on
|
|
313
|
+
// parse or upgrade – before the first connect, when nothing is rendered
|
|
314
|
+
// yet. The state was remembered below; apply it now that render() ran.
|
|
315
|
+
if (first && this._formDisabled) this.onFormDisabled(true);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
formResetCallback() { this.resetValue(); }
|
|
319
|
+
formStateRestoreCallback(state, mode) { this.restoreValue(state, mode); }
|
|
320
|
+
formDisabledCallback(disabled) {
|
|
321
|
+
this._formDisabled = disabled;
|
|
322
|
+
if (this._initialized) this.onFormDisabled(disabled);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** Subclasses override. */
|
|
326
|
+
resetValue() {}
|
|
327
|
+
restoreValue(state, mode) {}
|
|
328
|
+
onFormDisabled(disabled) {}
|
|
329
|
+
|
|
330
|
+
setFormValue(value, state) {
|
|
331
|
+
this._internals.setFormValue(value, state);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
setValidity(flags, message, anchor) {
|
|
335
|
+
this._internals.setValidity(flags, message, anchor);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Mirrors the native control's validity onto the host, so required works. */
|
|
339
|
+
syncValidityFrom(control) {
|
|
340
|
+
if (!control) return;
|
|
341
|
+
if (control.validity.valid) this._internals.setValidity({});
|
|
342
|
+
else this._internals.setValidity(control.validity, control.validationMessage, control);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export { NkElement, NkFormElement, getCurrentTheme };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NkElement } from './base.js';
|
|
2
|
+
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
2
3
|
|
|
3
4
|
// <nk-ai-input-row placeholder="Ask Mona something …"></nk-ai-input-row>
|
|
4
5
|
// → <div class="nk-ai-input-row"><span>✨</span><input><button class="nk-ai-send">↑</button></div>
|
|
@@ -1,20 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NkElement } from './base.js';
|
|
2
|
+
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
2
3
|
|
|
3
4
|
// <nk-ai-msg role="user" name="You" avatar="MK">Summarise the open tasks.</nk-ai-msg>
|
|
4
5
|
// <nk-ai-msg role="assistant" name="Mona" badge="· AI" avatar="✨">
|
|
5
6
|
// Two tasks are open …
|
|
6
7
|
// <button slot="actions" value="copy">📋 Copy</button><button slot="actions" value="retry">↻ Rephrase</button>
|
|
7
8
|
// </nk-ai-msg>
|
|
9
|
+
// <nk-ai-msg role="user" bubble>What is still open?</nk-ai-msg> → a grey bubble on the right, as in Notion's AI chat
|
|
8
10
|
// → <div class="nk-ai-msg user"><span class="mini-avatar">MK</span><div class="a-body"><div class="a-name">You</div>…<div class="nk-ai-actions">…</div></div></div>
|
|
9
11
|
// Action buttons stay in the light DOM (slotted twins) and fire nk-action { action: value }.
|
|
10
12
|
class NkAiMsg extends NkElement {
|
|
11
|
-
static get observedAttributes() { return ['role', 'name', 'badge', 'avatar', 'color']; }
|
|
13
|
+
static get observedAttributes() { return ['role', 'name', 'badge', 'avatar', 'color', 'bubble']; }
|
|
12
14
|
|
|
13
15
|
render() {
|
|
14
16
|
this._box = this.createElement('div', ['nk-ai-msg']);
|
|
15
17
|
this._avatar = this.createElement('span', ['mini-avatar']);
|
|
16
18
|
const avatarSlot = this.createElement('slot', [], { name: 'avatar' });
|
|
17
19
|
avatarSlot.appendChild(this._avatar);
|
|
20
|
+
this._avatarSlot = avatarSlot;
|
|
18
21
|
const body = this.createElement('div', ['a-body']);
|
|
19
22
|
this._name = this.createElement('div', ['a-name']);
|
|
20
23
|
this._nameText = document.createTextNode('');
|
|
@@ -31,12 +34,16 @@ class NkAiMsg extends NkElement {
|
|
|
31
34
|
|
|
32
35
|
_sync() {
|
|
33
36
|
const user = this.getAttribute('role') === 'user';
|
|
37
|
+
const bubble = this.getBoolAttr('bubble');
|
|
34
38
|
this._box.classList.toggle('user', user);
|
|
39
|
+
this._box.classList.toggle('bubble', bubble);
|
|
35
40
|
const name = this.getAttribute('name');
|
|
36
41
|
this._nameText.data = name || '';
|
|
37
42
|
const badge = this.getAttribute('badge');
|
|
38
43
|
this._badge.textContent = badge ? ` ${badge}` : '';
|
|
39
|
-
|
|
44
|
+
// A bubble stands without avatar and name; the text says who asked.
|
|
45
|
+
this._name.style.display = name && !bubble ? '' : 'none';
|
|
46
|
+
this._avatarSlot.style.display = bubble ? 'none' : '';
|
|
40
47
|
this._avatar.textContent = this.getAttribute('avatar') || (user ? (name || 'U').slice(0, 2).toUpperCase() : '✨');
|
|
41
48
|
const color = this.getAttribute('color');
|
|
42
49
|
this._avatar.style.background = color || '';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NkElement } from './base.js';
|
|
2
|
+
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
2
3
|
|
|
3
4
|
// <nk-ai-thread><nk-ai-msg …>…</nk-ai-msg>…</nk-ai-thread> → <div class="nk-ai-thread">
|
|
4
5
|
class NkAiThread extends NkElement {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { NkElement } from './base.js';
|
|
2
|
+
import { p as paintAvatar, i as initialsOf } from './shared/avatar-BiqCaHOt.js';
|
|
3
|
+
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
4
|
+
|
|
5
|
+
// <nk-avatar>MK</nk-avatar> → <span class="nk-avatar">MK</span>
|
|
6
|
+
// <nk-avatar size="small" color="green">SL</nk-avatar>
|
|
7
|
+
// <nk-avatar name="Ada Lovelace" size="large"></nk-avatar> → initials "AL"
|
|
8
|
+
// <nk-avatar src="ada.jpg" name="Ada Lovelace" size="xlarge"></nk-avatar>
|
|
9
|
+
// <nk-avatar square>A</nk-avatar> → a workspace icon
|
|
10
|
+
// size: small (20px) · default 24px · large (32px) · xlarge (56px). color: one
|
|
11
|
+
// of Notion's nine names, or any CSS background. Without content the initials
|
|
12
|
+
// come from `name`; with `src` a photo fills the circle and `name` is its alt.
|
|
13
|
+
class NkAvatar extends NkElement {
|
|
14
|
+
static get observedAttributes() { return ['size', 'color', 'square', 'src', 'name']; }
|
|
15
|
+
|
|
16
|
+
render() {
|
|
17
|
+
this._box = this.createElement('span', ['nk-avatar']);
|
|
18
|
+
this._slot = document.createElement('slot');
|
|
19
|
+
this._initials = document.createTextNode('');
|
|
20
|
+
this._slot.appendChild(this._initials);
|
|
21
|
+
this._img = this.createElement('img', [], { alt: '' });
|
|
22
|
+
this._box.appendChild(this._slot);
|
|
23
|
+
this._wrapper.appendChild(this._box);
|
|
24
|
+
this._sync();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
_sync() {
|
|
28
|
+
const size = this.getAttribute('size');
|
|
29
|
+
for (const s of ['small', 'large', 'xlarge']) this._box.classList.toggle(s, s === size);
|
|
30
|
+
this._box.classList.toggle('square', this.getBoolAttr('square'));
|
|
31
|
+
paintAvatar(this._box, this.getAttribute('color'));
|
|
32
|
+
const name = this.getAttribute('name');
|
|
33
|
+
this._initials.data = initialsOf(name);
|
|
34
|
+
const src = this.getAttribute('src');
|
|
35
|
+
if (src) {
|
|
36
|
+
this._img.src = src;
|
|
37
|
+
this._img.alt = name || '';
|
|
38
|
+
if (this._slot.isConnected) this._slot.replaceWith(this._img);
|
|
39
|
+
} else if (this._img.isConnected) this._img.replaceWith(this._slot);
|
|
40
|
+
// A picture or initials with a name speak for themselves; a bare glyph is decoration.
|
|
41
|
+
if (name && !src) { this._box.setAttribute('role', 'img'); this._box.setAttribute('aria-label', name); }
|
|
42
|
+
else { this._box.removeAttribute('role'); this._box.removeAttribute('aria-label'); }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
onAttributeChanged() { this._sync(); }
|
|
46
|
+
|
|
47
|
+
get size() { return this.getAttribute('size'); }
|
|
48
|
+
set size(v) { v == null ? this.removeAttribute('size') : this.setAttribute('size', v); }
|
|
49
|
+
get color() { return this.getAttribute('color'); }
|
|
50
|
+
set color(v) { v == null ? this.removeAttribute('color') : this.setAttribute('color', v); }
|
|
51
|
+
get src() { return this.getAttribute('src'); }
|
|
52
|
+
set src(v) { v == null ? this.removeAttribute('src') : this.setAttribute('src', v); }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
customElements.define('nk-avatar', NkAvatar);
|
|
56
|
+
|
|
57
|
+
export { NkAvatar };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NkElement } from './base.js';
|
|
2
|
+
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
2
3
|
|
|
3
4
|
// <nk-banner variant="info">ℹ️ <span>Text</span><span slot="action">Open</span></nk-banner>
|
|
4
5
|
// → <div class="nk-banner info">…<span class="b-action">Open</span></div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NkElement } from './base.js';
|
|
2
|
+
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
2
3
|
|
|
3
4
|
// <nk-block-host handle><p>Editable content lives here.</p></nk-block-host>
|
|
4
5
|
// → <div class="nk-block-host"><span class="nk-block-handle">⠿</span>…</div>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as tagFor, r as renderPropertyCell } from './shared/property-cell-
|
|
1
|
+
import { NkElement } from './base.js';
|
|
2
|
+
import { t as tagFor, r as renderPropertyCell } from './shared/property-cell-o7N30kAQ.js';
|
|
3
|
+
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
4
|
+
import './shared/avatar-BiqCaHOt.js';
|
|
3
5
|
|
|
4
6
|
// <nk-board-view name="board" label="▤ Board" group-by="status" new-row></nk-board-view>
|
|
5
7
|
// Groups rows by a select column (default: the first select column); one
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NkElement } from './base.js';
|
|
2
|
+
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
2
3
|
|
|
3
4
|
// <nk-btn variant="primary" small>Save</nk-btn> → <button class="nk-btn primary small">
|
|
4
5
|
// <nk-btn href="/docs" variant="secondary">Docs</nk-btn> → <a class="nk-btn secondary">
|