lapikit 0.6.0 → 0.6.1
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/@types/props.d.ts +1 -0
- package/dist/animations/ripple.d.ts +1 -1
- package/dist/animations/ripple.js +36 -23
- package/dist/components/app/app.svelte +122 -8
- package/dist/components/card/card.svelte +49 -96
- package/dist/components/card/card.types.d.ts +22 -2
- package/dist/components/card/modules/card-actions.svelte +53 -0
- package/dist/components/card/modules/card-actions.svelte.d.ts +4 -0
- package/dist/components/card/modules/card-container.svelte +51 -0
- package/dist/components/card/modules/card-container.svelte.d.ts +4 -0
- package/dist/components/card/modules/card-content.svelte +56 -0
- package/dist/components/card/modules/card-content.svelte.d.ts +4 -0
- package/dist/components/card/modules/card-media.svelte +52 -0
- package/dist/components/card/modules/card-media.svelte.d.ts +4 -0
- package/dist/components/card/modules/card-title.svelte +56 -0
- package/dist/components/card/modules/card-title.svelte.d.ts +4 -0
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.js +5 -0
- package/dist/constants.js +6 -1
- package/dist/utils/components.d.ts +1 -0
- package/dist/utils/components.js +16 -0
- package/package.json +1 -1
package/dist/@types/props.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export type PropValue = string | boolean | number | null | undefined;
|
|
2
2
|
export type SizeType = 'default' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
3
3
|
export type RoundedType = 0 | '0' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
4
|
+
export type DensityType = 'none' | 'compact' | 'default' | 'comfortable';
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
const triggerEvents = ['pointerdown', 'touchstart', 'keydown'];
|
|
2
2
|
const cancelEvents = ['mouseleave', 'dragleave', 'touchmove', 'touchcancel', 'pointerup', 'keyup'];
|
|
3
|
+
function resolveDuration(options) {
|
|
4
|
+
return options.duration && options.duration > 0 ? options.duration : undefined;
|
|
5
|
+
}
|
|
6
|
+
function getCoords(e) {
|
|
7
|
+
if (window.TouchEvent && e instanceof TouchEvent) {
|
|
8
|
+
return { x: e.touches[0].clientX, y: e.touches[0].clientY };
|
|
9
|
+
}
|
|
10
|
+
return { x: e.clientX, y: e.clientY };
|
|
11
|
+
}
|
|
3
12
|
export function ripple(el, options = {}) {
|
|
4
13
|
const rippleContainer = document.createElement('div');
|
|
14
|
+
const activeRipples = new Set();
|
|
5
15
|
addClasses();
|
|
6
16
|
setOptions(options);
|
|
7
17
|
function isAriaDisabled() {
|
|
@@ -19,24 +29,22 @@ export function ripple(el, options = {}) {
|
|
|
19
29
|
rippleContainer.classList.add('kit-ripple--center');
|
|
20
30
|
}
|
|
21
31
|
}
|
|
22
|
-
function setOptions(
|
|
23
|
-
if (
|
|
32
|
+
function setOptions(newOptions) {
|
|
33
|
+
if (newOptions.disabled || isAriaDisabled()) {
|
|
24
34
|
rippleContainer.remove();
|
|
25
35
|
}
|
|
26
36
|
else {
|
|
27
37
|
el.appendChild(rippleContainer);
|
|
28
38
|
}
|
|
29
|
-
if (
|
|
30
|
-
|
|
39
|
+
if (newOptions.component) {
|
|
40
|
+
rippleContainer.style.setProperty('--system-ripple-radius', `var(--kit-${newOptions.component}-radius)`);
|
|
31
41
|
}
|
|
32
|
-
if (
|
|
33
|
-
rippleContainer.style.setProperty('--system-ripple-
|
|
42
|
+
if (newOptions.color) {
|
|
43
|
+
rippleContainer.style.setProperty('--system-ripple-color', newOptions.color);
|
|
34
44
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (options.duration) {
|
|
39
|
-
rippleContainer.style.setProperty('--system-animation-ripple-duration', `${options.duration}ms`);
|
|
45
|
+
const duration = resolveDuration(newOptions);
|
|
46
|
+
if (duration) {
|
|
47
|
+
rippleContainer.style.setProperty('--system-animation-ripple-duration', `${duration}ms`);
|
|
40
48
|
}
|
|
41
49
|
}
|
|
42
50
|
function createRipple(e, center) {
|
|
@@ -54,12 +62,7 @@ export function ripple(el, options = {}) {
|
|
|
54
62
|
}
|
|
55
63
|
addClasses(center);
|
|
56
64
|
const rect = el.getBoundingClientRect();
|
|
57
|
-
const clientX
|
|
58
|
-
? e.touches[0].clientX
|
|
59
|
-
: e.clientX;
|
|
60
|
-
const clientY = window.TouchEvent && e instanceof TouchEvent
|
|
61
|
-
? e.touches[0].clientY
|
|
62
|
-
: e.clientY;
|
|
65
|
+
const { x: clientX, y: clientY } = getCoords(e);
|
|
63
66
|
const x = clientX - rect.left > el.offsetWidth / 2 ? 0 : el.offsetWidth;
|
|
64
67
|
const y = clientY - rect.top > el.offsetHeight / 2 ? 0 : el.offsetHeight;
|
|
65
68
|
const radius = Math.hypot(x - (clientX - rect.left), y - (clientY - rect.top));
|
|
@@ -69,16 +72,23 @@ export function ripple(el, options = {}) {
|
|
|
69
72
|
ripple.style.top = `${clientY - rect.top - radius}px`;
|
|
70
73
|
ripple.style.width = ripple.style.height = `${radius * 2}px`;
|
|
71
74
|
rippleContainer.appendChild(ripple);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
let timeoutId;
|
|
76
|
+
function cleanup() {
|
|
77
|
+
if (timeoutId) {
|
|
78
|
+
clearTimeout(timeoutId);
|
|
75
79
|
}
|
|
80
|
+
cancelEvents.forEach((event) => el.removeEventListener(event, removeRipple));
|
|
81
|
+
ripple.remove();
|
|
82
|
+
}
|
|
83
|
+
function removeRipple() {
|
|
76
84
|
ripple.style.opacity = '0';
|
|
77
|
-
setTimeout(() => {
|
|
78
|
-
ripple.remove();
|
|
79
|
-
}, options.duration || 1000);
|
|
80
85
|
cancelEvents.forEach((event) => el.removeEventListener(event, removeRipple));
|
|
86
|
+
timeoutId = setTimeout(() => {
|
|
87
|
+
ripple.remove();
|
|
88
|
+
activeRipples.delete(cleanup);
|
|
89
|
+
}, resolveDuration(options) || 1000);
|
|
81
90
|
}
|
|
91
|
+
activeRipples.add(cleanup);
|
|
82
92
|
cancelEvents.forEach((event) => el.addEventListener(event, removeRipple, { passive: true }));
|
|
83
93
|
}
|
|
84
94
|
triggerEvents.forEach((event) => el.addEventListener(event, createRipple, { passive: event === 'touchstart' }));
|
|
@@ -87,6 +97,9 @@ export function ripple(el, options = {}) {
|
|
|
87
97
|
triggerEvents.forEach((event) => {
|
|
88
98
|
el.removeEventListener(event, createRipple);
|
|
89
99
|
});
|
|
100
|
+
activeRipples.forEach((cleanup) => cleanup());
|
|
101
|
+
activeRipples.clear();
|
|
102
|
+
rippleContainer.remove();
|
|
90
103
|
},
|
|
91
104
|
update(newOptions) {
|
|
92
105
|
options = newOptions;
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
.kit-application {
|
|
11
11
|
color-scheme: light;
|
|
12
12
|
|
|
13
|
+
/* Hue vars — used by components for dynamic semantic color calculations */
|
|
13
14
|
--kit-h-neutral: 220;
|
|
14
15
|
--kit-h-success: 145;
|
|
15
16
|
--kit-h-warning: 35;
|
|
@@ -28,19 +29,135 @@
|
|
|
28
29
|
|
|
29
30
|
--kit-accent: hsl(220 90% 56%);
|
|
30
31
|
|
|
32
|
+
--kit-focus: hsl(35, 90%, 56%);
|
|
33
|
+
|
|
34
|
+
/* Layout */
|
|
31
35
|
--kit-radius-1: 8px;
|
|
32
36
|
--kit-radius-2: 12px;
|
|
33
37
|
--kit-space-1: 6px;
|
|
34
38
|
--kit-space-2: 10px;
|
|
35
39
|
--kit-space-3: 14px;
|
|
36
|
-
|
|
37
|
-
--kit-focus: hsl(35, 90%, 56%);
|
|
38
|
-
|
|
39
40
|
--kit-disabled-opacity: 0.55;
|
|
40
|
-
|
|
41
41
|
--kit-font:
|
|
42
42
|
ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
|
43
43
|
'Segoe UI Symbol', 'Noto Color Emoji';
|
|
44
|
+
|
|
45
|
+
/* ─── Color tokens (light mode defaults) ─────────────────────────── */
|
|
46
|
+
|
|
47
|
+
/* Backgrounds */
|
|
48
|
+
--kit-color-bg: hsl(0 0% 100%);
|
|
49
|
+
--kit-color-bg-secondary: hsl(240 15% 96.5%);
|
|
50
|
+
--kit-color-bg-tertiary: hsl(240 10% 93%);
|
|
51
|
+
|
|
52
|
+
/* Surfaces — elevated layers (cards, sheets, popovers) */
|
|
53
|
+
--kit-color-surface: hsl(0 0% 100%);
|
|
54
|
+
--kit-color-surface-raised: hsl(0 0% 100%);
|
|
55
|
+
|
|
56
|
+
/* Labels — text hierarchy */
|
|
57
|
+
--kit-color-label: hsl(222 20% 10%);
|
|
58
|
+
--kit-color-label-secondary: hsl(220 10% 40%);
|
|
59
|
+
--kit-color-label-tertiary: hsl(220 8% 58%);
|
|
60
|
+
--kit-color-label-quaternary: hsl(220 5% 76%);
|
|
61
|
+
|
|
62
|
+
/* Fills — control & input backgrounds */
|
|
63
|
+
--kit-color-fill: hsl(240 4% 91%);
|
|
64
|
+
--kit-color-fill-secondary: hsl(240 8% 93%);
|
|
65
|
+
--kit-color-fill-tertiary: hsl(240 11% 95%);
|
|
66
|
+
|
|
67
|
+
/* Separator */
|
|
68
|
+
--kit-color-separator: hsl(220 16% 88%);
|
|
69
|
+
|
|
70
|
+
/* Semantic */
|
|
71
|
+
--kit-color-accent: hsl(220 90% 56%);
|
|
72
|
+
--kit-color-success: hsl(145 50% 38%);
|
|
73
|
+
--kit-color-warning: hsl(35 80% 45%);
|
|
74
|
+
--kit-color-danger: hsl(5 65% 48%);
|
|
75
|
+
--kit-color-info: hsl(205 60% 42%);
|
|
76
|
+
--kit-color-focus: hsl(35 90% 56%);
|
|
77
|
+
|
|
78
|
+
/* ─── Backward-compat aliases (removed once components are updated) ─ */
|
|
79
|
+
/* --kit-bg: var(--kit-color-bg);
|
|
80
|
+
--kit-fg: var(--kit-color-label);
|
|
81
|
+
--kit-muted: var(--kit-color-label-tertiary);
|
|
82
|
+
--kit-surface-1: var(--kit-color-surface);
|
|
83
|
+
--kit-surface-2: var(--kit-color-bg-secondary);
|
|
84
|
+
--kit-surface-3: var(--kit-color-bg-tertiary);
|
|
85
|
+
--kit-border: var(--kit-color-separator);
|
|
86
|
+
--kit-accent: var(--kit-color-accent);
|
|
87
|
+
--kit-focus: var(--kit-color-focus); */
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.kit-application {
|
|
91
|
+
--kit-shape-none: 0;
|
|
92
|
+
--kit-shape-xs: 4px;
|
|
93
|
+
--kit-shape-sm: 6px;
|
|
94
|
+
--kit-shape-md: 10px;
|
|
95
|
+
--kit-shape-lg: 14px;
|
|
96
|
+
--kit-shape-xl: 18px;
|
|
97
|
+
|
|
98
|
+
--kit-space-compact: 6px;
|
|
99
|
+
--kit-space-default: 10px;
|
|
100
|
+
--kit-space-comfortable: 14px;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Light — explicit override (same values as defaults, forces color-scheme) */
|
|
104
|
+
.light {
|
|
105
|
+
color-scheme: light;
|
|
106
|
+
|
|
107
|
+
--kit-color-bg: hsl(0 0% 100%);
|
|
108
|
+
--kit-color-bg-secondary: hsl(240 15% 96.5%);
|
|
109
|
+
--kit-color-bg-tertiary: hsl(240 10% 93%);
|
|
110
|
+
|
|
111
|
+
--kit-color-surface: hsl(0 0% 100%);
|
|
112
|
+
--kit-color-surface-raised: hsl(0 0% 100%);
|
|
113
|
+
|
|
114
|
+
--kit-color-label: hsl(222 20% 10%);
|
|
115
|
+
--kit-color-label-secondary: hsl(220 10% 40%);
|
|
116
|
+
--kit-color-label-tertiary: hsl(220 8% 58%);
|
|
117
|
+
--kit-color-label-quaternary: hsl(220 5% 76%);
|
|
118
|
+
|
|
119
|
+
--kit-color-fill: hsl(240 4% 91%);
|
|
120
|
+
--kit-color-fill-secondary: hsl(240 8% 93%);
|
|
121
|
+
--kit-color-fill-tertiary: hsl(240 11% 95%);
|
|
122
|
+
|
|
123
|
+
--kit-color-separator: hsl(220 16% 88%);
|
|
124
|
+
|
|
125
|
+
--kit-color-accent: hsl(220 90% 56%);
|
|
126
|
+
--kit-color-success: hsl(145 50% 38%);
|
|
127
|
+
--kit-color-warning: hsl(35 80% 45%);
|
|
128
|
+
--kit-color-danger: hsl(5 65% 48%);
|
|
129
|
+
--kit-color-info: hsl(205 60% 42%);
|
|
130
|
+
--kit-color-focus: hsl(35 90% 56%);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Dark */
|
|
134
|
+
.dark {
|
|
135
|
+
color-scheme: dark;
|
|
136
|
+
|
|
137
|
+
--kit-color-bg: hsl(240 3% 11%);
|
|
138
|
+
--kit-color-bg-secondary: hsl(240 2% 17.5%);
|
|
139
|
+
--kit-color-bg-tertiary: hsl(240 1.5% 23%);
|
|
140
|
+
|
|
141
|
+
--kit-color-surface: hsl(240 2% 19%);
|
|
142
|
+
--kit-color-surface-raised: hsl(240 1.5% 25%);
|
|
143
|
+
|
|
144
|
+
--kit-color-label: hsl(0 0% 100%);
|
|
145
|
+
--kit-color-label-secondary: hsl(240 3% 56%);
|
|
146
|
+
--kit-color-label-tertiary: hsl(240 2.5% 38%);
|
|
147
|
+
--kit-color-label-quaternary: hsl(240 1.5% 25%);
|
|
148
|
+
|
|
149
|
+
--kit-color-fill: hsl(240 2.5% 28%);
|
|
150
|
+
--kit-color-fill-secondary: hsl(240 2.5% 24%);
|
|
151
|
+
--kit-color-fill-tertiary: hsl(240 2% 20%);
|
|
152
|
+
|
|
153
|
+
--kit-color-separator: hsl(240 2% 26%);
|
|
154
|
+
|
|
155
|
+
--kit-color-accent: hsl(220 85% 65%);
|
|
156
|
+
--kit-color-success: hsl(145 50% 50%);
|
|
157
|
+
--kit-color-warning: hsl(35 85% 58%);
|
|
158
|
+
--kit-color-danger: hsl(5 70% 60%);
|
|
159
|
+
--kit-color-info: hsl(205 65% 58%);
|
|
160
|
+
--kit-color-focus: hsl(35 90% 62%);
|
|
44
161
|
}
|
|
45
162
|
|
|
46
163
|
:global(.outline) {
|
|
@@ -59,13 +176,10 @@
|
|
|
59
176
|
position: absolute;
|
|
60
177
|
border-radius: 50%;
|
|
61
178
|
pointer-events: none;
|
|
62
|
-
-webkit-transition: 0.6s;
|
|
63
179
|
transition: 0.6s;
|
|
64
|
-
-webkit-animation: animation-l-ripple var(--system-animation-ripple-duration, 0.4s)
|
|
65
|
-
cubic-bezier(0.4, 0, 0.2, 1);
|
|
66
180
|
animation: animation-l-ripple var(--system-animation-ripple-duration, 0.4s)
|
|
67
181
|
cubic-bezier(0.4, 0, 0.2, 1);
|
|
68
|
-
border-radius:
|
|
182
|
+
border-radius: 50%;
|
|
69
183
|
}
|
|
70
184
|
|
|
71
185
|
:global(.kit-ripple--center) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { useClassName, useStyles } from '../../utils';
|
|
2
|
+
import { useClassName, useIsInteractive, useStyles } from '../../utils';
|
|
3
3
|
import { makeComponentProps } from '../../html-mapped';
|
|
4
4
|
import { ripple } from '../../animations';
|
|
5
5
|
import type { CardProps } from './card.types.js';
|
|
@@ -21,45 +21,20 @@
|
|
|
21
21
|
active = false,
|
|
22
22
|
disabled = false,
|
|
23
23
|
noRipple,
|
|
24
|
+
color,
|
|
25
|
+
background,
|
|
24
26
|
...rest
|
|
25
27
|
}: CardProps = $props();
|
|
26
28
|
|
|
27
|
-
let
|
|
28
|
-
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
let safeDensity = $derived(
|
|
32
|
-
density === 'compact' || density === 'default' || density === 'comfortable'
|
|
33
|
-
? density
|
|
34
|
-
: 'default'
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
let safeIs = $derived(
|
|
38
|
-
is === 'div' ||
|
|
39
|
-
is === 'article' ||
|
|
40
|
-
is === 'section' ||
|
|
41
|
-
is === 'aside' ||
|
|
42
|
-
is === 'a' ||
|
|
43
|
-
is === 'button'
|
|
44
|
-
? is
|
|
45
|
-
: 'article'
|
|
46
|
-
);
|
|
47
|
-
let tag = $derived((href && 'a') || safeIs || 'article');
|
|
48
|
-
let hasEventHandler = $derived(
|
|
49
|
-
typeof rest.onclick === 'function' ||
|
|
50
|
-
typeof rest.onpointerdown === 'function' ||
|
|
51
|
-
typeof rest.onkeydown === 'function'
|
|
52
|
-
);
|
|
53
|
-
let isInteractive = $derived(interactive || tag === 'a' || tag === 'button' || hasEventHandler);
|
|
29
|
+
let tag = $derived((href && 'a') || is);
|
|
30
|
+
let isInteractive = $derived(useIsInteractive(rest, tag, ['a', 'button'], interactive));
|
|
54
31
|
let isDisabled = $derived(!!disabled);
|
|
55
32
|
let resolvedHref = $derived(tag === 'a' && !isDisabled ? href : undefined);
|
|
56
33
|
let resolvedType = $derived(tag === 'button' ? (type ?? 'button') : undefined);
|
|
57
34
|
let resolvedDisabled = $derived(tag === 'button' ? isDisabled : undefined);
|
|
58
35
|
let resolvedTabIndex = $derived(tag === 'a' && isDisabled ? -1 : undefined);
|
|
59
36
|
|
|
60
|
-
let { classProps, styleProps, restProps } = $derived(
|
|
61
|
-
makeComponentProps(rest as Record<string, unknown>)
|
|
62
|
-
);
|
|
37
|
+
let { classProps, styleProps, restProps } = $derived(makeComponentProps(rest));
|
|
63
38
|
|
|
64
39
|
let componentClass = $derived(
|
|
65
40
|
useClassName({
|
|
@@ -88,8 +63,8 @@
|
|
|
88
63
|
type={resolvedType}
|
|
89
64
|
disabled={resolvedDisabled}
|
|
90
65
|
tabindex={resolvedTabIndex}
|
|
91
|
-
data-variant={
|
|
92
|
-
data-density={
|
|
66
|
+
data-variant={variant}
|
|
67
|
+
data-density={density}
|
|
93
68
|
data-rounded={rounded}
|
|
94
69
|
data-interactive={isInteractive}
|
|
95
70
|
data-active={active}
|
|
@@ -99,9 +74,11 @@
|
|
|
99
74
|
component: 'card',
|
|
100
75
|
disabled: noRipple || disabled || !isInteractive
|
|
101
76
|
}}
|
|
77
|
+
style:--kit-card-fg={color && `var(--kit-color-${color})`}
|
|
78
|
+
style:--kit-card-bg={background && `var(--kit-color-${background})`}
|
|
102
79
|
{...restProps}
|
|
103
80
|
>
|
|
104
|
-
{#if
|
|
81
|
+
{#if variant === 'outline'}
|
|
105
82
|
<span class="outline"></span>
|
|
106
83
|
{/if}
|
|
107
84
|
|
|
@@ -110,25 +87,15 @@
|
|
|
110
87
|
|
|
111
88
|
<style>
|
|
112
89
|
.kit-card {
|
|
113
|
-
--kit-card-bg: var(--kit-surface-2);
|
|
114
|
-
--kit-card-fg: var(--kit-fg);
|
|
115
|
-
--kit-card-bd: var(--kit-border);
|
|
116
|
-
--kit-card-radius: var(--kit-radius-2);
|
|
117
|
-
--kit-card-padding-compact: var(--kit-space-1);
|
|
118
|
-
--kit-card-padding-default: var(--kit-space-2);
|
|
119
|
-
--kit-card-padding-comfortable: var(--kit-space-3);
|
|
120
|
-
--kit-card-hover-bg: color-mix(in oklab, var(--kit-card-bg), var(--kit-card-fg) 6%);
|
|
121
|
-
--kit-card-active-bg: color-mix(in oklab, var(--kit-card-bg), var(--kit-card-fg) 10%);
|
|
122
|
-
|
|
123
90
|
display: flex;
|
|
91
|
+
position: relative;
|
|
124
92
|
flex-direction: column;
|
|
125
|
-
gap: var(--kit-
|
|
93
|
+
gap: var(--kit-card-gap);
|
|
126
94
|
background: var(--kit-card-bg);
|
|
127
95
|
color: var(--kit-card-fg);
|
|
128
|
-
border: 0;
|
|
129
96
|
border-radius: var(--kit-card-radius);
|
|
97
|
+
border: 0;
|
|
130
98
|
box-sizing: border-box;
|
|
131
|
-
position: relative;
|
|
132
99
|
transition:
|
|
133
100
|
background 140ms ease,
|
|
134
101
|
color 140ms ease,
|
|
@@ -148,53 +115,72 @@
|
|
|
148
115
|
text-align: inherit;
|
|
149
116
|
}
|
|
150
117
|
|
|
118
|
+
/**
|
|
119
|
+
* rounded
|
|
120
|
+
* @link ...
|
|
121
|
+
*/
|
|
151
122
|
.kit-card[data-rounded='0'] {
|
|
152
|
-
--kit-card-radius:
|
|
123
|
+
--kit-card-radius: var(--kit-shape-none);
|
|
153
124
|
}
|
|
154
125
|
.kit-card[data-rounded='xs'] {
|
|
155
|
-
--kit-card-radius:
|
|
126
|
+
--kit-card-radius: var(--kit-shape-xs);
|
|
156
127
|
}
|
|
157
128
|
.kit-card[data-rounded='sm'] {
|
|
158
|
-
--kit-card-radius:
|
|
129
|
+
--kit-card-radius: var(--kit-shape-sm);
|
|
159
130
|
}
|
|
160
131
|
.kit-card[data-rounded='md'] {
|
|
161
|
-
--kit-card-radius:
|
|
132
|
+
--kit-card-radius: var(--kit-shape-md);
|
|
162
133
|
}
|
|
163
134
|
.kit-card[data-rounded='lg'] {
|
|
164
|
-
--kit-card-radius:
|
|
135
|
+
--kit-card-radius: var(--kit-shape-lg);
|
|
165
136
|
}
|
|
166
137
|
.kit-card[data-rounded='xl'] {
|
|
167
|
-
--kit-card-radius:
|
|
138
|
+
--kit-card-radius: var(--kit-shape-xl);
|
|
168
139
|
}
|
|
169
140
|
|
|
141
|
+
/**
|
|
142
|
+
* density
|
|
143
|
+
* @link https://lapikit.dev/docs/components/card#density
|
|
144
|
+
*/
|
|
145
|
+
.kit-card[data-density='none'] {
|
|
146
|
+
padding: 0;
|
|
147
|
+
}
|
|
170
148
|
.kit-card[data-density='compact'] {
|
|
171
|
-
|
|
149
|
+
--kit-card-gap: var(--kit-space-compact);
|
|
150
|
+
padding: var(--kit-space-compact);
|
|
172
151
|
}
|
|
173
152
|
.kit-card[data-density='default'] {
|
|
174
|
-
|
|
153
|
+
--kit-card-gap: var(--kit-space-default);
|
|
154
|
+
padding: var(--kit-space-default);
|
|
175
155
|
}
|
|
176
156
|
.kit-card[data-density='comfortable'] {
|
|
177
|
-
|
|
157
|
+
--kit-card-gap: var(--kit-space-comfortable);
|
|
158
|
+
padding: var(--kit-space-comfortable);
|
|
178
159
|
}
|
|
179
160
|
|
|
161
|
+
/**
|
|
162
|
+
* variant
|
|
163
|
+
* @link https://lapikit.dev/docs/components/card#variants
|
|
164
|
+
*/
|
|
180
165
|
.kit-card[data-variant='filled'] {
|
|
181
|
-
--kit-card-bg: var(--kit-
|
|
182
|
-
--kit-card-fg:
|
|
166
|
+
--kit-card-bg: var(--kit-color-surface);
|
|
167
|
+
--kit-card-fg: var(--kit-color-label);
|
|
168
|
+
|
|
183
169
|
--kit-card-hover-bg: color-mix(in oklab, var(--kit-card-bg), black 10%);
|
|
184
170
|
--kit-card-active-bg: color-mix(in oklab, var(--kit-card-bg), black 16%);
|
|
185
171
|
}
|
|
186
|
-
|
|
187
172
|
.kit-card[data-variant='outline'] {
|
|
188
173
|
--kit-card-bg: transparent;
|
|
189
|
-
--kit-card-fg: var(--kit-
|
|
190
|
-
--kit-card-bd: var(--kit-
|
|
174
|
+
--kit-card-fg: var(--kit-color-label);
|
|
175
|
+
--kit-card-bd: var(--kit-card-fg);
|
|
176
|
+
|
|
191
177
|
--kit-card-hover-bg: color-mix(in oklab, var(--kit-card-fg), transparent 80%);
|
|
192
178
|
--kit-card-active-bg: color-mix(in oklab, var(--kit-card-fg), transparent 92%);
|
|
193
179
|
}
|
|
194
|
-
|
|
195
180
|
.kit-card[data-variant='text'] {
|
|
196
181
|
--kit-card-bg: transparent;
|
|
197
|
-
--kit-card-fg: var(--kit-
|
|
182
|
+
--kit-card-fg: var(--kit-color-label);
|
|
183
|
+
|
|
198
184
|
--kit-card-hover-bg: color-mix(in oklab, var(--kit-card-fg), transparent 80%);
|
|
199
185
|
--kit-card-active-bg: color-mix(in oklab, var(--kit-card-fg), transparent 92%);
|
|
200
186
|
}
|
|
@@ -238,37 +224,4 @@
|
|
|
238
224
|
.kit-card .outline {
|
|
239
225
|
--outline-color: var(--kit-card-bd);
|
|
240
226
|
}
|
|
241
|
-
|
|
242
|
-
.kit-card__media {
|
|
243
|
-
overflow: hidden;
|
|
244
|
-
border-radius: calc(var(--kit-card-radius) - 2px);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
.kit-card__header {
|
|
248
|
-
font-weight: 600;
|
|
249
|
-
line-height: 1.3;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
.kit-card__body {
|
|
253
|
-
display: block;
|
|
254
|
-
line-height: 1.45;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
.kit-card__footer {
|
|
258
|
-
display: flex;
|
|
259
|
-
align-items: center;
|
|
260
|
-
justify-content: space-between;
|
|
261
|
-
gap: var(--kit-space-2);
|
|
262
|
-
flex-wrap: wrap;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
.kit-card__footer-content {
|
|
266
|
-
color: var(--kit-muted);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
.kit-card__actions {
|
|
270
|
-
display: inline-flex;
|
|
271
|
-
align-items: center;
|
|
272
|
-
gap: var(--kit-space-1);
|
|
273
|
-
}
|
|
274
227
|
</style>
|
|
@@ -1,14 +1,34 @@
|
|
|
1
|
-
import type { Component, RoundedType } from '../../@types';
|
|
1
|
+
import type { Component, DensityType, RoundedType } from '../../@types';
|
|
2
2
|
export interface CardProps extends Component {
|
|
3
3
|
ref?: HTMLElement | null;
|
|
4
4
|
is?: 'div' | 'article' | 'section' | 'aside' | 'a' | 'button';
|
|
5
5
|
href?: string;
|
|
6
6
|
type?: 'button' | 'submit' | 'reset';
|
|
7
7
|
variant?: 'filled' | 'outline' | 'text';
|
|
8
|
-
density?:
|
|
8
|
+
density?: DensityType;
|
|
9
9
|
rounded?: RoundedType;
|
|
10
10
|
interactive?: boolean;
|
|
11
11
|
active?: boolean;
|
|
12
12
|
disabled?: boolean;
|
|
13
13
|
noRipple?: boolean;
|
|
14
14
|
}
|
|
15
|
+
export interface CardTitleProps extends Component {
|
|
16
|
+
ref?: HTMLElement | null;
|
|
17
|
+
is?: 'div' | 'p' | 'span' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
18
|
+
}
|
|
19
|
+
export interface CardContentProps extends Component {
|
|
20
|
+
ref?: HTMLElement | null;
|
|
21
|
+
is?: 'div' | 'p' | 'span' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
22
|
+
}
|
|
23
|
+
export interface CardMediaProps extends Component {
|
|
24
|
+
ref?: HTMLElement | null;
|
|
25
|
+
is?: 'div' | 'span';
|
|
26
|
+
}
|
|
27
|
+
export interface CardContainerProps extends Component {
|
|
28
|
+
ref?: HTMLElement | null;
|
|
29
|
+
is?: 'div' | 'span';
|
|
30
|
+
}
|
|
31
|
+
export interface CardActionsProps extends Component {
|
|
32
|
+
ref?: HTMLElement | null;
|
|
33
|
+
is?: 'div' | 'span';
|
|
34
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { CardActionsProps } from '../card.types';
|
|
3
|
+
import { makeComponentProps } from '../../../html-mapped';
|
|
4
|
+
import { useClassName, useStyles } from '../../../utils';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
ref = $bindable(),
|
|
8
|
+
is = 'div',
|
|
9
|
+
children,
|
|
10
|
+
class: className = '',
|
|
11
|
+
style: styleAttr = '',
|
|
12
|
+
's-class': sClass,
|
|
13
|
+
's-style': sStyle,
|
|
14
|
+
...rest
|
|
15
|
+
}: CardActionsProps = $props();
|
|
16
|
+
|
|
17
|
+
let { classProps, styleProps, restProps } = $derived(makeComponentProps(rest));
|
|
18
|
+
|
|
19
|
+
let componentClass = $derived(
|
|
20
|
+
useClassName({
|
|
21
|
+
baseClass: 'kit-card-actions',
|
|
22
|
+
className: `${className ?? ''}`.trim(),
|
|
23
|
+
sClass,
|
|
24
|
+
classProps
|
|
25
|
+
})
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
let componentStyle = $derived(
|
|
29
|
+
useStyles({
|
|
30
|
+
styleAttr,
|
|
31
|
+
sStyle,
|
|
32
|
+
styleProps
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<svelte:element
|
|
38
|
+
this={is}
|
|
39
|
+
bind:this={ref}
|
|
40
|
+
class={componentClass}
|
|
41
|
+
style={componentStyle}
|
|
42
|
+
{...restProps}
|
|
43
|
+
>
|
|
44
|
+
{@render children?.()}
|
|
45
|
+
</svelte:element>
|
|
46
|
+
|
|
47
|
+
<style>
|
|
48
|
+
.kit-card-actions {
|
|
49
|
+
display: inline-flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
gap: var(--kit-space-1);
|
|
52
|
+
}
|
|
53
|
+
</style>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { CardContainerProps } from '../card.types';
|
|
3
|
+
import { makeComponentProps } from '../../../html-mapped';
|
|
4
|
+
import { useClassName, useStyles } from '../../../utils';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
ref = $bindable(),
|
|
8
|
+
is = 'div',
|
|
9
|
+
children,
|
|
10
|
+
class: className = '',
|
|
11
|
+
style: styleAttr = '',
|
|
12
|
+
's-class': sClass,
|
|
13
|
+
's-style': sStyle,
|
|
14
|
+
...rest
|
|
15
|
+
}: CardContainerProps = $props();
|
|
16
|
+
|
|
17
|
+
let { classProps, styleProps, restProps } = $derived(makeComponentProps(rest));
|
|
18
|
+
|
|
19
|
+
let componentClass = $derived(
|
|
20
|
+
useClassName({
|
|
21
|
+
baseClass: 'kit-card-container',
|
|
22
|
+
className: `${className ?? ''}`.trim(),
|
|
23
|
+
sClass,
|
|
24
|
+
classProps
|
|
25
|
+
})
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
let componentStyle = $derived(
|
|
29
|
+
useStyles({
|
|
30
|
+
styleAttr,
|
|
31
|
+
sStyle,
|
|
32
|
+
styleProps
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<svelte:element
|
|
38
|
+
this={is}
|
|
39
|
+
bind:this={ref}
|
|
40
|
+
class={componentClass}
|
|
41
|
+
style={componentStyle}
|
|
42
|
+
{...restProps}
|
|
43
|
+
>
|
|
44
|
+
{@render children?.()}
|
|
45
|
+
</svelte:element>
|
|
46
|
+
|
|
47
|
+
<style>
|
|
48
|
+
.kit-card-container {
|
|
49
|
+
gap: var(--kit-card-gap);
|
|
50
|
+
}
|
|
51
|
+
</style>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { CardContentProps } from '../card.types';
|
|
3
|
+
import { makeComponentProps } from '../../../html-mapped';
|
|
4
|
+
import { useClassName, useStyles } from '../../../utils';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
ref = $bindable(),
|
|
8
|
+
is = 'div',
|
|
9
|
+
children,
|
|
10
|
+
class: className = '',
|
|
11
|
+
style: styleAttr = '',
|
|
12
|
+
's-class': sClass,
|
|
13
|
+
's-style': sStyle,
|
|
14
|
+
...rest
|
|
15
|
+
}: CardContentProps = $props();
|
|
16
|
+
|
|
17
|
+
let { classProps, styleProps, restProps } = $derived(makeComponentProps(rest));
|
|
18
|
+
|
|
19
|
+
let componentClass = $derived(
|
|
20
|
+
useClassName({
|
|
21
|
+
baseClass: 'kit-card-content',
|
|
22
|
+
className: `${className ?? ''}`.trim(),
|
|
23
|
+
sClass,
|
|
24
|
+
classProps
|
|
25
|
+
})
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
let componentStyle = $derived(
|
|
29
|
+
useStyles({
|
|
30
|
+
styleAttr,
|
|
31
|
+
sStyle,
|
|
32
|
+
styleProps
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<svelte:element
|
|
38
|
+
this={is}
|
|
39
|
+
bind:this={ref}
|
|
40
|
+
class={componentClass}
|
|
41
|
+
style={componentStyle}
|
|
42
|
+
{...restProps}
|
|
43
|
+
>
|
|
44
|
+
{@render children?.()}
|
|
45
|
+
</svelte:element>
|
|
46
|
+
|
|
47
|
+
<style>
|
|
48
|
+
.kit-card-content {
|
|
49
|
+
display: block;
|
|
50
|
+
line-height: 1.45;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.kit-card-content > :global(*) {
|
|
54
|
+
margin: 0;
|
|
55
|
+
}
|
|
56
|
+
</style>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { CardMediaProps } from '../card.types';
|
|
3
|
+
import { makeComponentProps } from '../../../html-mapped';
|
|
4
|
+
import { useClassName, useStyles } from '../../../utils';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
ref = $bindable(),
|
|
8
|
+
is = 'div',
|
|
9
|
+
children,
|
|
10
|
+
class: className = '',
|
|
11
|
+
style: styleAttr = '',
|
|
12
|
+
's-class': sClass,
|
|
13
|
+
's-style': sStyle,
|
|
14
|
+
...rest
|
|
15
|
+
}: CardMediaProps = $props();
|
|
16
|
+
|
|
17
|
+
let { classProps, styleProps, restProps } = $derived(makeComponentProps(rest));
|
|
18
|
+
|
|
19
|
+
let componentClass = $derived(
|
|
20
|
+
useClassName({
|
|
21
|
+
baseClass: 'kit-card-media',
|
|
22
|
+
className: `${className ?? ''}`.trim(),
|
|
23
|
+
sClass,
|
|
24
|
+
classProps
|
|
25
|
+
})
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
let componentStyle = $derived(
|
|
29
|
+
useStyles({
|
|
30
|
+
styleAttr,
|
|
31
|
+
sStyle,
|
|
32
|
+
styleProps
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<svelte:element
|
|
38
|
+
this={is}
|
|
39
|
+
bind:this={ref}
|
|
40
|
+
class={componentClass}
|
|
41
|
+
style={componentStyle}
|
|
42
|
+
{...restProps}
|
|
43
|
+
>
|
|
44
|
+
{@render children?.()}
|
|
45
|
+
</svelte:element>
|
|
46
|
+
|
|
47
|
+
<style>
|
|
48
|
+
.kit-card-media {
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
border-radius: calc(var(--kit-card-radius) - 2px);
|
|
51
|
+
}
|
|
52
|
+
</style>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { CardTitleProps } from '../card.types';
|
|
3
|
+
import { makeComponentProps } from '../../../html-mapped';
|
|
4
|
+
import { useClassName, useStyles } from '../../../utils';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
ref = $bindable(),
|
|
8
|
+
is = 'div',
|
|
9
|
+
children,
|
|
10
|
+
class: className = '',
|
|
11
|
+
style: styleAttr = '',
|
|
12
|
+
's-class': sClass,
|
|
13
|
+
's-style': sStyle,
|
|
14
|
+
...rest
|
|
15
|
+
}: CardTitleProps = $props();
|
|
16
|
+
|
|
17
|
+
let { classProps, styleProps, restProps } = $derived(makeComponentProps(rest));
|
|
18
|
+
|
|
19
|
+
let componentClass = $derived(
|
|
20
|
+
useClassName({
|
|
21
|
+
baseClass: 'kit-card-title',
|
|
22
|
+
className: `${className ?? ''}`.trim(),
|
|
23
|
+
sClass,
|
|
24
|
+
classProps
|
|
25
|
+
})
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
let componentStyle = $derived(
|
|
29
|
+
useStyles({
|
|
30
|
+
styleAttr,
|
|
31
|
+
sStyle,
|
|
32
|
+
styleProps
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<svelte:element
|
|
38
|
+
this={is}
|
|
39
|
+
bind:this={ref}
|
|
40
|
+
class={componentClass}
|
|
41
|
+
style={componentStyle}
|
|
42
|
+
{...restProps}
|
|
43
|
+
>
|
|
44
|
+
{@render children?.()}
|
|
45
|
+
</svelte:element>
|
|
46
|
+
|
|
47
|
+
<style>
|
|
48
|
+
.kit-card-title {
|
|
49
|
+
font-weight: 600;
|
|
50
|
+
line-height: 1.3;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.kit-card-title > :global(*) {
|
|
54
|
+
margin: 0;
|
|
55
|
+
}
|
|
56
|
+
</style>
|
|
@@ -6,6 +6,11 @@ export { default as KitAppbar } from './appbar/appbar.svelte';
|
|
|
6
6
|
export { default as KitBtn } from './btn/btn.svelte';
|
|
7
7
|
export { default as KitIcon } from './icon/icon.svelte';
|
|
8
8
|
export { default as KitCard } from './card/card.svelte';
|
|
9
|
+
export { default as KitCardMedia } from './card/modules/card-media.svelte';
|
|
10
|
+
export { default as KitCardTitle } from './card/modules/card-title.svelte';
|
|
11
|
+
export { default as KitCardContent } from './card/modules/card-content.svelte';
|
|
12
|
+
export { default as KitCardActions } from './card/modules/card-actions.svelte';
|
|
13
|
+
export { default as KitCardContainer } from './card/modules/card-container.svelte';
|
|
9
14
|
export { default as KitChip } from './chip/chip.svelte';
|
|
10
15
|
export { default as KitAvatar } from './avatar/avatar.svelte';
|
|
11
16
|
export { default as KitDropdown } from './dropdown/dropdown.svelte';
|
package/dist/components/index.js
CHANGED
|
@@ -4,6 +4,11 @@ export { default as KitAppbar } from './appbar/appbar.svelte';
|
|
|
4
4
|
export { default as KitBtn } from './btn/btn.svelte';
|
|
5
5
|
export { default as KitIcon } from './icon/icon.svelte';
|
|
6
6
|
export { default as KitCard } from './card/card.svelte';
|
|
7
|
+
export { default as KitCardMedia } from './card/modules/card-media.svelte';
|
|
8
|
+
export { default as KitCardTitle } from './card/modules/card-title.svelte';
|
|
9
|
+
export { default as KitCardContent } from './card/modules/card-content.svelte';
|
|
10
|
+
export { default as KitCardActions } from './card/modules/card-actions.svelte';
|
|
11
|
+
export { default as KitCardContainer } from './card/modules/card-container.svelte';
|
|
7
12
|
export { default as KitChip } from './chip/chip.svelte';
|
|
8
13
|
export { default as KitAvatar } from './avatar/avatar.svelte';
|
|
9
14
|
export { default as KitDropdown } from './dropdown/dropdown.svelte';
|
package/dist/constants.js
CHANGED
|
@@ -28,7 +28,12 @@ export const lapikitComponents = [
|
|
|
28
28
|
'spacer',
|
|
29
29
|
'separator',
|
|
30
30
|
'chip',
|
|
31
|
-
'card'
|
|
31
|
+
'card',
|
|
32
|
+
'card-title',
|
|
33
|
+
'card-content',
|
|
34
|
+
'card-media',
|
|
35
|
+
'card-actions',
|
|
36
|
+
'card-container'
|
|
32
37
|
];
|
|
33
38
|
export const lapikitLabsComponents = ['sheet'];
|
|
34
39
|
export const lapikitPlugins = {
|
|
@@ -8,6 +8,7 @@ import type { useClassNameProps, useStylesProps } from '../@types';
|
|
|
8
8
|
* @returns A computed class string.
|
|
9
9
|
*/
|
|
10
10
|
export declare function useClassName({ baseClass, className, sClass, classProps }?: useClassNameProps): string;
|
|
11
|
+
export declare function useIsInteractive(props: Record<string, unknown>, tag: string, interactiveTags: readonly string[], interactive?: boolean): boolean;
|
|
11
12
|
/**
|
|
12
13
|
* useStyles - Utility to compute style declarations for a component (optimized pure function).
|
|
13
14
|
* @param styleAttr - Inline style attribute as a string.
|
package/dist/utils/components.js
CHANGED
|
@@ -54,6 +54,22 @@ export function useClassName({ baseClass = '', className, sClass, classProps } =
|
|
|
54
54
|
}
|
|
55
55
|
return classes.filter(Boolean).join(' ');
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* useIsInteractive - Utility to determine whether a component should behave as interactive:
|
|
59
|
+
* either explicitly flagged, rendered as an interactive tag (e.g. 'a', 'button'), or given
|
|
60
|
+
* one of the standard interactive event handlers (onclick, onpointerdown, onkeydown).
|
|
61
|
+
* @param props - The rest/spread props object of a component.
|
|
62
|
+
* @param tag - The resolved element tag the component renders as.
|
|
63
|
+
* @param interactiveTags - The tags considered interactive for this component (e.g. ['a', 'button']).
|
|
64
|
+
* @param interactive - Explicit interactive flag passed to the component.
|
|
65
|
+
* @returns True if the component should behave as interactive.
|
|
66
|
+
*/
|
|
67
|
+
const interactiveEventKeys = ['onclick', 'onpointerdown', 'onkeydown'];
|
|
68
|
+
export function useIsInteractive(props, tag, interactiveTags, interactive = false) {
|
|
69
|
+
return (interactive ||
|
|
70
|
+
interactiveTags.includes(tag) ||
|
|
71
|
+
interactiveEventKeys.some((key) => typeof props[key] === 'function'));
|
|
72
|
+
}
|
|
57
73
|
/**
|
|
58
74
|
* useStyles - Utility to compute style declarations for a component (optimized pure function).
|
|
59
75
|
* @param styleAttr - Inline style attribute as a string.
|