@placeholderco/placeholder-ui 2.2.2 → 2.2.4
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/dist/app.css +7 -1
- package/dist/layout/Navbar.svelte +131 -6
- package/dist/layout/Navbar.svelte.d.ts +10 -2
- package/dist/layout/NavbarItemView.svelte +7 -2
- package/dist/models/NavbarItem.d.ts +2 -0
- package/dist/ui/Link.svelte +5 -1
- package/dist/ui/Link.svelte.d.ts +2 -0
- package/dist/ui/Table.svelte.d.ts +1 -1
- package/package.json +1 -1
package/dist/app.css
CHANGED
|
@@ -46,7 +46,6 @@
|
|
|
46
46
|
--ui-link-color: var(--ui-tertiary-dark);
|
|
47
47
|
--ui-link-hover-color: var(--ui-accent-dark);
|
|
48
48
|
|
|
49
|
-
|
|
50
49
|
/* ---- Surfaces and text (light mode) ---- */
|
|
51
50
|
--off-white: rgb(221, 221, 221);
|
|
52
51
|
--input-bg: #fff;
|
|
@@ -82,6 +81,13 @@
|
|
|
82
81
|
font-family: var(--ui-font-family);
|
|
83
82
|
}
|
|
84
83
|
|
|
84
|
+
/* The `site` Navbar variant is a taller marketing header. Raising the token here (rather than
|
|
85
|
+
on the header itself) keeps --page-content-height, Sidenav and Drawer in step with it.
|
|
86
|
+
:where() keeps specificity at :root level so a consumer's own --header-height still wins. */
|
|
87
|
+
:root:where(:has(.pui-navbar.site)) {
|
|
88
|
+
--header-height: 68px;
|
|
89
|
+
}
|
|
90
|
+
|
|
85
91
|
.dark {
|
|
86
92
|
--input-bg: rgb(33, 37, 41);
|
|
87
93
|
--page-bg: rgb(36, 37, 40);
|
|
@@ -11,11 +11,20 @@
|
|
|
11
11
|
import Link from '../ui/Link.svelte';
|
|
12
12
|
import Logo from '../ui/Logo.svelte';
|
|
13
13
|
import type { Snippet } from 'svelte';
|
|
14
|
+
import { themeState } from '../theme.svelte.js';
|
|
14
15
|
|
|
15
|
-
export type NavbarVariant = 'default' | 'primary';
|
|
16
|
+
export type NavbarVariant = 'default' | 'primary' | 'site';
|
|
16
17
|
|
|
17
18
|
export interface NavbarProps {
|
|
18
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Visual variant.
|
|
21
|
+
* - `primary` uses the primary brand colour as the background across all themes.
|
|
22
|
+
* - `site` is a marketing-site header: logo on the left, links pushed to the right with an
|
|
23
|
+
* accent underline on the `active` item. Below 768px the links collapse and the drawer
|
|
24
|
+
* button (enable with `showDrawerButton`) is shown instead. While a site header is on the
|
|
25
|
+
* page, `--header-height` is raised to 68px (see app.css), so `--page-content-height`
|
|
26
|
+
* follows automatically.
|
|
27
|
+
*/
|
|
19
28
|
variant?: NavbarVariant;
|
|
20
29
|
/** App navigation link displayed as a header */
|
|
21
30
|
appNav?: Hyperlink;
|
|
@@ -67,15 +76,25 @@
|
|
|
67
76
|
}: NavbarProps = $props();
|
|
68
77
|
|
|
69
78
|
const isPrimary = $derived(variant === 'primary');
|
|
79
|
+
const isSite = $derived(variant === 'site');
|
|
80
|
+
// Site variant: the drawer button is the only nav control on mobile, so keep it legible on dark
|
|
81
|
+
const drawerButtonVariant = $derived(
|
|
82
|
+
isPrimary || (isSite && themeState.isDarkMode) ? 'accent-subtle' : 'secondary-subtle'
|
|
83
|
+
);
|
|
70
84
|
</script>
|
|
71
85
|
|
|
72
|
-
<header class={isPrimary ? 'primary' : ''}>
|
|
86
|
+
<header class="pui-navbar {isPrimary ? 'primary' : isSite ? 'site' : ''}">
|
|
73
87
|
<div class="inner-navbar {className} {inContainer ? 'container' : ''}">
|
|
88
|
+
{#if isSite && !noLogo}
|
|
89
|
+
<span class="site-logo">
|
|
90
|
+
<Logo href={logoHref} />
|
|
91
|
+
</span>
|
|
92
|
+
{/if}
|
|
74
93
|
{@render leftSection?.()}
|
|
75
94
|
{#if showDrawerButton && drawerButtonPosition === 'left'}
|
|
76
95
|
<ActionIcon
|
|
77
96
|
svg={drawerButtonIcon}
|
|
78
|
-
variant={
|
|
97
|
+
variant={drawerButtonVariant}
|
|
79
98
|
size="1.25rem"
|
|
80
99
|
onclick={onDrawerButtonClick}
|
|
81
100
|
class="drawer-btn drawer-btn-left"
|
|
@@ -122,7 +141,7 @@
|
|
|
122
141
|
darkVariant="accent-subtle"
|
|
123
142
|
lightVariant={isPrimary ? 'accent-subtle' : 'primary-subtle'}
|
|
124
143
|
/>
|
|
125
|
-
{#if !viewportState.isMobile && !noLogo}
|
|
144
|
+
{#if !isSite && !viewportState.isMobile && !noLogo}
|
|
126
145
|
{#if logoHref}
|
|
127
146
|
<Logo href={logoHref} fill={isPrimary ? 'white' : undefined} />
|
|
128
147
|
{:else}
|
|
@@ -132,7 +151,7 @@
|
|
|
132
151
|
{#if showDrawerButton && drawerButtonPosition === 'right'}
|
|
133
152
|
<ActionIcon
|
|
134
153
|
svg={drawerButtonIcon}
|
|
135
|
-
variant={
|
|
154
|
+
variant={drawerButtonVariant}
|
|
136
155
|
size="1.25rem"
|
|
137
156
|
onclick={onDrawerButtonClick}
|
|
138
157
|
class="drawer-btn drawer-btn-right"
|
|
@@ -197,6 +216,112 @@
|
|
|
197
216
|
color: var(--ui-accent);
|
|
198
217
|
}
|
|
199
218
|
|
|
219
|
+
/* Site variant: marketing-site header. Logo on the left, links on the right with a 2px
|
|
220
|
+
underline that lights up (accent) on the active item. */
|
|
221
|
+
header.site .inner-navbar .links-container {
|
|
222
|
+
gap: 1.5rem;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
header.site .inner-navbar .links-container > :global(a),
|
|
226
|
+
header.site
|
|
227
|
+
.inner-navbar
|
|
228
|
+
.links-container
|
|
229
|
+
> :global(.navbar-parent)
|
|
230
|
+
> :global(.navbar-parent-trigger) {
|
|
231
|
+
font-weight: 500;
|
|
232
|
+
padding: 0.35rem 0;
|
|
233
|
+
border-bottom: 2px solid transparent;
|
|
234
|
+
transition:
|
|
235
|
+
color 200ms,
|
|
236
|
+
border-color 200ms;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
header.site .inner-navbar .links-container > :global(a:hover),
|
|
240
|
+
header.site
|
|
241
|
+
.inner-navbar
|
|
242
|
+
.links-container
|
|
243
|
+
> :global(.navbar-parent)
|
|
244
|
+
> :global(.navbar-parent-trigger:hover) {
|
|
245
|
+
color: var(--ui-tertiary-dark);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
:global(.dark) header.site .inner-navbar .links-container > :global(a:hover),
|
|
249
|
+
:global(.dark)
|
|
250
|
+
header.site
|
|
251
|
+
.inner-navbar
|
|
252
|
+
.links-container
|
|
253
|
+
> :global(.navbar-parent)
|
|
254
|
+
> :global(.navbar-parent-trigger:hover) {
|
|
255
|
+
color: var(--ui-accent);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
header.site .inner-navbar .links-container > :global(a.active),
|
|
259
|
+
header.site
|
|
260
|
+
.inner-navbar
|
|
261
|
+
.links-container
|
|
262
|
+
> :global(.navbar-parent)
|
|
263
|
+
> :global(.navbar-parent-trigger.active) {
|
|
264
|
+
border-bottom-color: var(--ui-accent);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* Brand logo sits on the left and is sized by height so any aspect ratio works */
|
|
268
|
+
header.site .site-logo {
|
|
269
|
+
display: inline-flex;
|
|
270
|
+
align-items: center;
|
|
271
|
+
flex-shrink: 0;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
header.site .site-logo :global(.logo),
|
|
275
|
+
header.site .site-logo :global(.pui-icon) {
|
|
276
|
+
display: inline-flex;
|
|
277
|
+
align-items: center;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
header.site .site-logo :global(.pui-icon svg) {
|
|
281
|
+
height: 40px;
|
|
282
|
+
width: auto;
|
|
283
|
+
display: block;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* Primary links are right-aligned in the site variant (unless a growing .middle exists) */
|
|
287
|
+
header.site .primary.links-container {
|
|
288
|
+
margin-left: auto;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
header.site .primary.links-container ~ .secondary,
|
|
292
|
+
header.site .primary.links-container ~ .right,
|
|
293
|
+
header.site .primary.links-container ~ :global(.theme-switcher) {
|
|
294
|
+
margin-left: 0;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@media (max-width: 767px) {
|
|
298
|
+
/* Links collapse into the drawer; the drawer button takes their place */
|
|
299
|
+
header.site .inner-navbar .links-container {
|
|
300
|
+
display: none;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
header.site .site-logo :global(.pui-icon svg) {
|
|
304
|
+
height: 34px;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/* The hidden links containers still count as siblings, so re-apply the auto margin
|
|
308
|
+
to the first visible right-hand element */
|
|
309
|
+
header.site .inner-navbar > .right,
|
|
310
|
+
header.site .inner-navbar > :global(.theme-switcher) {
|
|
311
|
+
margin-left: auto;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
header.site .inner-navbar > .right ~ :global(.theme-switcher) {
|
|
315
|
+
margin-left: 0;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
@media (min-width: 768px) {
|
|
320
|
+
header.site .inner-navbar :global(.drawer-btn) {
|
|
321
|
+
display: none;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
200
325
|
.inner-navbar {
|
|
201
326
|
display: flex;
|
|
202
327
|
align-items: center;
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import type { NavbarItem } from '../models/NavbarItem.js';
|
|
2
2
|
import { type Hyperlink } from '../index.js';
|
|
3
3
|
import type { Snippet } from 'svelte';
|
|
4
|
-
export type NavbarVariant = 'default' | 'primary';
|
|
4
|
+
export type NavbarVariant = 'default' | 'primary' | 'site';
|
|
5
5
|
export interface NavbarProps {
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Visual variant.
|
|
8
|
+
* - `primary` uses the primary brand colour as the background across all themes.
|
|
9
|
+
* - `site` is a marketing-site header: logo on the left, links pushed to the right with an
|
|
10
|
+
* accent underline on the `active` item. Below 768px the links collapse and the drawer
|
|
11
|
+
* button (enable with `showDrawerButton`) is shown instead. While a site header is on the
|
|
12
|
+
* page, `--header-height` is raised to 68px (see app.css), so `--page-content-height`
|
|
13
|
+
* follows automatically.
|
|
14
|
+
*/
|
|
7
15
|
variant?: NavbarVariant;
|
|
8
16
|
/** App navigation link displayed as a header */
|
|
9
17
|
appNav?: Hyperlink;
|
|
@@ -25,7 +25,12 @@
|
|
|
25
25
|
{@const Component = item.component}
|
|
26
26
|
<Component {...item.componentProps} />
|
|
27
27
|
{:else if 'href' in item}
|
|
28
|
-
<Link
|
|
28
|
+
<Link
|
|
29
|
+
href={item.href}
|
|
30
|
+
onclick={item.onclick}
|
|
31
|
+
class="navbar-item {item.class ?? ''} {item.active ? 'active' : ''}"
|
|
32
|
+
ariaCurrent={item.active ? 'page' : undefined}
|
|
33
|
+
>
|
|
29
34
|
{#if item.iconSvg}
|
|
30
35
|
<Icon svg={item.iconSvg} size="1.1em" class="navbar-item-icon" />
|
|
31
36
|
{/if}
|
|
@@ -35,7 +40,7 @@
|
|
|
35
40
|
<div class="navbar-parent" use:clickOutside={close}>
|
|
36
41
|
<button
|
|
37
42
|
type="button"
|
|
38
|
-
class="navbar-item navbar-parent-trigger {item.class ?? ''}"
|
|
43
|
+
class="navbar-item navbar-parent-trigger {item.class ?? ''} {item.active ? 'active' : ''}"
|
|
39
44
|
aria-expanded={open}
|
|
40
45
|
aria-haspopup="true"
|
|
41
46
|
onclick={(e) => {
|
|
@@ -3,6 +3,8 @@ type BaseNavbarItem = {
|
|
|
3
3
|
alwaysShow?: boolean;
|
|
4
4
|
showInOnly?: undefined | 'Navbar' | 'Drawer';
|
|
5
5
|
class?: string;
|
|
6
|
+
/** Marks the item as the current page. Adds an `active` class (and `aria-current="page"` on links); the `site` navbar variant underlines it. */
|
|
7
|
+
active?: boolean;
|
|
6
8
|
/** Custom click handler. For link items, call event.preventDefault() to override the default href navigation. */
|
|
7
9
|
onclick?: (event: MouseEvent) => void;
|
|
8
10
|
};
|
package/dist/ui/Link.svelte
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
children?: Snippet;
|
|
17
17
|
/** Click event handler */
|
|
18
18
|
onclick?: (event: MouseEvent) => void;
|
|
19
|
+
/** Value for the `aria-current` attribute (e.g. `'page'` for the current page) */
|
|
20
|
+
ariaCurrent?: 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | undefined;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
let {
|
|
@@ -24,7 +26,8 @@
|
|
|
24
26
|
target = undefined,
|
|
25
27
|
class: classes = '',
|
|
26
28
|
children,
|
|
27
|
-
onclick
|
|
29
|
+
onclick,
|
|
30
|
+
ariaCurrent = undefined
|
|
28
31
|
}: LinkProps = $props();
|
|
29
32
|
</script>
|
|
30
33
|
|
|
@@ -33,6 +36,7 @@
|
|
|
33
36
|
class={classes}
|
|
34
37
|
href={href?.startsWith('/') ? resolve(href as any) : href}
|
|
35
38
|
{rel}
|
|
39
|
+
aria-current={ariaCurrent}
|
|
36
40
|
onclick={(e) => interceptLinkClick(e, { href, target, onclick })}
|
|
37
41
|
data-sveltekit-preload-data="hover"
|
|
38
42
|
>
|
package/dist/ui/Link.svelte.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface LinkProps {
|
|
|
12
12
|
children?: Snippet;
|
|
13
13
|
/** Click event handler */
|
|
14
14
|
onclick?: (event: MouseEvent) => void;
|
|
15
|
+
/** Value for the `aria-current` attribute (e.g. `'page'` for the current page) */
|
|
16
|
+
ariaCurrent?: 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | undefined;
|
|
15
17
|
}
|
|
16
18
|
declare const Link: import("svelte").Component<LinkProps, {}, "">;
|
|
17
19
|
type Link = ReturnType<typeof Link>;
|
|
@@ -84,6 +84,6 @@ export interface TableProps<T = any> {
|
|
|
84
84
|
* Use with `onsort` when the server returns pre-sorted rows. */
|
|
85
85
|
manualSort?: boolean;
|
|
86
86
|
}
|
|
87
|
-
declare const Table: import("svelte").Component<TableProps<any>, {}, "
|
|
87
|
+
declare const Table: import("svelte").Component<TableProps<any>, {}, "page" | "selectedRows" | "expandedKeys" | "sortKey" | "sortDirection">;
|
|
88
88
|
type Table = ReturnType<typeof Table>;
|
|
89
89
|
export default Table;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@placeholderco/placeholder-ui",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.4",
|
|
4
4
|
"description": "A modern, customizable Svelte 5 UI component library with comprehensive theming support. Re-brand it with a single config object.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Placeholderco",
|