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
126
127
128
129
130
//! 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.
#[must_use = "a component does nothing until it is rendered: add it as a child or return it from `render`"]
#[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 {
/// Creates a surface.
pub fn new() -> Self {
Self {
variant: SurfaceVariant::default(),
padding: px(0.),
gap: px(0.),
children: Vec::new(),
sx: None,
}
}
/// Sets the surface variant.
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 {
crate::util::refine_sx(&mut self.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)
}
}
crate::util::impl_component_styled!(Surface);