-
Notifications
You must be signed in to change notification settings - Fork 15.7k
feat(theming): add base theme config #35220
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
feat(theming): add base theme config #35220
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review by Korbit AI
Korbit automatically attempts to detect when you fix issues in new commits.
Category | Issue | Status |
---|---|---|
Blocking font CSS imports delay rendering ▹ view | 🧠 Not in standard | |
Asymmetric base theme fallback logic ▹ view | 🧠 Not in scope | |
Duplicated validation logic ▹ view | 🧠 Not in scope | |
Complex Theme Merging Logic ▹ view | 🧠 Incorrect | |
Shared mutable token dictionary between base themes ▹ view | 🧠 Not in scope | |
Complex theme resolution logic needs strategy pattern ▹ view | 🧠 Not in scope |
Files scanned
File Path | Reviewed |
---|---|
superset-frontend/packages/superset-ui-core/src/theme/GlobalStyles.tsx | ✅ |
superset/daos/theme.py | ✅ |
superset-frontend/src/types/bootstrapTypes.ts | ✅ |
superset-frontend/packages/superset-ui-core/src/theme/types.ts | ✅ |
superset-frontend/packages/superset-ui-core/src/theme/Theme.tsx | ✅ |
superset-frontend/src/pages/ThemeList/index.tsx | ✅ |
superset/views/base.py | ✅ |
superset-frontend/src/theme/ThemeController.ts | ✅ |
superset/config.py | ✅ |
Explore our documentation to understand the languages and file types we support and the files we ignore.
Check out our docs on how you can make Korbit work best for you and your team.
superset-frontend/packages/superset-ui-core/src/theme/Theme.tsx
Outdated
Show resolved
Hide resolved
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #35220 +/- ##
===========================================
+ Coverage 0 71.84% +71.84%
===========================================
Files 0 587 +587
Lines 0 43520 +43520
Branches 0 4704 +4704
===========================================
+ Hits 0 31267 +31267
- Misses 0 11025 +11025
- Partials 0 1228 +1228
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
c97b708
to
eede35d
Compare
export interface BootstrapThemeDataConfig { | ||
default: SerializableThemeConfig | {}; | ||
dark: SerializableThemeConfig | {}; | ||
baseThemeDefault?: SerializableThemeConfig | null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have to admit I didn't think about this when ideating. It's kind of getting out-of-control that we're sending 4 theme configs over from the backend on every page load. Wondering if there's a way that the THEME_DEFAULT/THEME_DARK could be treated as BASE?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we just treated themes in CRUD as overrides on top of THEME_DEFAULT/THEME_DARK?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a great point! You're absolutely right that sending 4 theme configs on every page load is getting excessive. I think your suggestion to treat THEME_DEFAULT/THEME_DARK
as the base themes makes a lot of sense.
Here's what I'm thinking:
Current architecture:
- BASE_THEME_DEFAULT/BASE_THEME_DARK: organization-wide base tokens
- THEME_DEFAULT/THEME_DARK: system themes (merged with base)
- User CRUD themes: also merged with base
Simplified architecture:
- THEME_DEFAULT/THEME_DARK: act as both system defaults and base themes
- User CRUD themes: simple overrides on top of the system themes
Let me know your thoughts on this @mistercrunch
eede35d
to
1559605
Compare
Does the PR description need an update/reboot after the latest changes? |
}; | ||
} | ||
|
||
if (baseTheme.components || config.components) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
didn't look deep into the logic, but is this whole section a use case for lodash
's merge function? Maybe can save ~30 LoC
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, mergeWith
will do the trick. Nice catch!
<Tooltip title={t('This is the system dark theme')}> | ||
<Tag color="default"> | ||
<Tag | ||
color="default" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: repeating the lines above, wondering the elegant way to do this. Maybe either marginRight
on that first one or [maybe better] wrapping both into <Space size="small" style={{ display: 'inline-flex' }}>
might be most antd-aligned, style-free. I think it'll generate and empty div but that's probably ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Somehow came to hate most css
and even style
(to a slightly lesser extend) while working on theming for months. I think I'm getting a little OCD about it...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right! I think I found a "better/elegant" way of doing this
* @param theme - The theme configuration to check | ||
* @returns True if the theme is dark mode, false otherwise | ||
*/ | ||
private isThemeDark(theme: AnyThemeConfig): boolean { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
already exists in packages/superset-ui-core/src/theme/utils/themeUtils.ts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes but that function serves a different purpose, it checks if a computed theme is dark
by examining the background color
.
The ThemeController
needed something different, it needs to check if a configuration uses dark algorithm before the theme is computed. So I added a new function isThemeConfigDark
alongside the isThemeDark
in packages/superset-ui-core/src/theme/utils/themeUtils.ts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh right. Though in theory could potentially have a dark theme without the dark algorithm, though highly unlikely/impractical. I think the main Theme class has or used to have an isDark method too. theme.fromConfig(themeConfig).isDark
or similar might work too but require a bit more computation.
there's a subtle difference between a theme config and a computed theme, probably the typing system should be aware of the difference (maybe it is already, I'd have to check) ... The Theme class knows how to generate one from the other. There might be two isThemeConfigDark
and isThemeDark
.... If we need both maybe they both live in utils.
I'm not sure what makes sense to do in the context of this PR, but might be good to sort this out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mistercrunch please take a look into this --> d4eb799#diff-b48e95953d3af3d6d542dd931e3269b3f74031f32a292dd4b77fb11ddba31097R128
Let me know your thoughts
superset/daos/theme.py
Outdated
"Multiple system default themes found (%s), none will be used", | ||
len(system_defaults), | ||
) | ||
return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this line probably not needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree!
superset/daos/theme.py
Outdated
"Multiple system dark themes found (%s), none will be used", | ||
len(system_darks), | ||
) | ||
return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line not needed as there's a catchall bellow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree as well!
superset/views/base.py
Outdated
|
||
def _load_theme_from_model( | ||
theme_model: Any | None, fallback_theme: dict[str, Any] | None, theme_type: str | ||
) -> dict[str, Any] | None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's a type for it here superset/themes/types.py
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure! I'll change this
superset/views/base.py
Outdated
return fallback_theme | ||
|
||
|
||
def _process_theme(theme: dict[str, Any] | None, theme_type: str) -> dict[str, Any]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
superset/themes/types.py
private isEmptyTheme(theme: any): boolean { | ||
if (!theme) return true; | ||
|
||
// If it's an empty object {}, it's empty | ||
if (typeof theme === 'object' && Object.keys(theme).length === 0) | ||
return true; | ||
|
||
// If theme has an algorithm, it's not empty (even without tokens) | ||
if (theme.algorithm) return false; | ||
|
||
// Check if theme has any tokens defined | ||
if (theme.token && Object.keys(theme.token).length > 0) return false; | ||
|
||
// Check if theme has components defined | ||
if (theme.components && Object.keys(theme.components).length > 0) | ||
return false; | ||
|
||
return true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't look right to me, can we add proper types to the argument? First two checks might not be needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, I was overcomplicating things here. Thanks, nice catch!
67899ab
to
aac1df4
Compare
aac1df4
to
d4eb799
Compare
it('renders strong text', () => { | ||
render(<Typography.Text strong>Strong Text</Typography.Text>); | ||
expect(screen.getByText('Strong Text')).toHaveStyle('font-weight: 500'); | ||
expect(screen.getByText('Strong Text')).toHaveStyle('font-weight: 600'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why this test needed to change, from my understanding we just moved the default base theme from the frontend to the backend.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test changed because fontWeightStrong: 500
theme token was moved from the frontend (Theme.tsx) to the backend (config.py). During unit tests, the backend config isn't available, so Ant Design's default (600) is used instead. The test now reflects this fallback behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, then I'm surprised we don't have more unit tests failing. Noting this test doesn't seem to use the spec/helpers/ render (which renders with a theme provider), it does import '@testing-library/jest-dom';
instead of @superset-ui/core/spec
, wondering if it has something to do with it.
Sidetrack (outside the scope of this PR): It's a bit of a mess around this, might be good to not allow importing @testing-library
directly so we're always providing a theme context. It's a bit confusing since each library might have slightly different helpers (useRedux:true in main app for instance).
Not sure what to do with all this, maybe it's just out-of-scope of this PR and this change is ok.
SUMMARY
This PR implements a base theme configuration system that provides consistent foundation themes across the application. This change introduces
THEME_DEFAULT
andTHEME_DARK
as complete base themes that serve as the foundation for all theme customization, ensuring consistent theming while maintaining flexibility.TESTING INSTRUCTIONS
Test base theme configuration:
Test with custom theme tokens:
THEME_DEFAULT
inconfig.py
:Test UI theme administration:
Test confirmation modals theming:
Test dashboard-specific themes:
ADDITIONAL INFORMATION