Skip to content

Commit

Permalink
feat(ui): allow change top bar theme color
Browse files Browse the repository at this point in the history
Signed-off-by: Roman Dmytrenko <[email protected]>
  • Loading branch information
erka committed May 31, 2024
1 parent 07208a6 commit 346cedf
Show file tree
Hide file tree
Showing 10 changed files with 76 additions and 8 deletions.
4 changes: 4 additions & 0 deletions config/flipt.schema.cue
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,10 @@ import "strings"
#ui: {
enabled?: bool | *true
default_theme?: "light" | "dark" | *"system"
topbar?: {
color?: string
label?: string
}
}

#audit: {
Expand Down
12 changes: 12 additions & 0 deletions config/flipt.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1184,6 +1184,18 @@
"enum": ["light", "dark", "system"],
"default": "system",
"deprecated": false
},
"topbar": {
"type": "object",
"additionalProperties": false,
"properties": {
"color": {
"type": "string"
},
"label": {
"type": "string"
}
}
}
},
"title": "UI"
Expand Down
10 changes: 10 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1328,6 +1328,16 @@ func TestLoad(t *testing.T) {
path: "./testdata/analytics/invalid_buffer_configuration_flush_period.yml",
wantErr: errors.New("flush period below 10 seconds"),
},
{
name: "ui topbar with custom color",
path: "./testdata/ui/topbar_color.yml",
expected: func() *Config {
cfg := Default()
cfg.UI.Topbar.Color = "#42bda0"
cfg.UI.Topbar.Label = "World"
return cfg
},
},
}

for _, tt := range tests {
Expand Down
5 changes: 5 additions & 0 deletions internal/config/testdata/ui/topbar_color.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ui:
topbar:
color: "#42bda0"
label: World

9 changes: 8 additions & 1 deletion internal/config/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@ var (
_ defaulter = (*UIConfig)(nil)
)

// UITopbar represents the configuration of a user interface top bar component.
type UITopbar struct {
Color string `json:"color" mapstructure:"color" yaml:"color"`
Label string `json:"label" mapstructure:"label" yaml:"label"`
}

// UIConfig contains fields, which control the behaviour
// of Flipt's user interface.
type UIConfig struct {
DefaultTheme UITheme `json:"defaultTheme" mapstructure:"default_theme" yaml:"default_theme"`
DefaultTheme UITheme `json:"defaultTheme" mapstructure:"default_theme" yaml:"default_theme"`
Topbar UITopbar `json:"topbar,omitempty" mapstructure:"topbar" yaml:"topbar,omitempty"`
}

func (c *UIConfig) setDefaults(v *viper.Viper) error {
Expand Down
13 changes: 12 additions & 1 deletion ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import formbricks from '@formbricks/js';
import { useEffect, useState } from 'react';
import { Helmet } from 'react-helmet';
import { useSelector } from 'react-redux';
import { selectConfig } from '~/app/meta/metaSlice';
import { selectCurrentNamespace } from '~/app/namespaces/namespacesSlice';
import { createHashRouter, redirect, RouterProvider } from 'react-router-dom';
import ErrorLayout from './app/ErrorLayout';
import Flag from './app/flags/Flag';
Expand Down Expand Up @@ -174,11 +176,20 @@ export default function App() {
}
}, [theme, systemPrefersDark]);

const { ui } = useSelector(selectConfig);

const namespace = useSelector(selectCurrentNamespace);

let title = `Flipt · ${namespace.key}`;
if (ui.topbar?.label) {
title = `Flipt/${ui.topbar.label} · ${namespace.key}`;
}

