Skip to content

Commit 16aa316

Browse files
committed
feat: update card components with ceramic design system
- Replace Card component with new header/footer based architecture - Add CardTitle, CardTitleWithIcon, CardSubtitle, and CardFooter components - Update MetricCard and ChartCard to use new Card API - Add ceramic color tokens and design system variables to CSS - Remove grid_cols parameter and legacy styling approach
1 parent 4f43f92 commit 16aa316

File tree

2 files changed

+343
-68
lines changed

2 files changed

+343
-68
lines changed

src/dashkit/card.py

Lines changed: 106 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,120 @@
1-
from typing import Any, Literal
1+
from collections.abc import Sequence
22

33
from dash import html
4+
from dash.development.base_component import Component
5+
from dash.html.Section import Section
6+
from dash_iconify import DashIconify
7+
8+
Children = Component | Sequence[Component] | str | Section
49

510

611
def Card(
7-
children: Any,
12+
children: Children,
13+
header: Children | None = None,
14+
footer: Children | None = None,
815
className: str = "",
9-
title: str | None = None,
10-
grid_cols: Literal[1, 2, 3, 4, 6, 12, "full"] = 1,
11-
**kwargs: Any,
12-
) -> html.Div:
13-
"""
14-
Reusable card component with consistent dashkit styling and grid support.
16+
header_className: str | None = None,
17+
body_className: str | None = None,
18+
) -> Component:
19+
header_base = (
20+
"flex flex-wrap items-center justify-between gap-4 px-6 py-4 "
21+
"[&:has(+footer)]:border-b [&:has(+footer)]:border-ceramic-bg-separator "
22+
"text-ceramic-secondary leading-5 "
23+
)
24+
return html.Section(
25+
children=[
26+
(
27+
html.Header(
28+
children=header,
29+
className=(header_base + (header_className.strip() if header_className else "")).strip(),
30+
)
31+
if header
32+
else None
33+
),
34+
html.Div(
35+
children=html.Div(
36+
children=children,
37+
className=(
38+
("flex flex-col " + (body_className.strip() if body_className else "px-6 py-2")).strip()
39+
),
40+
),
41+
className="bg-white dark:bg-dashkit-surface overflow-hidden rounded-2xl ring-1 ring-[#191C21]/4 dark:ring-ceramic-black/20 shadow-[0_1px_2px_0_rgba(25,28,33,.06),0_0_2px_0_theme(colors.ceramic.black/.08)] dark:shadow-[inset_0_0_1px_1px_theme(colors.ceramic.white/.01),0_1px_3px_0_theme(colors.ceramic.black/.4),0_0_3px_0_theme(colors.ceramic.black/.2)] flex-1 min-h-0 ",
42+
),
43+
(
44+
html.Footer(
45+
children=html.Div(
46+
children=footer,
47+
className="flex items-start px-5 gap-1.5 text-ceramic-body-3 text-ceramic-secondary -mx-5 border-t border-ceramic-bg-separator first:border-none [:where(&)]:py-3 first:[:where(&)]:pt-0 last:[:where(&)]:pb-0 flex-1 leading-5",
48+
),
49+
className="px-7 pb-3 pt-4",
50+
)
51+
if footer
52+
else None
53+
),
54+
],
55+
className="group flex flex-col w-full h-full rounded-2xl px-[4px] [:where(&)]:py-1 bg-dashkit-panel-light dark:bg-dashkit-panel-dark "
56+
+ className.strip(),
57+
)
1558

16-
Args:
17-
children: Content to display inside the card
18-
className: Additional CSS classes
19-
title: Optional title to display at the top of the card
20-
grid_cols: Grid columns span (1-12, or "full" for col-span-full)
21-
**kwargs: Additional props passed to the outer div
22-
"""
23-
# Map grid_cols to CSS classes
24-
grid_class_map = {
25-
1: "col-span-1",
26-
2: "col-span-2",
27-
3: "col-span-3",
28-
4: "col-span-4",
29-
6: "col-span-6",
30-
12: "col-span-12",
31-
"full": "col-span-full",
32-
}
33-
34-
grid_class = grid_class_map.get(grid_cols, "col-span-1")
35-
36-
# Base card styling with dashkit colors
37-
base_classes = "bg-white dark:bg-dashkit-panel-dark p-6 rounded-lg border border-dashkit-border-light dark:border-dashkit-border-dark"
38-
combined_classes = f"{base_classes} {grid_class} {className}".strip()
39-
40-
# Build card content
41-
card_content = []
42-
43-
# Add title if provided
44-
if title:
45-
card_content.append(
46-
html.H3(
47-
title,
48-
className="text-lg font-medium mb-4 text-dashkit-text dark:text-dashkit-text-invert",
49-
)
50-
)
5159

