1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
//! Surface — port of `@heroui/surface`.
//!
//! A container that applies surface-level styling. Mirrors the React API:
//! `variant` of `transparent | default | secondary | tertiary`. Upstream
//! `.surface` is only `relative text-foreground` plus each variant's
//! fill/foreground classes; the docs examples add their `flex flex-col gap-3
//! rounded-3xl p-6` skeleton through `className`, so it is not a Surface
//! default. This port keeps a minimal column skeleton with zero default
//! padding and gap so the repository `padding`/`gap` builders work; they are
//! conveniences, not upstream props. Upstream also publishes its variant
//! through `SurfaceContext`; GPUI has no ancestor context propagation, so
//! nothing here reads the surrounding surface.
use gpui::{
div, px, AnyElement, App, IntoElement, ParentElement, Pixels, RenderOnce, Styled, Window,
};
use herogpui_theme::ActiveTheme;
/// Prominence level of a surface (`variant` prop).
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum SurfaceVariant {
/// No background — for overlays and cards with a custom background.
Transparent,
/// `bg-surface`.
#[default]
Default,
/// `bg-surface-secondary`.
Secondary,
/// `bg-surface-tertiary`.
Tertiary,
}
/// HeroUI Surface.
#[derive(IntoElement)]
pub struct Surface {
variant: SurfaceVariant,
padding: Pixels,
gap: Pixels,
children: Vec<AnyElement>,
/// The `sx` slot, refined over the root style at the end of render.
sx: Option<Box<gpui::StyleRefinement>>,
}
impl Surface {
pub fn new() -> Self {
Self {
variant: SurfaceVariant::default(),
padding: px(0.),
gap: px(0.),
children: Vec::new(),
sx: None,
}
}
pub fn variant(mut self, variant: SurfaceVariant) -> Self {
self.variant = variant;
self
}
/// The one slot for caller-owned low-level styling: GPUI's styling methods
/// (`bg`, `text_color`, `w`, `h`, `p`, `rounded`, `border_color`, …)
/// applied to the surface's root element after every value the variant and
/// the active theme chose, so they win.
pub fn sx(mut self, style: impl FnOnce(gpui::Div) -> gpui::Div) -> Self {
self.sx = Some(crate::util::capture_sx(style));
self
}
/// Repository convenience, not an upstream prop.
pub fn padding(mut self, padding: impl Into<Pixels>) -> Self {
self.padding = padding.into();
self
}
/// Repository convenience, not an upstream prop.
pub fn gap(mut self, gap: impl Into<Pixels>) -> Self {
self.gap = gap.into();
self
}
}
impl Default for Surface {
fn default() -> Self {
Self::new()
}
}
impl ParentElement for Surface {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements);
}
}
impl RenderOnce for Surface {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let colors = cx.colors();
let mut el = div()
.flex()
.flex_col()
.gap(self.gap)
.p(self.padding)
.text_color(colors.foreground);
el = match self.variant {
// `.surface--transparent` is only `bg-transparent`; GPUI's default
// div background is already transparent, so nothing extra to paint.
SurfaceVariant::Transparent => el,
// Each fill brings its own foreground: `.surface--secondary` is
// `bg-surface-secondary text-surface-secondary-foreground`, and the
// text colour was going unset.
SurfaceVariant::Default => el
.bg(colors.surface.background)
.text_color(colors.surface.foreground),
SurfaceVariant::Secondary => el
.bg(colors.surface_secondary)
.text_color(colors.surface_secondary_foreground()),
SurfaceVariant::Tertiary => el
.bg(colors.surface_tertiary)
.text_color(colors.surface_tertiary_foreground()),
};
let el = el.children(self.children);
crate::util::apply_sx(el, &self.sx)
}
}