return (
<>
<Helmet>
<meta charSet="utf-8" />
<title>Flipt</title>
<title>{title}</title>
<link rel="icon" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script dangerouslySetInnerHTML={{ __html: nightwind.init() }} />
Expand Down
3 changes: 2 additions & 1 deletion ui/src/app/meta/metaSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ const initialState: IMetaSlice = {
readOnly: false
},
ui: {
defaultTheme: Theme.SYSTEM
defaultTheme: Theme.SYSTEM,
topbar: { color: '' }
},
analyticsEnabled: false
}
Expand Down
13 changes: 9 additions & 4 deletions ui/src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Bars3BottomLeftIcon } from '@heroicons/react/24/outline';
import { useSelector } from 'react-redux';
import { selectInfo, selectReadonly } from '~/app/meta/metaSlice';
import { selectConfig, selectInfo, selectReadonly } from '~/app/meta/metaSlice';
import { useSession } from '~/data/hooks/session';
import Notifications from './header/Notifications';
import ReadOnly from './header/ReadOnly';
import UserProfile from './header/UserProfile';

type HeaderProps = {
setSidebarOpen: (sidebarOpen: boolean) => void;
};
Expand All @@ -17,19 +16,25 @@ export default function Header(props: HeaderProps) {
const readOnly = useSelector(selectReadonly);

const { session } = useSession();
const { ui } = useSelector(selectConfig);
const topbarStyle = { backgroundColor: ui.topbar?.color };

return (
<div className="sticky top-0 z-20 flex h-16 flex-shrink-0 bg-gray-950 dark:border-b dark:border-b-white/20">
<div className="sticky top-0 z-20 flex h-16 flex-shrink-0 bg-gray-950 dark:border-b dark:border-b-white/20 dark:md:border-b-0">
<button
type="button"
className="without-ring nightwind-prevent text-white px-4 md:hidden"
style={topbarStyle}
onClick={() => setSidebarOpen(true)}
>
<span className="sr-only">Open sidebar</span>
<Bars3BottomLeftIcon className="h-6 w-6" aria-hidden="true" />
</button>

<div className="flex flex-1 justify-between px-4">
<div
className="top-0 flex flex-1 justify-between px-4"
style={topbarStyle}
>
<div className="flex flex-1" />
<div className="flex items-center">
{/* read-only mode */}
Expand Down
9 changes: 8 additions & 1 deletion ui/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { Fragment } from 'react';
import { Link } from 'react-router-dom';
import logoLight from '~/assets/logo-light.png';
import Nav from './Nav';
import { useSelector } from 'react-redux';
import { selectConfig } from '~/app/meta/metaSlice';

type SidebarProps = {
sidebarOpen: boolean;
Expand All @@ -13,6 +15,8 @@ type SidebarProps = {
export default function Sidebar(props: SidebarProps) {
const { sidebarOpen, setSidebarOpen } = props;

const { ui } = useSelector(selectConfig);
const topbarStyle = { backgroundColor: ui.topbar?.color };
return (
<>
<Transition.Root show={sidebarOpen} as={Fragment}>
Expand Down Expand Up @@ -94,7 +98,10 @@ export default function Sidebar(props: SidebarProps) {
{/* Static sidebar for desktop */}
<div className="hidden md:fixed md:inset-y-0 md:flex md:w-64 md:flex-col">
<div className="bg-gray-200 flex min-h-0 flex-1 flex-col">
<div className="relative flex h-16 flex-shrink-0 items-center bg-gray-950 px-4 pb-1 pt-2 dark:border-b dark:border-b-white/20">
<div
className="relative flex h-16 flex-shrink-0 items-center bg-gray-950 px-4 pb-1 pt-2 dark:border-b dark:border-b-white/20"
style={topbarStyle}
>
<Link to="/">
<img
src={logoLight}
Expand Down
6 changes: 6 additions & 0 deletions ui/src/types/Meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ export interface IStorage {
git?: IGit;
}

export interface ITopbar {
color?: string;
label?: string;
}

export interface IUI {
defaultTheme: Theme;
topbar: ITopbar;
}

export interface IConfig {
Expand Down

0 comments on commit 346cedf

Please sign in to comment.