52-
# Add children (can be a single element or list)
53-
if isinstance(children, list):
54-
card_content.extend(children)
55-
else:
56-
card_content.append(children)
60+
def CardTitle(
61+
children: Children,
62+
className: str = "",
63+
**kwargs,
64+
) -> html.Span:
65+
return html.Span(
66+
children=children,
67+
className="flex flex-wrap items-center gap-x-2 gap-y-0.5 font-medium text-ceramic-primary text-[16px] "
68+
+ className.strip(),
69+
)
70+
71+
72+
def CardTitleWithIcon(
73+
icon: str,
74+
children: Children,
75+
className: str = "",
76+
**kwargs,
77+
) -> html.Span:
78+
return html.Span(
79+
children=[
80+
DashIconify(icon=icon, className="stroke-2"),
81+
CardTitle(children, className=className, **kwargs),
82+
],
83+
className="flex flex-wrap items-center gap-x-2 gap-y-0.5 font-medium text-ceramic-primary text-[16px] "
84+
+ className.strip(),
85+
)
86+
5787

58-
return html.Div(card_content, className=combined_classes, **kwargs)
88+
def CardSubtitle(
89+
children: Children,
90+
className: str = "",
91+
**kwargs,
92+
) -> html.Span:
93+
return html.Span(
94+
children=children,
95+
className="text-xs text-ceramic-secondary pr-2 " + className.strip(),
96+
)
97+
98+
99+
def CardFooter(
100+
children: Children,
101+
className: str = "",
102+
**kwargs,
103+
) -> html.Span:
104+
return html.Span(
105+
children=children,
106+
className="text-xs text-ceramic-secondary " + className.strip(),
107+
)
59108

60109

61110
def MetricCard(
62111
title: str,
63112
value: str,
64113
trend: str | None = None,
65114
trend_positive: bool = True,
66-
grid_cols: Literal[1, 2, 3, 4, 6, 12, "full"] = 1,
67115
className: str = "",
68-
**kwargs: Any,
69-
) -> html.Div:
116+
**kwargs,
117+
) -> Component:
70118
"""
71119
Specialized card for displaying metrics/KPIs.
72120
@@ -75,7 +123,6 @@ def MetricCard(
75123
value: Metric value to display
76124
trend: Optional trend indicator (e.g., "+2.1%", "↗ +5%")
77125
trend_positive: Whether trend is positive (affects color)
78-
grid_cols: Grid columns span
79126
className: Additional CSS classes
80127
**kwargs: Additional props
81128
"""
@@ -102,38 +149,29 @@ def MetricCard(
102149

103150
return Card(
104151
html.Div(content, className="text-center"),
105-
className=f"bg-dashkit-panel-light dark:bg-dashkit-surface {className}",
106-
grid_cols=grid_cols,
152+
className=className,
107153
**kwargs,
108154
)
109155

110156

111157
def ChartCard(
112158
title: str,
113-
chart: Any,
114-
grid_cols: Literal[1, 2, 3, 4, 6, 12, "full"] = 1,
159+
chart: Children,
115160
className: str = "",
116-
**kwargs: Any,
117-
) -> html.Div:
161+
**kwargs,
162+
) -> Component:
118163
"""
119164
Specialized card for displaying charts with consistent styling.
120165
121166
Args:
122167
title: Chart title
123168
chart: Chart component (e.g., dmc.LineChart, dmc.BarChart, etc.)
124-
grid_cols: Grid columns span
125169
className: Additional CSS classes
126170
**kwargs: Additional props
127171
"""
128172
return Card(
129-
[
130-
html.H3(
131-
title,
132-
className="text-lg font-medium mb-4 text-dashkit-text dark:text-dashkit-text-invert",
133-
),
134-
chart,
135-
],
173+
chart,
174+
header=CardTitle(title),
136175
className=className,
137-
grid_cols=grid_cols,
138176
**kwargs,
139177
)

0 commit comments

Comments
 (0)