Skip to content

Commit c93e8b1

Browse files
author
FalkWolsky
committed
Branding for refreshed UI
1 parent b36c9a4 commit c93e8b1

42 files changed

Lines changed: 713 additions & 286 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useLayoutEffect } from "react";
12
import { default as App } from "antd/es/app";
23
import type { MessageInstance } from "antd/es/message/interface";
34
import type { ModalStaticFunctions } from "antd/es/modal/confirm";
@@ -7,12 +8,29 @@ let messageInstance: MessageInstance;
78
let notificationInstance: NotificationInstance;
89
let modalInstance: Omit<ModalStaticFunctions, 'warn'>;
910

10-
export default () => {
11-
const staticFunction = App.useApp();
12-
messageInstance = staticFunction.message;
13-
modalInstance = staticFunction.modal;
14-
notificationInstance = staticFunction.notification;
11+
// Nested application shells can supply a themed API without leaking it after exit.
12+
const scopes = new Map<symbol, { priority: number; api: ReturnType<typeof App.useApp> }>();
13+
function updateInstances() {
14+
const active = Array.from(scopes.values()).sort((a, b) => b.priority - a.priority)[0]?.api;
15+
if (active) {
16+
messageInstance = active.message;
17+
modalInstance = active.modal;
18+
notificationInstance = active.notification;
19+
}
20+
}
21+
22+
export default function GlobalInstances({ priority = 0 }: { priority?: number }) {
23+
const api = App.useApp();
24+
useLayoutEffect(() => {
25+
const key = Symbol();
26+
scopes.set(key, { priority, api });
27+
updateInstances();
28+
return () => {
29+
scopes.delete(key);
30+
updateInstances();
31+
};
32+
}, [api, priority]);
1533
return null;
16-
};
34+
}
1735

18-
export { messageInstance, notificationInstance, modalInstance };
36+
export { messageInstance, notificationInstance, modalInstance };
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { DefaultTheme } from "styled-components";
2+
3+
type AdminTheme = DefaultTheme & { admin?: { variables: Record<string, string> } };
4+
5+
// React theme context survives portals; inherited DOM variables do not.
6+
// Outside the Admin shell this contributes no styles.
7+
export const adminThemeVariables = ({ theme }: { theme: DefaultTheme }) =>
8+
(theme as AdminTheme).admin?.variables;

‎client/packages/lowcoder-design/src/components/button.tsx‎

Lines changed: 57 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import styled, { css } from "styled-components";
55
import { Loading } from "./Loading";
66
import * as React from "react";
77
import { CSSProperties, forwardRef } from "react";
8+
import { adminThemeVariables } from "./adminThemeVariables";
89

