Skip to content

Commit 88faf13

Browse files
authored
feat: update card components with ceramic design system (#13)
1 parent 4f43f92 commit 88faf13

2 files changed

Lines changed: 345 additions & 68 deletions

File tree

src/dashkit/card.py

Lines changed: 108 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,122 @@
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+
**kwargs,
19+
) -> Component:
20+
header_base = (
21+
"flex flex-wrap items-center justify-between gap-4 px-6 py-4 "
22+
"[&:has(+footer)]:border-b [&:has(+footer)]:border-ceramic-bg-separator "
23+
"text-ceramic-secondary leading-5 "
24+
)
25+
return html.Section(
26+
children=[
27+
(
28+
html.Header(
29+
children=header,
30+
className=(header_base + (header_className.strip() if header_className else "")).strip(),
31+
)
32+
if header
33+
else None
34+
),
35+
html.Div(
36+
children=html.Div(
37+
children=children,
38+
className=(
39+
("flex flex-col " + (body_className.strip() if body_className else "px-6 py-2")).strip()
40+
),
41+
),
42+
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 ",
43+
),
44+
(
45+
html.Footer(
46+
children=html.Div(
47+
children=footer,
48+
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",
49+
),
50+
className="px-7 pb-3 pt-4",
51+
)
52+
if footer
53+
else None
54+
),
55+
],
56+
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 "
57+
+ className.strip(),
58+
**kwargs,
59+
)
1560

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-
)
5161

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)
62+
def CardTitle(
63+
children: Children,
64+
className: str = "",
65+
**kwargs,
66+
) -> html.Span:
67+
return html.Span(
68+
children=children,
69+
className="flex flex-wrap items-center gap-x-2 gap-y-0.5 font-medium text-ceramic-primary text-[16px] "
70+
+ className.strip(),
71+
)
5772

58-
return html.Div(card_content, className=combined_classes, **kwargs)
73+
74+
def CardTitleWithIcon(
75+
icon: str,
76+
children: Children,
77+
className: str = "",
78+
**kwargs,
79+
) -> html.Span:
80+
return html.Span(
81+
children=[
82+
DashIconify(icon=icon, className="stroke-2"),
83+
CardTitle(children, className=className, **kwargs),
84+
],
85+
className="flex flex-wrap items-center gap-x-2 gap-y-0.5 font-medium text-ceramic-primary text-[16px] "
86+
+ className.strip(),
87+
)
88+
89+
90+
def CardSubtitle(
91+
children: Children,
92+
className: str = "",
93+
**kwargs,
94+
) -> html.Span:
95+
return html.Span(
96+
children=children,
97+
className="text-xs text-ceramic-secondary pr-2 " + className.strip(),
98+
)
99+
100+
101+
def CardFooter(
102+
children: Children,
103+
className: str = "",
104+
**kwargs,
105+
) -> html.Span:
106+
return html.Span(
107+
children=children,
108+
className="text-xs text-ceramic-secondary " + className.strip(),
109+
)
59110

60111

61112
def MetricCard(
62113
title: str,
63114
value: str,
64115
trend: str | None = None,
65116
trend_positive: bool = True,
66-
grid_cols: Literal[1, 2, 3, 4, 6, 12, "full"] = 1,
67117
className: str = "",
68-
**kwargs: Any,
69-
) -> html.Div:
118+
**kwargs,
119+
) -> Component:
70120
"""
71121
Specialized card for displaying metrics/KPIs.
72122
@@ -75,7 +125,6 @@ def MetricCard(
75125
value: Metric value to display
76126
trend: Optional trend indicator (e.g., "+2.1%", "↗ +5%")
77127
trend_positive: Whether trend is positive (affects color)
78-
grid_cols: Grid columns span
79128
className: Additional CSS classes
80129
**kwargs: Additional props
81130
"""
@@ -102,38 +151,29 @@ def MetricCard(
102151

103152
return Card(
104153
html.Div(content, className="text-center"),
105-
className=f"bg-dashkit-panel-light dark:bg-dashkit-surface {className}",
106-
grid_cols=grid_cols,
154+
className=className,
107155
**kwargs,
108156
)
109157

110158

111159
def ChartCard(
112160
title: str,
113-
chart: Any,
114-
grid_cols: Literal[1, 2, 3, 4, 6, 12, "full"] = 1,
161+
chart: Children,
115162
className: str = "",
116-
**kwargs: Any,
117-
) -> html.Div:
163+
**kwargs,
164+
) -> Component:
118165
"""
119166
Specialized card for displaying charts with consistent styling.
120167
121168
Args:
122169
title: Chart title
123170
chart: Chart component (e.g., dmc.LineChart, dmc.BarChart, etc.)
124-
grid_cols: Grid columns span
125171
className: Additional CSS classes
126172
**kwargs: Additional props
127173
"""
128174
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-
],
175+
chart,
176+
header=CardTitle(title),
136177
className=className,
137-
grid_cols=grid_cols,
138178
**kwargs,
139179
)

0 commit comments

Comments
 (0)