005 - 앱 테마 커스터마이징하기
Backstage는 라이트/다크 모드 변형이 있는 기본 테마와 함께 제공돼요. 테마는 @backstage/theme 패키지의 일부로 제공되며, 기본 테마를 커스터마이징하거나 완전히 새로운 테마를 만드는 유틸리티도 포함돼요. 이 글에서는 사용자 정의 테마를 만들고 적용하는 방법을 설명드릴게요.
출처: 문서
본문
이 문서는 새 Backstage 앱에서 기본값으로 사용되는 새 프론트엔드 시스템을 기준으로 작성되었어요. 만약 Backstage 앱이 아직 이전 프론트엔드 시스템을 사용하고 있다면, 이 가이드의 이전 프론트엔드 시스템 버전을 읽어보세요.
Backstage는 라이트/다크 모드 변형이 있는 기본 테마와 함께 제공돼요. 테마는 @backstage/theme 패키지의 일부로 제공되며, 기본 테마를 커스터마이징하거나 완전히 새로운 테마를 만드는 유틸리티도 포함돼요.
사용자 정의 테마 만들기
새 테마를 만드는 가장 쉬운 방법은 @backstage/theme 패키지가 내보내는 createUnifiedTheme 함수를 사용하는 거예요. 이 함수를 사용해서 색상 팔레트와 글꼴 같은 기본 테마의 일부 기본 매개변수를 오버라이드할 수 있어요.
예를 들어 기본 라이트 테마를 기반으로 다음과 같이 새 테마를 만들 수 있어요:
packages/app/src/theme/myTheme.ts
import { createBaseThemeOptions, createUnifiedTheme, palettes,} from '@backstage/theme';export const myTheme = createUnifiedTheme({ ...createBaseThemeOptions({ palette: palettes.light, }), fontFamily: 'Comic Sans MS', defaultPageTheme: 'home',});
테마 파일을 배치할 packages/app/src 안에 theme 폴더를 만들어서 깔끔하게 정리하는 것을 권장해요.
@backstage/theme가 내보내는 BackstageTheme 유형과 일치하는 테마를 처음부터 만들 수도 있어요. 그 방법에 대한 자세한 내용은 Material UI 테마 문서를 참고하세요.
사용자 정의 테마 사용하기
새 프론트엔드 시스템에서 테마는 @backstage/plugin-app-react의 ThemeBlueprint를 사용해서 확장(extension)으로 설치돼요. 테마 확장은 프론트엔드 모듈로 묶인 다음 createApp에 전달돼요.
먼저 필요한 패키지를 설치해요:
Backstage 루트 디렉터리에서
yarn --cwd packages/app add @backstage/frontend-plugin-api @backstage/plugin-app-react
그런 다음 테마 확장을 만들고 앱에 설치해요:
packages/app/src/App.tsx
import { createApp } from '@backstage/frontend-defaults';import { createFrontendModule } from '@backstage/frontend-plugin-api';import { ThemeBlueprint } from '@backstage/plugin-app-react';import { UnifiedThemeProvider } from '@backstage/theme';import LightIcon from '@material-ui/icons/WbSunny';import { myTheme } from './theme/myTheme';const myThemeExtension = ThemeBlueprint.make({ name: 'my-theme', params: { theme: { id: 'my-theme', title: 'My Custom Theme', variant: 'light', icon: <LightIcon />, Provider: ({ children }) => ( <UnifiedThemeProvider theme={myTheme} children={children} /> ), }, },});const app = createApp({ features: [ createFrontendModule({ pluginId: 'app', extensions: [myThemeExtension], }), ],});export default app.createRoot();
여러분의 사용자 정의 테마 확장은 내장 라이트/다크 테마와 나란히 추가돼요. 사용자 정의 테마가 기본 테마를 대체하도록 하려면 app-config.yaml에서 기본 테마를 비활성화할 수 있어요:
app-config.yaml
app: extensions: - theme:app/light: false - theme:app/dark: false
사용자 정의 테마 예시
packages/app/src/theme/myTheme.ts
import { createBaseThemeOptions, createUnifiedTheme, genPageTheme, palettes, shapes,} from '@backstage/theme';export const myTheme = createUnifiedTheme({ ...createBaseThemeOptions({ palette: { ...palettes.light, primary: { main: '#343b58', }, secondary: { main: '#565a6e', }, error: { main: '#8c4351', }, warning: { main: '#8f5e15', }, info: { main: '#34548a', }, success: { main: '#485e30', }, background: { default: '#d5d6db', paper: '#d5d6db', }, banner: { info: '#34548a', error: '#8c4351', text: '#343b58', link: '#565a6e', }, errorBackground: '#8c4351', warningBackground: '#8f5e15', infoBackground: '#343b58', navigation: { background: '#343b58', indicator: '#8f5e15', color: '#d5d6db', selectedColor: '#ffffff', }, }, }), defaultPageTheme: 'home', fontFamily: 'Comic Sans MS', /* below drives the header colors */ pageTheme: { home: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.wave }), documentation: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.wave2, }), tool: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.round }), service: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.wave, }), website: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.wave, }), library: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.wave, }), other: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.wave }), app: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.wave }), apis: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.wave }), },});
Backstage와 Material UI 컴포넌트 오버라이드를 포함한 더 완전한 사용자 정의 테마 예시는 Backstage 데모 사이트의 Aperture 테마를 참고하세요.
사용자 정의 타이포그래피
사용자 정의 테마를 만들 때 기본 타이포그래피의 다양한 측면도 커스터마이징할 수 있어요. 단순화된 테마를 사용한 예시는 다음과 같아요:
packages/app/src/theme/myTheme.ts
import { createBaseThemeOptions, createUnifiedTheme, palettes,} from '@backstage/theme';export const myTheme = createUnifiedTheme({ ...createBaseThemeOptions({ palette: palettes.light, typography: { htmlFontSize: 16, fontFamily: 'Arial, sans-serif', h1: { fontSize: 54, fontWeight: 700, marginBottom: 10, }, h2: { fontSize: 40, fontWeight: 700, marginBottom: 8, }, h3: { fontSize: 32, fontWeight: 700, marginBottom: 6, }, h4: { fontWeight: 700, fontSize: 28, marginBottom: 6, }, h5: { fontWeight: 700, fontSize: 24, marginBottom: 4, }, h6: { fontWeight: 700, fontSize: 20, marginBottom: 2, }, }, defaultPageTheme: 'home', }),});
타이포그래피 설정의 일부만 오버라이드하고 싶다면, 예를 들어 h1만 오버라이드하려면 이렇게 하면 돼요:
packages/app/src/theme/myTheme.ts
import { createBaseThemeOptions, createUnifiedTheme, defaultTypography, palettes,} from '@backstage/theme';export const myTheme = createUnifiedTheme({ ...createBaseThemeOptions({ palette: palettes.light, typography: { ...defaultTypography, htmlFontSize: 16, fontFamily: 'Roboto, sans-serif', h1: { fontSize: 72, fontWeight: 700, marginBottom: 10, }, }, defaultPageTheme: 'home', }),});
사용자 정의 글꼴
사용자 정의 글꼴을 추가하려면 먼저 import할 수 있도록 글꼴을 저장해야 해요. 프론트엔드 애플리케이션의 src 폴더에 assets/fonts 디렉터리를 만드는 것을 제안해요.
그런 다음 Material UI Typography의 @font-face 구문에 따라 글꼴 스타일을 선언할 수 있어요.
그 다음 components 아래의 MuiCssBaseline의 styleOverrides를 활용해서 @font-face 배열에 글꼴을 추가할 수 있어요.
packages/app/src/theme/myTheme.ts
import MyCustomFont from '../assets/fonts/My-Custom-Font.woff2';const myCustomFont = { fontFamily: 'My-Custom-Font', fontStyle: 'normal', fontDisplay: 'swap', fontWeight: 300, src: ` local('My-Custom-Font'), url(${MyCustomFont}) format('woff2'), `,};export const myTheme = createUnifiedTheme({ fontFamily: 'My-Custom-Font', palette: palettes.light, components: { MuiCssBaseline: { styleOverrides: { '@font-face': [myCustomFont], }, }, },});
서로 다른 글꼴이나 여러 글꼴을 사용하려면 최상위 fontFamily를 본문에 원하는 것으로 설정하고, typography의 fontFamily를 오버라이드해서 다양한 제목의 글꼴을 제어하면 돼요.
packages/app/src/theme/myTheme.ts
import MyCustomFont from '../assets/fonts/My-Custom-Font.woff2';import myAwesomeFont from '../assets/fonts/My-Awesome-Font.woff2';const myCustomFont = { fontFamily: 'My-Custom-Font', fontStyle: 'normal', fontDisplay: 'swap', fontWeight: 300, src: ` local('My-Custom-Font'), url(${MyCustomFont}) format('woff2'), `,};const myAwesomeFont = { fontFamily: 'My-Awesome-Font', fontStyle: 'normal', fontDisplay: 'swap', fontWeight: 300, src: ` local('My-Awesome-Font'), url(${myAwesomeFont}) format('woff2'), `,};export const myTheme = createUnifiedTheme({ fontFamily: 'My-Custom-Font', components: { MuiCssBaseline: { styleOverrides: { '@font-face': [myCustomFont, myAwesomeFont], }, }, }, ...createBaseThemeOptions({ palette: palettes.light, typography: { ...defaultTypography, htmlFontSize: 16, fontFamily: 'My-Custom-Font', h1: { fontSize: 72, fontWeight: 700, marginBottom: 10, fontFamily: 'My-Awesome-Font', }, }, defaultPageTheme: 'home', }),});
Backstage 및 Material UI 컴포넌트 스타일 오버라이드하기
사용자 정의 테마를 만들 때는 테마 객체를 사용하는 컴포넌트의 CSS 규칙에 다른 값을 적용하게 돼요. 예를 들어 Backstage 컴포넌트의 스타일은 이렇게 보일 수 있어요:
const useStyles = makeStyles<BackstageTheme>( theme => ({ header: { padding: theme.spacing(3), boxShadow: '0 0 8px 3px rgba(20, 20, 20, 0.3)', backgroundImage: theme.page.backgroundImage, }, }), { name: 'BackstageHeader' },);
padding이 theme.spacing에서 값을 얻는 방식을 주목하세요. 즉 사용자 정의 테마에 spacing 값을 설정하면 이 컴포넌트의 padding 속성에 영향을 주고, theme.page.backgroundImage를 사용하는 backgroundImage도 마찬가지예요. 하지만 boxShadow 속성은 테마의 어떤 값도 참조하지 않아요. 즉 사용자 정의 테마를 만드는 것만으로는 box-shadow 속성을 바꾸거나 margin 같은 아직 정의되지 않은 css 규칙을 추가하기에 충분하지 않아요. 이런 경우에는 오버라이드도 만들어야 해요.
다음과 같이 하면 돼요:
packages/app/src/theme/myTheme.ts
import { createBaseThemeOptions, createUnifiedTheme, palettes,} from '@backstage/theme';export const myTheme = createUnifiedTheme({ ...createBaseThemeOptions({ palette: palettes.light, }), fontFamily: 'Comic Sans MS', defaultPageTheme: 'home', components: { BackstageHeader: { styleOverrides: { header: ({ theme }) => ({ width: 'auto', margin: '20px', boxShadow: 'none', borderBottom: `4px solid ${theme.palette.primary.main}`, }), }, }, },});
사용자 정의 로고
사용자 정의 테마 외에도, 사이트의 맨 왼쪽 위에 표시되는 로고도 커스터마이징할 수 있어요.
프론트엔드 앱에서 src/components/Root/ 폴더를 찾아요. 두 가지 컴포넌트를 찾을 수 있어요:
-
LogoFull.tsx- Sidebar 탐색이 열렸을 때 사용되는 더 큰 로고. -
LogoIcon.tsx- Sidebar 탐색이 닫혔을 때 사용되는 더 작은 로고.
이미지를 교체하려면 해당 컴포넌트의 관련 코드를 원시 SVG 정의로 간단히 교체하면 돼요.
PNG 같은 다른 웹 이미지 형식을 import해서 사용할 수도 있어요. 이렇게 하려면 새 이미지를 src/components/Root/logo/my-company-logo.png 같은 새 하위 디렉터리에 배치하고 이 코드를 추가해요:
import MyCustomLogoFull from './logo/my-company-logo.png';const LogoFull = () => { return <img src={MyCustomLogoFull} />;};
아이콘
지금까지 자신만의 테마를 만들고 로고를 추가하는 방법을 살펴봤어요. 다음 섹션들에서는 기존 아이콘을 오버라이드하고 아이콘을 더 추가하는 방법을 보여드릴게요.
사용자 정의 아이콘
@backstage/plugin-app-react의 IconBundleBlueprint를 사용해서 앱의 기본 아이콘을 커스터마이징할 수 있어요. 이것은 내장 아이콘을 오버라이드하는 확장을 만들어요.
packages/app/src/App.tsx
import { createApp } from '@backstage/frontend-defaults';import { createFrontendModule } from '@backstage/frontend-plugin-api';import { IconBundleBlueprint } from '@backstage/plugin-app-react';import { ExampleIcon } from './assets/customIcons';const customIconBundle = IconBundleBlueprint.make({ name: 'custom-icons', params: { icons: { github: ExampleIcon, }, },});const app = createApp({ features: [ createFrontendModule({ pluginId: 'app', extensions: [customIconBundle], }), ],});export default app.createRoot();
아이콘 추가하기
엔티티 링크 같은 다른 곳에서 사용할 수 있도록 추가 아이콘을 등록할 수 있어요. 예를 들어 alert 아이콘을 추가하려면:
packages/app/src/App.tsx
import { createApp } from '@backstage/frontend-defaults';import { createFrontendModule } from '@backstage/frontend-plugin-api';import AlarmIcon from '@material-ui/icons/Alarm';import { IconBundleBlueprint } from '@backstage/plugin-app-react';const extraIcons = IconBundleBlueprint.make({ name: 'extra-icons', params: { icons: { alert: AlarmIcon, }, },});const app = createApp({ features: [ createFrontendModule({ pluginId: 'app', extensions: [extraIcons], }), ],});export default app.createRoot();
그런 다음 엔티티 링크에서 아이콘을 이렇게 alert로 참조할 수 있어요:
apiVersion: backstage.io/v1alpha1kind: Componentmetadata: name: artist-lookup description: Artist Lookup links: - url: https://example.com/alert title: Alerts icon: alert
결과는 이렇게 보여요:
이 아이콘을 사용하는 또 다른 방법은 AppContext에서 이렇게 사용하는 거예요:
import { useApp } from '@backstage/core-plugin-api';const app = useApp();const alertIcon = app.getSystemIcon('alert');
여러 위치에서 사용하려는 아이콘이 있다면 이 방법을 사용하고 싶을 수 있어요.
아이콘이 기본 아이콘이나 추가한 아이콘 중 하나로 사용할 수 없으면 Material UI의 LanguageIcon으로 대체돼요.
사용자 정의 사이드바
새 프론트엔드 시스템에서 사이드바는 내장 app/nav 확장이 관리해요. NavContentBlueprint 확장을 만들어서 커스터마이징할 수 있어요. 하위 메뉴와 사용자 정의 그룹화가 있는 사용자 정의 사이드바 레이아웃을 만드는 자세한 지침은 사이드바 커스터마이징 문서를 참고하세요.
사용자 정의 홈페이지
사용자 정의 테마, 사용자 정의 로고 외에도 앱의 홈페이지를 커스터마이징할 수 있어요. 전체 가이드는 다음 페이지에서 읽을 수 있어요.
Material UI v5로 마이그레이션하기
이제 Backstage에서 Material UI v5를 지원해요. 시작하려면 마이그레이션 가이드를 확인해 보세요.