910
const buttonStyleConfig = {
1011
normal: css`
@@ -28,95 +29,95 @@ const buttonStyleConfig = {
2829
`,
2930
primary: css`
3031
&&& {
31-
background: #4965f2;
32-
border: 1px solid #4965f2;
33-
color: #ffffff;
32+
background: var(--admin-accent, #4965f2);
33+
border: 1px solid var(--admin-accent, #4965f2);
34+
color: var(--admin-on-accent, #ffffff);
3435
3536
&.ant-btn-background-ghost {
36-
background-color: #fafbff;
37-
color: #4965f2;
38-
border-color: #c9d1fc;
37+
background-color: var(--admin-tint, #fafbff);
38+
color: var(--admin-link, #4965f2);
39+
border-color: var(--admin-line-hover, #c9d1fc);
3940
4041
&:hover {
41-
color: #315efb;
42-
background-color: #f5faff;
43-
border-color: #c2d6ff;
42+
color: var(--admin-link-hover, #315efb);
43+
background-color: var(--admin-hover, #f5faff);
44+
border-color: var(--admin-line-hover, #c2d6ff);
4445
}
4546
4647
&:focus {
47-
color: #315efb;
48-
background-color: #f5faff;
49-
border-color: #c2d6ff;
48+
color: var(--admin-link-hover, #315efb);
49+
background-color: var(--admin-hover, #f5faff);
50+
border-color: var(--admin-line-hover, #c2d6ff);
5051
}
5152
}
5253
5354
&:focus {
54-
background: #4965f2;
55-
border: 1px solid #4965f2;
56-
color: #ffffff;
55+
background: var(--admin-accent, #4965f2);
56+
border: 1px solid var(--admin-accent, #4965f2);
57+
color: var(--admin-on-accent, #ffffff);
5758
}
5859
5960
&:hover {
60-
border: 1px solid #315efb;
61-
background: #315efb;
62-
color: #ffffff;
61+
border: 1px solid var(--admin-accent-hover, #315efb);
62+
background: var(--admin-accent-hover, #315efb);
63+
color: var(--admin-on-accent, #ffffff);
6364
}
6465
6566
:disabled {
6667
&:hover {
67-
border: 1px solid #dbe1fd;
68-
background: #dbe1fd;
69-
color: #ffffff;
68+
border: 1px solid var(--admin-line, #dbe1fd);
69+
background: var(--admin-line, #dbe1fd);
70+
color: var(--admin-on-accent, #ffffff);
7071
opacity: 1;
7172
}
7273
73-
border: 1px solid #dbe1fd;
74-
background: #dbe1fd;
75-
color: #ffffff;
74+
border: 1px solid var(--admin-line, #dbe1fd);
75+
background: var(--admin-line, #dbe1fd);
76+
color: var(--admin-on-accent, #ffffff);
7677
opacity: 1;
7778
}
7879
}
7980
`,
8081
blue: css`
81-
background-color: #fafbff;
82-
color: #4965f2;
83-
border-color: #c9d1fc;
82+
background-color: var(--admin-tint, #fafbff);
83+
color: var(--admin-link, #4965f2);
84+
border-color: var(--admin-line-hover, #c9d1fc);
8485
8586
&:hover {
86-
color: #315efb;
87-
background-color: #f5faff;
88-
border-color: #c2d6ff;
87+
color: var(--admin-link-hover, #315efb);
88+
background-color: var(--admin-hover, #f5faff);
89+
border-color: var(--admin-line-hover, #c2d6ff);
8990
}
9091
9192
&:focus {
92-
color: #315efb;
93-
background-color: #f5faff;
94-
border-color: #c2d6ff;
93+
color: var(--admin-link-hover, #315efb);
94+
background-color: var(--admin-hover, #f5faff);
95+
border-color: var(--admin-line-hover, #c2d6ff);
9596
}
9697
9798
:disabled,
9899
:disabled:hover {
99-
background: #f9fbff;
100+
background: var(--admin-hover, #f9fbff);
100101
border: 1px solid #dee9ff;
101102
border-radius: 4px;
102103
}
103104
`,
104105
link: css`
105106
&&& {
106-
color: #4955f2;
107-
border-color: #c9d1fc;
108-
background-color: #fafbff;
107+
color: var(--admin-link, #4955f2);
108+
border-color: var(--admin-line-hover, #c9d1fc);
109+
background-color: var(--admin-tint, #fafbff);
109110
110111
&:hover {
111-
color: #315efb;
112-
border-color: #c2d6ff;
113-
background-color: #f9fbff;
112+
color: var(--admin-link-hover, #315efb);
113+
border-color: var(--admin-line-hover, #c2d6ff);
114+
background-color: var(--admin-hover, #f9fbff);
114115
}
115116
116117
&:focus {
117-
color: #315efb;
118-
border-color: #c2d6ff;
119-
background-color: #f9fbff;
118+
color: var(--admin-link-hover, #315efb);
119+
border-color: var(--admin-line-hover, #c2d6ff);
120+
background-color: var(--admin-hover, #f9fbff);
120121
}
121122
}
122123
`,
@@ -143,6 +144,7 @@ const buttonStyleConfig = {
143144
export type TacoButtonType = keyof typeof buttonStyleConfig;
144145

145146
const StyledAntdButton = styled(Button)<{ $buttonType: TacoButtonType; $loading: boolean }>`
147+
${adminThemeVariables};
146148
${(props) => buttonStyleConfig[props.$buttonType]};
147149
display: flex;
148150
align-items: center;
@@ -189,8 +191,8 @@ const TacoButton = forwardRef(
189191
let loadingBackground;
190192
let loadingColor;
191193
if (buttonType === "link") {
192-
loadingBackground = "#fafbff";
193-
loadingColor = "#3377FF";
194+
loadingBackground = "var(--admin-tint, #fafbff)";
195+
loadingColor = "var(--admin-accent-hover, #3377FF)";
194196
} else if (buttonType === "delete") {
195197
loadingBackground = "#fef4f4";
196198
loadingColor = "#f73131";
@@ -227,9 +229,9 @@ export const BigButtonStyle = styled.button`
227229
height: 30px;
228230
width: 280px;
229231
margin-right: 2px;
230-
color: #4965f2;
231-
border: 1px solid #c9d1fc;
232-
background: #fafbff;
232+
color: var(--admin-accent, #4965f2);
233+
border: 1px solid var(--admin-line-hover, #c9d1fc);
234+
background: var(--admin-tint, #fafbff);
233235
border-radius: 4px;
234236
235237
font-size: 13px;
@@ -239,9 +241,9 @@ export const BigButtonStyle = styled.button`
239241
cursor: pointer;
240242
241243
&:hover {
242-
color: #3377ff;
243-
border: 1px solid #c2d6ff;
244-
background: #f9fbff;
244+
color: var(--admin-accent-hover, #3377ff);
245+
border: 1px solid var(--admin-line-hover, #c2d6ff);
246+
background: var(--admin-hover, #f9fbff);
245247
transition: all 0.4s ease;
246248
}
247249
`;
@@ -270,7 +272,7 @@ export const RedButton = styled(BigButtonStyle)`
270272
const StyledAddButton = styled.button`
271273
display: flex;
272274
align-items: center;
273-
color: #4965f2;
275+
color: var(--admin-accent, #4965f2);
274276
font-size: 13px;
275277
line-height: 13px;
276278
gap: 4px;
@@ -281,17 +283,17 @@ const StyledAddButton = styled.button`
281283
padding: 0;
282284
283285
&:hover {
284-
color: #315efb;
286+
color: var(--admin-accent-hover, #315efb);
285287
background: white;
286288
287289
svg g {
288-
stroke: #315efb;
290+
stroke: var(--admin-accent-hover, #315efb);
289291
}
290292
}
291293
292294
&:focus {
293295
background: white;
294-
color: #315efb;
296+
color: var(--admin-accent-hover, #315efb);
295297
}
296298
297299
svg {
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Admin branding preview</title></head>
4+
<body style="margin:0"><div id="root"></div><script type="module" src="/src/dev-utils/admin-branding-preview.tsx"></script></body>
5+
</html>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { BrandingConfig } from "api/enterpriseApi";
2+
import { Logo, LogoHome } from "assets/images";
3+
import { trans } from "i18n";
4+
import { buildMaterialPreviewURL } from "util/materialUtils";
5+
import styled from "styled-components";
6+
7+
const BrandLogo = styled.img`
8+
height: 28px;
9+
max-width: 100%;
10+
object-fit: contain;
11+
`;
12+
13+
export function AdminBrand({ branding, showName }: { branding?: BrandingConfig; showName: boolean }) {
14+
const { logo, standardTitle } = branding?.config_set || {};
15+
const name = standardTitle || trans("productName");
16+
if (logo) return <BrandLogo src={branding?.orgId ? buildMaterialPreviewURL(logo) : logo} alt={name} />;
17+
if (!showName && !standardTitle) return <LogoHome />;
18+
return (
19+
<span className="admin-brand-name">
20+
{!standardTitle && <span className="admin-brand-mark"><Logo /></span>}
21+
<span className="admin-brand-title" title={name}>{name}</span>
22+
</span>
23+
);
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import styled from "styled-components";
2+
import SideBar from "./SideBar";
3+
import { adminThemeVariables } from "lowcoder-design/src/components/adminThemeVariables";
4+
5+
export const AdminSidebar = styled(SideBar)`
6+
${adminThemeVariables};
7+
background: var(--admin-sidebar, #f8f3fb);
8+
color: var(--admin-sidebar-text, #281a30);
9+
font-family: var(--admin-font);
10+
padding: 20px 14px;
11+
overflow-y: auto;
12+
flex-shrink: 0;
13+
border-right: 1px solid var(--admin-line);
14+
15+
.sidebar-section:not(:last-child)::after {
16+
content: "";
17+
display: block;
18+
width: calc(100% - 24px);
19+
height: 1px;
20+
background: var(--admin-line);
21+
margin: 12px auto;
22+
}
23+
`;
24+
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Admin branding
2+
3+
The Admin shell uses the existing Enterprise branding API and Redux state. It does
4+
not introduce another branding API, setting or database representation.
5+
6+
- `getBrandingSetting` selects the current workspace configuration, then the global
7+
configuration. These are complete configurations, not a field-by-field merge.
8+
This preserves explicit `false` settings and the origin of logo references
9+
(workspace material IDs versus global URLs).
10+
- `getAdminAppearance` translates the settings into Ant Design tokens and
11+
`--admin-*` CSS variables. New Admin components should use these instead of fixed
12+
Lowcoder colors. Semantic error/success colors and app artwork remain independent.
13+
- Header and sidebar settings override the generated palette. The primary color
14+
remains exact; text on that color and links get readable foreground colors.
15+
- Stored font names such as `Open+Sans` are normalized for CSS. The existing app
16+
font loader loads the selected font. No custom font is required for the default
17+
locally bundled Ubuntu font.
18+
- The scoped styled-components theme carries variables into custom portal content.
19+
The nested Ant App supplies themed imperative dialogs/messages and restores the
20+
outer instances when leaving Admin. The app editor retains its own theme.
21+
- Existing custom CSS hooks (`top-header`, `sidebar`, `sidebar-item`,
22+
`sidebar-section`, `main-content`) and the workspace preload CSS path remain.
23+
24+
With the Vite development server running, `/admin-branding-preview.html` exercises
25+
the real shell, navigation, cards, tables and dialogs with sample company settings.
26+
It never saves account settings or calls the branding API, and is not a production
27+
build entry. Use it to check different brands, responsive navigation and custom CSS.
28+
29+
Regression coverage is in `adminAppearance.test.ts`, `adminTheme.test.tsx` and
30+
`redux/sagas/enterpriseBranding.test.ts`, including workspace switches and API
31+
failures. Branding API persistence still requires an Enterprise backend for a live
32+
end-to-end check.

0 commit comments

Comments
 (0)