diff --git a/docs/components/cn-color-picker.md b/docs/components/cn-color-picker.md
index 6e00da6..35bfcda 100644
--- a/docs/components/cn-color-picker.md
+++ b/docs/components/cn-color-picker.md
@@ -1,6 +1,6 @@
# CnColorPicker
-Themed wrapper around vue-color's `Chrome` picker. Forwards all props and events to the underlying component, so the full vue-color API stays available — this component only exists to remap the picker's hardcoded light-mode palette to Nextcloud CSS variables so it follows the active theme (light, dark, or nldesign).
+A color swatch button that opens a themed `Chrome` color picker (vue-color) in a popover. Clicking the swatch square toggles the picker. The active color is shown in the swatch (with a checker pattern behind it so alpha values render correctly). Remaps the picker's hardcoded light-mode palette to Nextcloud CSS variables so it follows the active theme (light, dark, or nldesign).
## Usage
@@ -30,22 +30,18 @@ export default {
## Props & events
-All props and listeners are forwarded to vue-color's `Chrome` component. The most commonly used:
-
| Prop | Type | Default | Description |
|------|------|---------|-------------|
-| `value` | `string \| object` | — | Current color (string like `'#abcdef'`, `'rgba(...)'`, `'hsl(...)'`, or a vue-color color object). |
-| `disabled` | `Boolean` | `false` | Disables the swatch trigger and prevents the popover from opening. |
+| `value` | `string \| object` | `null` | Current color (string like `'#abcdef'`, `'rgba(...)'`, `'hsl(...)'`, or a vue-color color object). `null`/empty renders a transparent swatch. |
+| `disabled` | `Boolean` | `false` | Disables the swatch button and prevents the popover from opening. |
| `mode` | `'hex' \| 'rgb' \| 'hsl' \| null` | `null` | Lock the numeric-input fields to a single mode and hide the toggle. `null` lets the user switch. The shown fields include alpha when `disable-alpha` is `false` (so `'rgb'` becomes RGBA, `'hsl'` becomes HSLA). |
-| `disable-alpha` | `Boolean` | `false` | Forwarded to `Chrome`. Hides the alpha bar and the alpha numeric field. |
-| `disable-fields` | `Boolean` | `false` | Forwarded to `Chrome`. Hides the hex/RGB/HSL numeric fields entirely. |
+
+Additional props (e.g. `disable-alpha`, `disable-fields`) are forwarded via `$attrs` to the underlying `Chrome` picker. See vue-color's [`Chrome` component](https://github.com/linx4200/vue-color) for the full prop surface.
| Event | Payload | Description |
|-------|---------|-------------|
| `input` | `{ hex, hex8, rgba, hsl, hsv, a, source }` | Fires whenever the user changes the color via any control. |
-See vue-color's [`Chrome` component](https://github.com/linx4200/vue-color) for the full prop and event surface.
-
## Theming
This component remaps the following Chrome internals to Nextcloud variables:
@@ -64,7 +60,6 @@ Consumers don't need to add any CSS overrides themselves.
## Notes
-- This is purely the picker UI. It does not include a swatch trigger, popover, text input, or format conversion — wire those at the call site if you need them.
- `vue-color` is a direct dependency of this library, so the picker is always available.
## Related
diff --git a/docs/components/cn-dashboard-page.md b/docs/components/cn-dashboard-page.md
index 9f9f102..f63cced 100644
--- a/docs/components/cn-dashboard-page.md
+++ b/docs/components/cn-dashboard-page.md
@@ -98,6 +98,8 @@ const { widgets, layout, loading, onLayoutChange } = useDashboardView({
| `type` | String | `'custom'` (default) or `'tile'` |
| `iconUrl` | String | Header icon image URL |
| `iconClass` | String | Header icon CSS class |
+| `titleIconPosition` | String | Position of the `widget-{id}-title-icon` slot: `'left'` (before title) or `'right'` (after actions, default) |
+| `titleIconColor` | String | CSS color applied to the title-icon slot container (e.g. `'#e74c3c'`) |
| `buttons` | Array | Footer buttons: `[{ text, link }]` |
| `itemApiVersions` | Number[] | NC Dashboard API versions — triggers auto-rendering |
| `reloadInterval` | Number | Auto-refresh interval in seconds (NC widgets) |
@@ -129,4 +131,5 @@ const { widgets, layout, loading, onLayoutChange } = useDashboardView({
| `header-actions` | — | Extra buttons in the page header (right side) |
| `widget-{widgetId}` | `{ item, widget }` | Custom content for a specific widget |
| `widget-{widgetId}-actions` | `{ item, widget }` | Header action buttons for a specific widget |
+| `widget-{widgetId}-title-icon` | `{ item, widget }` | Extra icon in the widget header; position and color controlled by `titleIconPosition` / `titleIconColor` on the widget definition |
| `empty` | — | Custom empty state when no layout items exist |
diff --git a/docs/components/cn-widget-wrapper.md b/docs/components/cn-widget-wrapper.md
index 9b7aee2..318ef76 100644
--- a/docs/components/cn-widget-wrapper.md
+++ b/docs/components/cn-widget-wrapper.md
@@ -46,6 +46,8 @@ Container shell around a dashboard widget. Provides a header with icon and title
| `flush` | Boolean | `false` | Remove content padding — lets content extend edge-to-edge |
| `iconUrl` | String | `null` | Image URL for the header icon |
| `iconClass` | String | `null` | CSS class for the header icon (e.g. Nextcloud icon class) |
+| `titleIconPosition` | String | `'right'` | Position of the `title-icon` slot in the header: `'left'` places it before the title group; `'right'` places it after the actions |
+| `titleIconColor` | String | `null` | CSS color value applied to the `title-icon` slot container (e.g. `'#e74c3c'`) |
| `buttons` | Array | `[]` | Footer button links: `[{ text, link }]` |
| `styleConfig` | Object | `{}` | Runtime style overrides: `{ backgroundColor?, borderStyle?, borderWidth?, borderColor?, borderRadius?, padding?: { top, right, bottom, left } }` |
@@ -55,4 +57,5 @@ Container shell around a dashboard widget. Provides a header with icon and title
|------|-------------|
| default | Widget content rendered in the scrollable body area |
| `actions` | Buttons or controls placed in the right side of the header |
+| `title-icon` | Extra icon element rendered in the header at the position controlled by `titleIconPosition` (left of title or right of actions) |
| `footer` | Custom footer content (replaces the `buttons` prop rendering) |
diff --git a/src/components/CnDashboardPage/CnDashboardPage.vue b/src/components/CnDashboardPage/CnDashboardPage.vue
index 6214f42..a1d72ed 100644
--- a/src/components/CnDashboardPage/CnDashboardPage.vue
+++ b/src/components/CnDashboardPage/CnDashboardPage.vue
@@ -83,7 +83,13 @@
:borderless="item.showTitle === false"
:flush="item.flush === true"
:buttons="getWidgetButtons(item)"
- :style-config="item.styleConfig || {}">
+ :style-config="item.styleConfig || {}"
+ :title-icon-position="getWidgetTitleIconPosition(item)"
+ :title-icon-color="getWidgetTitleIconColor(item)">
+
+
+
+
@@ -320,6 +326,16 @@ export default {
return def?.buttons || []
},
+ getWidgetTitleIconPosition(item) {
+ const def = this.getWidgetDef(item.widgetId)
+ return def?.titleIconPosition || 'right'
+ },
+
+ getWidgetTitleIconColor(item) {
+ const def = this.getWidgetDef(item.widgetId)
+ return def?.titleIconColor || null
+ },
+
isTile(item) {
const def = this.getWidgetDef(item.widgetId)
return def?.type === 'tile'
diff --git a/src/components/CnWidgetWrapper/CnWidgetWrapper.vue b/src/components/CnWidgetWrapper/CnWidgetWrapper.vue
index 5f90637..ba19169 100644
--- a/src/components/CnWidgetWrapper/CnWidgetWrapper.vue
+++ b/src/components/CnWidgetWrapper/CnWidgetWrapper.vue
@@ -15,6 +15,12 @@
:style="wrapperStyles">
@@ -114,6 +126,20 @@ export default {
type: String,
default: null,
},
+ /**
+ * Position of the title-icon slot in the header.
+ * 'left' places it before the title; 'right' places it after the actions.
+ */
+ titleIconPosition: {
+ type: String,
+ default: 'right',
+ validator: (v) => ['left', 'right'].includes(v),
+ },
+ /** CSS color value applied to the title-icon slot container */
+ titleIconColor: {
+ type: String,
+ default: null,
+ },
/** Footer action buttons: [{ text, link }] */
buttons: {
type: Array,
@@ -227,6 +253,12 @@ export default {
flex-shrink: 0;
}
+.cn-widget-wrapper__title-icon {
+ display: flex;
+ align-items: center;
+ flex-shrink: 0;
+}
+
.cn-widget-wrapper__footer {
display: flex;
justify-content: flex-end;