mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-11 22:49:54 +00:00
Add the new design system (#61524)
* Add the new design system * pnpm format * Fix session recordings not updating on theme changes * Remove color assertions
This commit is contained in:
@@ -21,11 +21,14 @@ const esModules = [
|
||||
'@exodus/bytes',
|
||||
'rettime',
|
||||
'@open-draft/deferred-promise',
|
||||
'@gravitational/design-system',
|
||||
].join('|');
|
||||
|
||||
/** @type {import('@jest/types').Config.InitialOptions} */
|
||||
module.exports = {
|
||||
...config,
|
||||
maxWorkers: '75%',
|
||||
workerIdleMemoryLimit: '1GB',
|
||||
globals: {
|
||||
electron: {},
|
||||
},
|
||||
|
||||
+5
-1
@@ -28,6 +28,8 @@
|
||||
"format": "oxfmt 'e/**/*.{ts,tsx,js,jsx,mts}' 'web/**/*.{ts,tsx,js,jsx,mts}' 'e2e/**/*.{ts,tsx,js,jsx,mts}'",
|
||||
"process-icons": "pnpm optimize-resource-icons && node web/packages/design/src/Icon/script/script.js && pnpm oxfmt 'web/packages/design/src/Icon/**/*.tsx'",
|
||||
"list-installed-versions": "./web/scripts/list-installed-versions.sh",
|
||||
"postinstall": "pnpm generate-theme",
|
||||
"generate-theme": "design-system typegen",
|
||||
"nop": "exit 0"
|
||||
},
|
||||
"private": true,
|
||||
@@ -70,6 +72,8 @@
|
||||
"@codemirror/lang-sql": "^6.10.0",
|
||||
"@codemirror/view": "^6.43.0",
|
||||
"@floating-ui/react": "^0.27.19",
|
||||
"@fontsource/ubuntu": "^5.2.8",
|
||||
"@gravitational/design-system": "https://github.com/gravitational/design-system/releases/download/v0.1.2/design-system.tgz",
|
||||
"@grpc/grpc-js": "1.14.4",
|
||||
"@hookform/resolvers": "^5.4.0",
|
||||
"@lezer/highlight": "^1.2.3",
|
||||
@@ -111,4 +115,4 @@
|
||||
"engines": {
|
||||
"node": "^24"
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+2595
-24
File diff suppressed because it is too large
Load Diff
@@ -50,5 +50,11 @@ shellEmulator: true
|
||||
packageImportMethod: clone-or-copy
|
||||
# Do not install packages released less than 2 days (2880 minutes) ago
|
||||
minimumReleaseAge: 2880
|
||||
# These are published as GitHub release tarballs rather than to the npm registry, so they have no
|
||||
# registry publish time for minimumReleaseAge to check and would otherwise fail the policy.
|
||||
minimumReleaseAgeExclude:
|
||||
- '@gravitational/design-system'
|
||||
- '@gravitational/swc-plugin-jsx-marker'
|
||||
- '@gravitational/swc-plugin-styled-components'
|
||||
# The pnpm team releases new versions pretty often, and we don't need to stay at the freshest version at all times.
|
||||
updateNotifier: false
|
||||
|
||||
+1
-3
@@ -8,9 +8,7 @@
|
||||
"web/**/*.ts",
|
||||
"web/**/*.tsx",
|
||||
"web/**/*.js",
|
||||
"web/**/*.jsx",
|
||||
"../web/src/**/*.ts",
|
||||
"../web/src/**/*.tsx"
|
||||
"web/**/*.jsx"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.node.json" },
|
||||
|
||||
@@ -44,14 +44,8 @@ const config: StorybookConfig = {
|
||||
},
|
||||
staticDirs: ['public'],
|
||||
addons: ['@storybook/addon-vitest'],
|
||||
viteFinal(config) {
|
||||
return {
|
||||
...config,
|
||||
server: {
|
||||
...config.server,
|
||||
allowedHosts: resolveAllowedHosts(),
|
||||
},
|
||||
};
|
||||
core: {
|
||||
allowedHosts: resolveAllowedHosts(),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
+50
-24
@@ -16,15 +16,26 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
BBLP_THEME,
|
||||
createThemeSystem,
|
||||
ThemeProvider as NewThemeProvider,
|
||||
TELEPORT_THEME,
|
||||
} from '@gravitational/design-system';
|
||||
import { Preview } from '@storybook/react-vite';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { http, HttpResponse } from 'msw';
|
||||
import { initialize, mswLoader } from 'msw-storybook-addon';
|
||||
import { ComponentType, PropsWithChildren } from 'react';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { sb } from 'storybook/test';
|
||||
|
||||
import Box from '../packages/design/src/Box';
|
||||
import { bblpTheme, darkTheme, lightTheme } from '../packages/design/src/theme';
|
||||
import {
|
||||
bblpTheme,
|
||||
darkTheme,
|
||||
lightTheme,
|
||||
resolveTheme,
|
||||
} from '../packages/design/src/theme';
|
||||
import { Theme } from '../packages/design/src/theme/themes/types';
|
||||
import { ConfiguredThemeProvider } from '../packages/design/src/ThemeProvider';
|
||||
import cfg from '../packages/teleport/src/config';
|
||||
@@ -85,33 +96,48 @@ interface ThemeDecoratorProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
function ThemeDecorator(props: PropsWithChildren<ThemeDecoratorProps>) {
|
||||
let ThemeProvider: ComponentType<PropsWithChildren<{ theme: Theme }>>;
|
||||
let theme = darkTheme;
|
||||
const teleportThemeSystem = createThemeSystem(TELEPORT_THEME.config);
|
||||
const bblpThemeSystem = createThemeSystem(BBLP_THEME.config);
|
||||
|
||||
function ThemeDecorator(props: PropsWithChildren<ThemeDecoratorProps>) {
|
||||
if (props.title.startsWith('Teleterm/')) {
|
||||
ThemeProvider = TeletermThemeProvider;
|
||||
theme =
|
||||
props.theme === 'Dark Theme' ? teletermDarkTheme : teletermLightTheme;
|
||||
} else {
|
||||
ThemeProvider = ConfiguredThemeProvider;
|
||||
switch (props.theme) {
|
||||
case 'Dark Theme':
|
||||
theme = darkTheme;
|
||||
break;
|
||||
case 'Light Theme':
|
||||
theme = lightTheme;
|
||||
break;
|
||||
case 'BBLP Theme':
|
||||
theme = bblpTheme;
|
||||
break;
|
||||
}
|
||||
const theme = resolveTheme(
|
||||
props.theme === 'Dark Theme' ? teletermDarkTheme : teletermLightTheme
|
||||
);
|
||||
|
||||
return (
|
||||
<TeletermThemeProvider theme={theme}>
|
||||
<Box p={3}>{props.children}</Box>
|
||||
</TeletermThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
let theme: Theme = resolveTheme(darkTheme);
|
||||
let system = teleportThemeSystem;
|
||||
let forcedTheme: 'light' | 'dark' | undefined = 'dark';
|
||||
|
||||
switch (props.theme) {
|
||||
case 'Dark Theme':
|
||||
theme = resolveTheme(darkTheme);
|
||||
forcedTheme = 'dark';
|
||||
break;
|
||||
case 'Light Theme':
|
||||
theme = resolveTheme(lightTheme);
|
||||
forcedTheme = 'light';
|
||||
break;
|
||||
case 'BBLP Theme':
|
||||
theme = resolveTheme(bblpTheme);
|
||||
system = bblpThemeSystem;
|
||||
forcedTheme = undefined;
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<Box p={3}>{props.children}</Box>
|
||||
</ThemeProvider>
|
||||
<NewThemeProvider system={system} forcedTheme={forcedTheme}>
|
||||
<ConfiguredThemeProvider theme={theme}>
|
||||
<Box p={3}>{props.children}</Box>
|
||||
</ConfiguredThemeProvider>
|
||||
</NewThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Vendored
+5
-2
@@ -17,12 +17,15 @@
|
||||
*/
|
||||
|
||||
import 'react';
|
||||
import type { LegacyThemeColors } from '@gravitational/design-system';
|
||||
import { CSSProp } from 'styled-components';
|
||||
|
||||
import { Theme } from 'design/theme/themes/types';
|
||||
import type { Theme } from 'design/theme/themes/types';
|
||||
|
||||
declare module 'styled-components' {
|
||||
export interface DefaultTheme extends Theme {}
|
||||
export interface DefaultTheme extends Theme {
|
||||
colors: LegacyThemeColors;
|
||||
}
|
||||
}
|
||||
|
||||
// https://styled-components.com/docs/api#usage-with-typescript
|
||||
|
||||
@@ -37,6 +37,8 @@ module.exports = {
|
||||
'^e-teleport/(.*)$': '<rootDir>/e/web/teleport/src/$1',
|
||||
'^gen-proto-js/(.*)$': '<rootDir>/gen/proto/js/$1',
|
||||
'^gen-proto-ts/(.*)$': '<rootDir>/gen/proto/ts/$1',
|
||||
'^@gravitational/design-system$':
|
||||
'<rootDir>/node_modules/@gravitational/design-system',
|
||||
},
|
||||
// Keep pre-v29 snapshot format to avoid existing snapshots breaking.
|
||||
// https://jestjs.io/docs/upgrading-to-jest29#snapshot-format
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
"@babel/preset-env": "^7.29.7",
|
||||
"@babel/preset-react": "^7.29.7",
|
||||
"@babel/preset-typescript": "^7.29.7",
|
||||
"@gravitational/swc-plugin-jsx-marker": "https://github.com/gravitational/design-system/releases/download/@gravitational/[email protected]/swc-plugin-jsx-marker.tgz",
|
||||
"@gravitational/swc-plugin-styled-components": "https://github.com/gravitational/design-system/releases/download/@gravitational/[email protected]/swc-plugin-styled-components.tgz",
|
||||
"@swc/core": "^1.15.40",
|
||||
"@swc/plugin-styled-components": "^12.10.0",
|
||||
"@types/jsdom": "^28.0.3",
|
||||
"@vitejs/plugin-react-swc": "^4.3.1",
|
||||
"babel-plugin-styled-components": "^2.3.0",
|
||||
|
||||
@@ -140,6 +140,27 @@ export function createViteConfig(
|
||||
},
|
||||
};
|
||||
|
||||
// When using a local design system, the browser will try to load the Ubuntu font from the
|
||||
// design system directory, which is outside the root directory.
|
||||
// These environment variables allow specifying a local design system path for development,
|
||||
// so Vite can serve the font files correctly.
|
||||
if (
|
||||
process.env.VITE_LOCAL_DESIGN_SYSTEM ||
|
||||
process.env.VITE_DESIGN_SYSTEM_DIR
|
||||
) {
|
||||
const designSystemPath = process.env.VITE_DESIGN_SYSTEM_DIR
|
||||
? resolve(rootDirectory, process.env.VITE_DESIGN_SYSTEM_DIR)
|
||||
: resolve(rootDirectory, '../design-system');
|
||||
|
||||
if (!existsSync(designSystemPath)) {
|
||||
throw new Error(
|
||||
`Could not find design system at ${designSystemPath}. Please ensure the path is correct.`
|
||||
);
|
||||
}
|
||||
|
||||
config.server.fs.allow.push(designSystemPath);
|
||||
}
|
||||
|
||||
if (process.env.VITE_ANALYZE_BUNDLE) {
|
||||
config.plugins.push(visualizer());
|
||||
}
|
||||
|
||||
@@ -26,8 +26,30 @@ import react from '@vitejs/plugin-react-swc';
|
||||
|
||||
/** @param {string} mode */
|
||||
export function reactPlugin(mode) {
|
||||
const plugins = [];
|
||||
|
||||
if (mode === 'development') {
|
||||
// swc-plugin-jsx-marker is a plugin for the new design system that adds
|
||||
// data-uic attributes to JSX elements so they are easier to detect in
|
||||
// devtools. It is only needed in development mode.
|
||||
plugins.push([
|
||||
'@gravitational/swc-plugin-jsx-marker',
|
||||
{
|
||||
libraryName: '@gravitational/design-system',
|
||||
styledFunction: 'styled',
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
// We use a custom SWC plugin here to stop styled-components from transforming
|
||||
// the CSS prop on the new design system components.
|
||||
plugins.push([
|
||||
'@gravitational/swc-plugin-styled-components',
|
||||
getStyledComponentsConfig(mode),
|
||||
]);
|
||||
|
||||
return react({
|
||||
plugins: [['@swc/plugin-styled-components', getStyledComponentsConfig(mode)]],
|
||||
plugins,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -41,6 +63,7 @@ function getStyledComponentsConfig(mode) {
|
||||
displayName: false,
|
||||
fileName: false,
|
||||
cssProp: true,
|
||||
cssPropIgnoreFromLibraries: ['@gravitational/design-system'],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,5 +73,6 @@ function getStyledComponentsConfig(mode) {
|
||||
displayName: true,
|
||||
fileName: true,
|
||||
cssProp: true,
|
||||
cssPropIgnoreFromLibraries: ['@gravitational/design-system'],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,30 +18,11 @@
|
||||
|
||||
import { MemoryRouter } from 'react-router';
|
||||
|
||||
import {
|
||||
CurrentPath,
|
||||
render,
|
||||
screen,
|
||||
theme,
|
||||
userEvent,
|
||||
} from 'design/utils/testing';
|
||||
import { CurrentPath, render, screen, userEvent } from 'design/utils/testing';
|
||||
|
||||
import { Alert, Banner } from '.';
|
||||
|
||||
describe('Alert', () => {
|
||||
test.each`
|
||||
kind | background
|
||||
${undefined} | ${theme.colors.interactive.tonal.danger[0]}
|
||||
${'neutral'} | ${theme.colors.interactive.tonal.neutral[0]}
|
||||
${'danger'} | ${theme.colors.interactive.tonal.danger[0]}
|
||||
${'warning'} | ${theme.colors.interactive.tonal.alert[0]}
|
||||
${'info'} | ${theme.colors.interactive.tonal.informational[0]}
|
||||
${'success'} | ${theme.colors.interactive.tonal.success[0]}
|
||||
`('renders appropriate background for kind $kind', ({ kind, background }) => {
|
||||
const { container } = render(<Alert kind={kind} />);
|
||||
expect(container.firstChild?.firstChild).toHaveStyle({ background });
|
||||
});
|
||||
|
||||
test('action buttons', async () => {
|
||||
const user = userEvent.setup();
|
||||
const primaryCallback = jest.fn();
|
||||
@@ -80,20 +61,6 @@ describe('Alert', () => {
|
||||
});
|
||||
|
||||
describe('Banner', () => {
|
||||
test.each`
|
||||
kind | background
|
||||
${undefined} | ${theme.colors.interactive.tonal.danger[2]}
|
||||
${'neutral'} | ${theme.colors.levels.elevated}
|
||||
${'primary'} | ${theme.colors.interactive.tonal.primary[2]}
|
||||
${'danger'} | ${theme.colors.interactive.tonal.danger[2]}
|
||||
${'warning'} | ${theme.colors.interactive.tonal.alert[2]}
|
||||
${'info'} | ${theme.colors.interactive.tonal.informational[2]}
|
||||
${'success'} | ${theme.colors.interactive.tonal.success[2]}
|
||||
`('renders appropriate background for kind $kind', ({ kind, background }) => {
|
||||
const { container } = render(<Banner kind={kind} />);
|
||||
expect(container.firstChild).toHaveStyle({ background });
|
||||
});
|
||||
|
||||
test('action buttons', async () => {
|
||||
const user = userEvent.setup();
|
||||
const primaryCallback = jest.fn();
|
||||
|
||||
@@ -22,64 +22,29 @@ import React, {
|
||||
PropsWithChildren,
|
||||
} from 'react';
|
||||
|
||||
import { render, screen, theme } from 'design/utils/testing';
|
||||
import { render, screen } from 'design/utils/testing';
|
||||
|
||||
import {
|
||||
Button,
|
||||
ButtonPrimary,
|
||||
ButtonSecondary,
|
||||
ButtonSize,
|
||||
ButtonWarning,
|
||||
} from './index';
|
||||
import { Button, ButtonPrimary, ButtonSize } from './index';
|
||||
|
||||
describe('design/Button', () => {
|
||||
it('renders a <button> and respects default "kind" prop == primary', () => {
|
||||
const { container } = render(<Button />);
|
||||
expect(container.firstChild?.nodeName).toBe('BUTTON');
|
||||
expect(container.firstChild).toHaveStyle({
|
||||
background: theme.colors.brand,
|
||||
});
|
||||
});
|
||||
|
||||
test('"kind" primary renders bg == theme.colors.buttons.primary.default', () => {
|
||||
const { container } = render(<ButtonPrimary />);
|
||||
expect(container.firstChild).toHaveStyle({
|
||||
background: theme.colors.buttons.primary.default,
|
||||
});
|
||||
});
|
||||
|
||||
test('"kind" secondary renders bg == theme.colors.buttons.secondary.default', () => {
|
||||
const { container } = render(<ButtonSecondary />);
|
||||
expect(container.firstChild).toHaveStyle({
|
||||
background: theme.colors.buttons.secondary.default,
|
||||
});
|
||||
});
|
||||
|
||||
test('"kind" warning renders bg == theme.colors.buttons.warning.default', () => {
|
||||
const { container } = render(<ButtonWarning />);
|
||||
expect(container.firstChild).toHaveStyle({
|
||||
background: theme.colors.buttons.warning.default,
|
||||
});
|
||||
});
|
||||
|
||||
test('"size" small renders min-height: 24px', () => {
|
||||
const { container } = render(<Button size="small" />);
|
||||
expect(container.firstChild).toHaveStyle({ 'min-height': '24px' });
|
||||
render(<Button size="small">Hi</Button>);
|
||||
expect(screen.getByRole('button')).toHaveStyle({ 'min-height': '24px' });
|
||||
});
|
||||
|
||||
test('"size" medium renders min-height: 32px', () => {
|
||||
const { container } = render(<Button size="medium" />);
|
||||
expect(container.firstChild).toHaveStyle('min-height: 32px');
|
||||
render(<Button size="medium">Hi</Button>);
|
||||
expect(screen.getByRole('button')).toHaveStyle('min-height: 32px');
|
||||
});
|
||||
|
||||
test('"size" large renders min-height: 40px', () => {
|
||||
const { container } = render(<Button size="large" />);
|
||||
expect(container.firstChild).toHaveStyle('min-height: 40px');
|
||||
render(<Button size="large">Hi</Button>);
|
||||
expect(screen.getByRole('button')).toHaveStyle('min-height: 40px');
|
||||
});
|
||||
|
||||
test('"block" prop renders width 100%', () => {
|
||||
const { container } = render(<Button block />);
|
||||
expect(container.firstChild).toHaveStyle('width: 100%');
|
||||
render(<Button block>Hi</Button>);
|
||||
expect(screen.getByRole('button')).toHaveStyle('width: 100%');
|
||||
});
|
||||
|
||||
describe('types and as prop', () => {
|
||||
|
||||
@@ -16,29 +16,28 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { render } from 'design/utils/testing';
|
||||
import { render, screen } from 'design/utils/testing';
|
||||
|
||||
import ButtonIcon from './index';
|
||||
|
||||
describe('design/ButtonIcon', () => {
|
||||
it('renders a <button> and respects default "size" to 1', () => {
|
||||
const { container } = render(<ButtonIcon />);
|
||||
expect(container.firstChild?.nodeName).toBe('BUTTON');
|
||||
expect(container.firstChild).toHaveStyle('font-size: 16px');
|
||||
render(<ButtonIcon />);
|
||||
expect(screen.getByRole('button')).toHaveStyle('font-size: 16px');
|
||||
});
|
||||
|
||||
test('"size" 0 maps to font-size 12px', () => {
|
||||
const { container } = render(<ButtonIcon size={0} />);
|
||||
expect(container.firstChild).toHaveStyle('font-size: 12px');
|
||||
render(<ButtonIcon size={0} />);
|
||||
expect(screen.getByRole('button')).toHaveStyle('font-size: 12px');
|
||||
});
|
||||
|
||||
test('"size" 1 maps to font-size 16px', () => {
|
||||
const { container } = render(<ButtonIcon size={1} />);
|
||||
expect(container.firstChild).toHaveStyle('font-size: 16px');
|
||||
render(<ButtonIcon size={1} />);
|
||||
expect(screen.getByRole('button')).toHaveStyle('font-size: 16px');
|
||||
});
|
||||
|
||||
test('"size" 2 maps to font-size 24px', () => {
|
||||
const { container } = render(<ButtonIcon size={2} />);
|
||||
expect(container.firstChild).toHaveStyle('font-size: 24px');
|
||||
render(<ButtonIcon size={2} />);
|
||||
expect(screen.getByRole('button')).toHaveStyle('font-size: 24px');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,18 +16,22 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { render } from 'design/utils/testing';
|
||||
import { render, screen } from 'design/utils/testing';
|
||||
|
||||
import ButtonLink from './index';
|
||||
|
||||
describe('design/ButtonLink', () => {
|
||||
it('respects the "as" prop', () => {
|
||||
const { container } = render(<ButtonLink />);
|
||||
expect(container.firstChild?.nodeName).toBe('A');
|
||||
render(<ButtonLink href="https://example.com">Hi</ButtonLink>);
|
||||
expect(screen.getByRole('link')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('respects the button size prop', () => {
|
||||
const { container } = render(<ButtonLink size="large" />);
|
||||
expect(container.firstChild).toHaveStyle('min-height: 40px');
|
||||
render(
|
||||
<ButtonLink href="https://example.com" size="large">
|
||||
Hi
|
||||
</ButtonLink>
|
||||
);
|
||||
expect(screen.getByRole('link')).toHaveStyle('min-height: 40px');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,9 +19,8 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
import Box from './../Box';
|
||||
import { darkTheme } from './../theme';
|
||||
|
||||
const Card = styled(Box).attrs(props => ({ theme: darkTheme, ...props }))`
|
||||
const Card = styled(Box)`
|
||||
box-shadow: ${props => props.theme.boxShadow[2]};
|
||||
border-radius: ${props => props.theme.radii[3]}px;
|
||||
background-color: ${props => props.theme.colors.levels.surface};
|
||||
|
||||
@@ -83,5 +83,8 @@ const DialogBox = styled.div<{
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100% - 96px);
|
||||
//TODO(gzdunek): Remove once all width overrides via dialogCss are updated for border-box sizing
|
||||
// (they should become 64px wider).
|
||||
box-sizing: content-box;
|
||||
${props => props.dialogCss?.(props)};
|
||||
`;
|
||||
|
||||
@@ -24,8 +24,8 @@ import { Indicator } from './Indicator';
|
||||
|
||||
describe('design/Indicator', () => {
|
||||
it('renders', async () => {
|
||||
const { container } = render(<Indicator delay={'none'} />);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
render(<Indicator delay={'none'} />);
|
||||
expect(screen.queryByTestId('indicator')).not.toBeInTheDocument();
|
||||
|
||||
await expect(screen.findByTestId('indicator')).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { render, screen, theme } from 'design/utils/testing';
|
||||
import { render, screen } from 'design/utils/testing';
|
||||
|
||||
import Input from './Input';
|
||||
|
||||
@@ -28,9 +28,6 @@ describe('design/Input', () => {
|
||||
});
|
||||
it('respects hasError prop', () => {
|
||||
render(<Input hasError={true} />);
|
||||
expect(screen.getByRole('textbox')).toHaveStyle({
|
||||
'border-color': theme.colors.interactive.solid.danger.default,
|
||||
});
|
||||
expect(screen.getByRole('graphics-symbol')).toHaveAttribute(
|
||||
'aria-label',
|
||||
'Error'
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Teleport
|
||||
* Copyright (C) 2023 Gravitational, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { render, theme } from 'design/utils/testing';
|
||||
|
||||
import Label, { Danger, Primary, Secondary, Warning } from './Label';
|
||||
|
||||
describe('design/Label', () => {
|
||||
const colors = [
|
||||
theme.colors.brand,
|
||||
theme.colors.spotBackground[0],
|
||||
theme.colors.warning.main,
|
||||
theme.colors.error.main,
|
||||
];
|
||||
|
||||
test.each`
|
||||
kind | Component | expected
|
||||
${'default: primary'} | ${Label} | ${colors[0]}
|
||||
${'primary'} | ${Primary} | ${colors[0]}
|
||||
${'secondary'} | ${Secondary} | ${colors[1]}
|
||||
${'warning'} | ${Warning} | ${colors[2]}
|
||||
${'danger'} | ${Danger} | ${colors[3]}
|
||||
`('component renders $kind label', ({ Component, expected }) => {
|
||||
const { container } = render(<Component />);
|
||||
expect(container.firstChild).toHaveStyle({
|
||||
'background-color': expected,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Teleport
|
||||
* Copyright (C) 2023 Gravitational, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { render, theme } from 'design/utils/testing';
|
||||
|
||||
import { LabelInput } from './LabelInput';
|
||||
|
||||
describe('design/LabelInput', () => {
|
||||
it('respects hasError prop', () => {
|
||||
const { container } = render(<LabelInput hasError={true} />);
|
||||
expect(container.firstChild).toHaveStyle({
|
||||
color: theme.colors.error.main,
|
||||
});
|
||||
});
|
||||
|
||||
it('respects default', () => {
|
||||
const { container } = render(<LabelInput />);
|
||||
expect(container.firstChild).toHaveStyle({
|
||||
color: theme.colors.light,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Teleport
|
||||
* Copyright (C) 2023 Gravitational, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { render, theme } from 'design/utils/testing';
|
||||
|
||||
import LabelState, {
|
||||
StateDanger,
|
||||
StateInfo,
|
||||
StateSuccess,
|
||||
StateWarning,
|
||||
} from './LabelState';
|
||||
|
||||
const colors = {
|
||||
primary: theme.colors.brand,
|
||||
info: theme.colors.spotBackground[0],
|
||||
warning: theme.colors.warning.main,
|
||||
danger: theme.colors.error.main,
|
||||
success: theme.colors.success.main,
|
||||
};
|
||||
|
||||
describe('design/LabelState', () => {
|
||||
test.each`
|
||||
Component | kind | expected
|
||||
${LabelState} | ${'primary'} | ${colors.primary}
|
||||
${StateInfo} | ${'secondary'} | ${colors.info}
|
||||
${StateWarning} | ${'warning'} | ${colors.warning}
|
||||
${StateDanger} | ${'danger'} | ${colors.danger}
|
||||
${StateSuccess} | ${'success'} | ${colors.success}
|
||||
`('respects kind prop set to $kind', ({ Component, expected }) => {
|
||||
const { container } = render(<Component />);
|
||||
expect(container.firstChild).toHaveStyle({
|
||||
background: expected,
|
||||
});
|
||||
|
||||
expect(getComputedStyle(container.firstChild).boxShadow).toBe('');
|
||||
});
|
||||
|
||||
it('respects shadow prop', () => {
|
||||
const { container } = render(<LabelState shadow={true} />);
|
||||
expect(getComputedStyle(container.firstChild).boxShadow).toEqual(
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
WidthProps,
|
||||
} from 'design/system';
|
||||
import { Theme } from 'design/theme/themes/types';
|
||||
import { fade } from 'design/theme/utils/colorManipulator';
|
||||
|
||||
export type LabelKind =
|
||||
| 'primary'
|
||||
@@ -76,8 +75,8 @@ const kinds = ({ theme, kind, shadow }: ThemedKindsProps) => {
|
||||
|
||||
if (shadow) {
|
||||
styles.boxShadow = `
|
||||
0 0 8px ${fade(styles.background, 0.24)},
|
||||
0 4px 16px ${fade(styles.background, 0.56)}
|
||||
0 0 8px color-mix(in srgb, ${styles.background} 24%, transparent),
|
||||
0 4px 16px color-mix(in srgb, ${styles.background} 56%, transparent)
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
*/
|
||||
|
||||
import { createGlobalStyle } from 'styled-components';
|
||||
|
||||
import './../assets/ubuntu/style.css';
|
||||
import '@gravitational/design-system/ubuntu.css';
|
||||
|
||||
const GlobalStyle = createGlobalStyle`
|
||||
|
||||
@@ -52,11 +51,14 @@ const GlobalStyle = createGlobalStyle`
|
||||
.name}; // this ensures Chrome's scrollbars are set to the right color depending on the theme
|
||||
}
|
||||
|
||||
// remove dotted Firefox outline
|
||||
button, a {
|
||||
outline: 0;
|
||||
::-moz-focus-inner {
|
||||
border: 0;
|
||||
@layer reset {
|
||||
// remove dotted Firefox outline
|
||||
button, a {
|
||||
outline: 0;
|
||||
|
||||
::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,90 +0,0 @@
|
||||
/**
|
||||
* Teleport
|
||||
* Copyright (C) 2023 Gravitational, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-family: 'Ubuntu2';
|
||||
src: url('./fonts/Ubuntu-Bold.woff2') format('woff2'),
|
||||
url('./fonts/Ubuntu-Bold.woff') format('woff'),
|
||||
url('./fonts/Ubuntu-Bold.ttf') format('truetype');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Ubuntu2';
|
||||
src: url('./fonts/Ubuntu-MediumItalic.woff2') format('woff2'),
|
||||
url('./fonts/Ubuntu-MediumItalic.woff') format('woff'),
|
||||
url('./fonts/Ubuntu-MediumItalic.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Ubuntu2';
|
||||
src: url('./fonts/Ubuntu-Regular.woff2') format('woff2'),
|
||||
url('./fonts/Ubuntu-Regular.woff') format('woff'),
|
||||
url('./fonts/Ubuntu-Regular.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Ubuntu2';
|
||||
src: url('./fonts/Ubuntu-BoldItalic.woff2') format('woff2'),
|
||||
url('./fonts/Ubuntu-BoldItalic.woff') format('woff'),
|
||||
url('./fonts/Ubuntu-BoldItalic.ttf') format('truetype');
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Ubuntu2';
|
||||
src: url('./fonts/Ubuntu-Light.woff2') format('woff2'),
|
||||
url('./fonts/Ubuntu-Light.woff') format('woff'),
|
||||
url('./fonts/Ubuntu-Light.ttf') format('truetype');
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Ubuntu2';
|
||||
src: url('./fonts/Ubuntu-LightItalic.woff2') format('woff2'),
|
||||
url('./fonts/Ubuntu-LightItalic.woff') format('woff'),
|
||||
url('./fonts/Ubuntu-LightItalic.ttf') format('truetype');
|
||||
font-weight: 300;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Ubuntu2';
|
||||
src: url('./fonts/Ubuntu-Medium.woff2') format('woff2'),
|
||||
url('./fonts/Ubuntu-Medium.woff') format('woff'),
|
||||
url('./fonts/Ubuntu-Medium.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Ubuntu2';
|
||||
src: url('./fonts/Ubuntu-Italic.woff2') format('woff2'),
|
||||
url('./fonts/Ubuntu-Italic.woff') format('woff'),
|
||||
url('./fonts/Ubuntu-Italic.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -24,3 +24,4 @@ export * from './themes/types';
|
||||
|
||||
export { darkTheme, lightTheme, bblpTheme };
|
||||
export { breakpointsPx } from './themes/sharedStyles';
|
||||
export { resolveTheme } from './themes/resolveTheme';
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type { LegacyThemeColors } from '@gravitational/design-system';
|
||||
import { useTheme } from 'styled-components';
|
||||
|
||||
import Box, { BoxProps } from '../Box';
|
||||
import Flex, { FlexProps } from '../Flex';
|
||||
import Link from '../Link';
|
||||
import Text, { H1, H2 } from '../Text';
|
||||
import { Theme, ThemeColors } from '../theme';
|
||||
import { Theme } from '../theme';
|
||||
|
||||
export default {
|
||||
title: 'Design/Theme/Colors',
|
||||
@@ -94,7 +95,7 @@ const ColorsComponent = () => {
|
||||
{Object.entries(theme.colors.interactive.tonal).map(
|
||||
([intent, colorGroup]) => (
|
||||
<Flex gap={4}>
|
||||
{colorGroup.map((background, i) => (
|
||||
{Object.entries(colorGroup).map(([i, background]) => (
|
||||
<SingleColorBox
|
||||
mb="2"
|
||||
path={`theme.colors.interactive.tonal.${intent}[${i}]`}
|
||||
@@ -307,7 +308,7 @@ function ColorsBox({
|
||||
themeType = undefined,
|
||||
...styles
|
||||
}: {
|
||||
colors: ThemeColors['levels'];
|
||||
colors: LegacyThemeColors['levels'];
|
||||
themeType?: string;
|
||||
} & FlexProps) {
|
||||
const list = Object.entries(colors).map(([key, colorsForKey]) => {
|
||||
|
||||
@@ -16,329 +16,17 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { lighten } from '../utils/colorManipulator';
|
||||
import { sharedColors, sharedStyles } from './sharedStyles';
|
||||
import { DataVisualisationColors, Theme, ThemeColors } from './types';
|
||||
import { sharedStyles } from './sharedStyles';
|
||||
import { type ThemeDefinition } from './types';
|
||||
|
||||
const dataVisualisationColors: DataVisualisationColors = {
|
||||
primary: {
|
||||
purple: '#9F85FF',
|
||||
wednesdays: '#F74DFF',
|
||||
picton: '#009EFF',
|
||||
sunflower: '#FFAB00',
|
||||
caribbean: '#00BFA6',
|
||||
abbey: '#FF6257',
|
||||
cyan: '#00D3F0',
|
||||
},
|
||||
secondary: {
|
||||
purple: '#7D59FF',
|
||||
wednesdays: '#D50DE0',
|
||||
picton: '#007CC9',
|
||||
sunflower: '#AC7400',
|
||||
caribbean: '#008775',
|
||||
abbey: '#DB3F34',
|
||||
cyan: '#009CB1',
|
||||
},
|
||||
tertiary: {
|
||||
purple: '#B9A6FF',
|
||||
wednesdays: '#FA96FF',
|
||||
picton: '#7BCDFF',
|
||||
sunflower: '#FFD98C',
|
||||
caribbean: '#2EFFD5',
|
||||
abbey: '#FF948D',
|
||||
cyan: '#74EEFF',
|
||||
},
|
||||
};
|
||||
|
||||
const levels = {
|
||||
deep: '#000000',
|
||||
|
||||
sunken: '#191919',
|
||||
|
||||
surface: '#232323',
|
||||
|
||||
elevated: '#282828',
|
||||
|
||||
popout: '#373737',
|
||||
};
|
||||
|
||||
const neutralColors = [
|
||||
'rgba(255,255,255,0.07)',
|
||||
'rgba(255,255,255,0.13)',
|
||||
'rgba(255,255,255,0.18)',
|
||||
];
|
||||
|
||||
const colors: ThemeColors = {
|
||||
...sharedColors,
|
||||
|
||||
levels,
|
||||
|
||||
spotBackground: neutralColors,
|
||||
|
||||
brand: '#FFA028',
|
||||
|
||||
interactive: {
|
||||
solid: {
|
||||
primary: {
|
||||
default: '#FFA028',
|
||||
hover: '#FFB04C',
|
||||
active: '#DB8922',
|
||||
},
|
||||
success: {
|
||||
default: '#00A223',
|
||||
hover: '#35D655',
|
||||
active: '#00851C',
|
||||
},
|
||||
accent: {
|
||||
default: '#66ABFF',
|
||||
hover: '#99C7FF',
|
||||
active: '#2B8EFF',
|
||||
},
|
||||
danger: {
|
||||
default: '#E51E3C',
|
||||
hover: '#FD2D4A',
|
||||
active: '#C31834',
|
||||
},
|
||||
alert: {
|
||||
default: '#FA5A28',
|
||||
hover: '#FB754C',
|
||||
active: '#D64D22',
|
||||
},
|
||||
},
|
||||
|
||||
tonal: {
|
||||
primary: [
|
||||
'rgba(255,160,40, 0.1)',
|
||||
'rgba(255,160,40, 0.18)',
|
||||
'rgba(255,160,40, 0.25)',
|
||||
],
|
||||
success: [
|
||||
'rgba(0, 162, 35, 0.1)',
|
||||
'rgba(0, 162, 35, 0.18)',
|
||||
'rgba(0, 162, 35, 0.25)',
|
||||
],
|
||||
// TODO rudream: update bblp interactive tonal colors.
|
||||
danger: [
|
||||
'rgba(255, 98, 87, 0.1)',
|
||||
'rgba(255, 98, 87, 0.18)',
|
||||
'rgba(255, 98, 87, 0.25)',
|
||||
],
|
||||
alert: [
|
||||
'rgba(255, 171, 0, 0.1)',
|
||||
'rgba(255, 171, 0, 0.18)',
|
||||
'rgba(255, 171, 0, 0.25)',
|
||||
],
|
||||
informational: [
|
||||
'rgba(0, 158, 255, 0.1)',
|
||||
'rgba(0, 158, 255, 0.18)',
|
||||
'rgba(0, 158, 255, 0.25)',
|
||||
],
|
||||
neutral: [neutralColors[0], neutralColors[1], neutralColors[2]],
|
||||
},
|
||||
},
|
||||
|
||||
text: {
|
||||
main: '#FFFFFF',
|
||||
slightlyMuted: '#BEBEBE',
|
||||
muted: '#8C8C8C',
|
||||
disabled: '#646464',
|
||||
primaryInverse: '#000000',
|
||||
},
|
||||
|
||||
buttons: {
|
||||
text: '#FFFFFF',
|
||||
textDisabled: 'rgba(255, 255, 255, 0.3)',
|
||||
bgDisabled: 'rgba(255, 255, 255, 0.12)',
|
||||
|
||||
primary: {
|
||||
text: '#000000',
|
||||
default: '#FFA028',
|
||||
hover: '#FFB04C',
|
||||
active: '#DB8922',
|
||||
},
|
||||
|
||||
secondary: {
|
||||
default: 'rgba(255,255,255,0.07)',
|
||||
hover: 'rgba(255,255,255,0.13)',
|
||||
active: 'rgba(255,255,255,0.18)',
|
||||
},
|
||||
|
||||
border: {
|
||||
default: 'rgba(255,255,255,0)',
|
||||
hover: 'rgba(255, 255, 255, 0.07)',
|
||||
active: 'rgba(255, 255, 255, 0.13)',
|
||||
border: 'rgba(255, 255, 255, 0.36)',
|
||||
},
|
||||
|
||||
warning: {
|
||||
text: '#FFFFFF',
|
||||
default: '#E51E3C',
|
||||
hover: '#FD2D4A',
|
||||
active: '#C31834',
|
||||
},
|
||||
|
||||
trashButton: {
|
||||
default: 'rgba(255, 255, 255, 0.07)',
|
||||
hover: 'rgba(255, 255, 255, 0.13)',
|
||||
},
|
||||
|
||||
link: {
|
||||
default: '#66ABFF',
|
||||
hover: '#99C7FF',
|
||||
active: '#2B8EFF',
|
||||
},
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
background: 'rgba(255, 255, 255, 0.8)',
|
||||
inverseBackground: 'rgba(0, 0, 0, 0.5)',
|
||||
inverseLinkDefault: '#0073BA',
|
||||
},
|
||||
|
||||
progressBarColor: '#00BFA5',
|
||||
|
||||
error: {
|
||||
main: '#E51E3C',
|
||||
hover: '#FD2D4A',
|
||||
active: '#C31834',
|
||||
},
|
||||
|
||||
warning: {
|
||||
main: '#FA5A28',
|
||||
hover: '#FB754C',
|
||||
active: '#D64D22',
|
||||
},
|
||||
|
||||
// TODO rudream: update bblp accent colors.
|
||||
accent: {
|
||||
main: 'rgba(0, 158, 255, 1)',
|
||||
hover: 'rgba(51, 177, 255, 1)',
|
||||
active: 'rgba(102, 197, 255, 1)',
|
||||
},
|
||||
|
||||
notice: {
|
||||
background: '#282828', // elevated
|
||||
},
|
||||
|
||||
action: {
|
||||
active: '#FFFFFF',
|
||||
hover: 'rgba(255, 255, 255, 0.1)',
|
||||
hoverOpacity: 0.1,
|
||||
selected: 'rgba(255, 255, 255, 0.2)',
|
||||
disabled: 'rgba(255, 255, 255, 0.3)',
|
||||
disabledBackground: 'rgba(255, 255, 255, 0.12)',
|
||||
},
|
||||
|
||||
terminal: {
|
||||
foreground: '#FFF',
|
||||
background: levels.sunken,
|
||||
selectionBackground: 'rgba(255, 255, 255, 0.18)',
|
||||
cursor: '#FFF',
|
||||
cursorAccent: levels.sunken,
|
||||
red: dataVisualisationColors.primary.abbey,
|
||||
green: dataVisualisationColors.primary.caribbean,
|
||||
yellow: dataVisualisationColors.primary.sunflower,
|
||||
blue: dataVisualisationColors.primary.picton,
|
||||
magenta: dataVisualisationColors.primary.purple,
|
||||
cyan: dataVisualisationColors.primary.cyan,
|
||||
brightWhite: lighten(levels.sunken, 0.89),
|
||||
white: lighten(levels.sunken, 0.78),
|
||||
brightBlack: lighten(levels.sunken, 0.61),
|
||||
black: '#000',
|
||||
brightRed: dataVisualisationColors.tertiary.abbey,
|
||||
brightGreen: dataVisualisationColors.tertiary.caribbean,
|
||||
brightYellow: dataVisualisationColors.tertiary.sunflower,
|
||||
brightBlue: dataVisualisationColors.tertiary.picton,
|
||||
brightMagenta: dataVisualisationColors.tertiary.purple,
|
||||
brightCyan: dataVisualisationColors.tertiary.cyan,
|
||||
searchMatch: '#FFD98C',
|
||||
activeSearchMatch: '#FFAB00',
|
||||
},
|
||||
|
||||
editor: {
|
||||
abbey: dataVisualisationColors.tertiary.abbey,
|
||||
purple: dataVisualisationColors.tertiary.purple,
|
||||
cyan: dataVisualisationColors.tertiary.cyan,
|
||||
picton: dataVisualisationColors.tertiary.picton,
|
||||
sunflower: dataVisualisationColors.tertiary.sunflower,
|
||||
caribbean: dataVisualisationColors.tertiary.caribbean,
|
||||
},
|
||||
|
||||
sessionRecording: {
|
||||
resource: '#66C5FF',
|
||||
user: '#C5B6FF',
|
||||
player: {
|
||||
progressBar: {
|
||||
background: 'rgba(255, 255, 255, 0.2)',
|
||||
seeking: 'rgba(255, 255, 255, 0.17)',
|
||||
progress: '#FFA028',
|
||||
},
|
||||
},
|
||||
riskLevels: {
|
||||
low: '#00A223',
|
||||
medium: '#FA5A28',
|
||||
high: '#FD2D4A',
|
||||
critical: '#E51E3C',
|
||||
},
|
||||
},
|
||||
|
||||
sessionRecordingTimeline: {
|
||||
background: levels.sunken,
|
||||
headerBackground: 'rgba(0, 0, 0, 0.13)',
|
||||
frameBorder: 'rgba(255, 255, 255, 0.2)',
|
||||
progressLine: '#E53E3E',
|
||||
border: {
|
||||
default: '#474747',
|
||||
hover: '#757575',
|
||||
},
|
||||
cursor: 'rgba(255, 255, 255, 0.4)',
|
||||
events: {
|
||||
inactivity: {
|
||||
background: 'rgba(159,133,255, 0.25)',
|
||||
text: 'rgba(255, 255, 255, 0.6)',
|
||||
},
|
||||
resize: {
|
||||
semiBackground: 'rgba(0, 0, 0, 0.8)',
|
||||
background: '#26323c',
|
||||
border: '#86c4ed',
|
||||
text: '#86c4ed',
|
||||
},
|
||||
join: {
|
||||
background: '#009EFF',
|
||||
text: 'rgba(0, 0, 0, 0.87)',
|
||||
},
|
||||
default: {
|
||||
background: 'rgba(255, 255, 255, 0.54)',
|
||||
text: '',
|
||||
},
|
||||
},
|
||||
timeMarks: {
|
||||
primary: '#E2E8F0',
|
||||
secondary: '#A0AEC0',
|
||||
absolute: '#E2E8F0',
|
||||
text: '#A0AEC0',
|
||||
},
|
||||
},
|
||||
|
||||
link: '#66ABFF',
|
||||
|
||||
highlightedNavigationItem: 'rgba(255, 255, 255, 0.3)',
|
||||
|
||||
success: {
|
||||
main: '#00A223',
|
||||
hover: '#35D655',
|
||||
active: '#00851C',
|
||||
},
|
||||
|
||||
dataVisualisation: dataVisualisationColors,
|
||||
};
|
||||
|
||||
const theme: Theme = {
|
||||
// Color tokens live in the design-system package now, e.g. for bblp:
|
||||
// https://github.com/gravitational/design-system/blob/main/src/themes/bblp/colors.ts
|
||||
// Only legacy styled-components shape lives here.
|
||||
const theme: ThemeDefinition = {
|
||||
...sharedStyles,
|
||||
name: 'bblp',
|
||||
type: 'dark',
|
||||
isCustomTheme: true,
|
||||
colors,
|
||||
};
|
||||
|
||||
export default theme;
|
||||
|
||||
@@ -16,331 +16,18 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { lighten } from '../utils/colorManipulator';
|
||||
import { sharedColors, sharedStyles } from './sharedStyles';
|
||||
import { DataVisualisationColors, Theme, ThemeColors } from './types';
|
||||
import { sharedStyles } from './sharedStyles';
|
||||
import { type ThemeDefinition } from './types';
|
||||
|
||||
/**
|
||||
* Used for the user icon in Connect (the top-right one).
|
||||
* In both the light and dark mode, the dark version of dataVisualisationColors is used.
|
||||
*/
|
||||
export const dataVisualisationColors: DataVisualisationColors = {
|
||||
primary: {
|
||||
purple: '#9F85FF',
|
||||
wednesdays: '#F74DFF',
|
||||
picton: '#009EFF',
|
||||
sunflower: '#FFAB00',
|
||||
caribbean: '#00BFA6',
|
||||
abbey: '#FF6257',
|
||||
cyan: '#00D3F0',
|
||||
},
|
||||
secondary: {
|
||||
purple: '#7D59FF',
|
||||
wednesdays: '#D50DE0',
|
||||
picton: '#007CC9',
|
||||
sunflower: '#AC7400',
|
||||
caribbean: '#008775',
|
||||
abbey: '#DB3F34',
|
||||
cyan: '#009CB1',
|
||||
},
|
||||
tertiary: {
|
||||
purple: '#B9A6FF',
|
||||
wednesdays: '#FA96FF',
|
||||
picton: '#7BCDFF',
|
||||
sunflower: '#FFD98C',
|
||||
caribbean: '#2EFFD5',
|
||||
abbey: '#FF948D',
|
||||
cyan: '#74EEFF',
|
||||
},
|
||||
};
|
||||
|
||||
const levels = {
|
||||
deep: '#000000',
|
||||
|
||||
sunken: '#0C143D',
|
||||
|
||||
surface: '#222C59',
|
||||
|
||||
elevated: '#344179',
|
||||
|
||||
popout: '#4A5688',
|
||||
};
|
||||
|
||||
const neutralColors = [
|
||||
'rgba(255,255,255,0.07)',
|
||||
'rgba(255,255,255,0.13)',
|
||||
'rgba(255,255,255,0.18)',
|
||||
];
|
||||
|
||||
const colors: ThemeColors = {
|
||||
...sharedColors,
|
||||
|
||||
levels,
|
||||
|
||||
spotBackground: neutralColors,
|
||||
|
||||
brand: '#9F85FF',
|
||||
|
||||
interactive: {
|
||||
solid: {
|
||||
primary: {
|
||||
default: '#9F85FF',
|
||||
hover: '#B29DFF',
|
||||
active: '#C5B6FF',
|
||||
},
|
||||
success: {
|
||||
default: '#00BFA6',
|
||||
hover: '#33CCB8',
|
||||
active: '#66D9CA',
|
||||
},
|
||||
accent: {
|
||||
default: '#009EFF',
|
||||
hover: '#33B1FF',
|
||||
active: '#66C5FF',
|
||||
},
|
||||
danger: {
|
||||
default: '#FF6257',
|
||||
hover: '#FF8179',
|
||||
active: '#FFA19A',
|
||||
},
|
||||
alert: {
|
||||
default: '#FFAB00',
|
||||
hover: '#FFBC33',
|
||||
active: '#FFCD66',
|
||||
},
|
||||
},
|
||||
|
||||
tonal: {
|
||||
primary: [
|
||||
'rgba(159,133,255, 0.1)',
|
||||
'rgba(159,133,255, 0.18)',
|
||||
'rgba(159,133,255, 0.25)',
|
||||
],
|
||||
success: [
|
||||
'rgba(0, 191, 166, 0.1)',
|
||||
'rgba(0, 191, 166, 0.18)',
|
||||
'rgba(0, 191, 166, 0.25)',
|
||||
],
|
||||
danger: [
|
||||
'rgba(255, 98, 87, 0.1)',
|
||||
'rgba(255, 98, 87, 0.18)',
|
||||
'rgba(255, 98, 87, 0.25)',
|
||||
],
|
||||
alert: [
|
||||
'rgba(255, 171, 0, 0.1)',
|
||||
'rgba(255, 171, 0, 0.18)',
|
||||
'rgba(255, 171, 0, 0.25)',
|
||||
],
|
||||
informational: [
|
||||
'rgba(0, 158, 255, 0.1)',
|
||||
'rgba(0, 158, 255, 0.18)',
|
||||
'rgba(0, 158, 255, 0.25)',
|
||||
],
|
||||
neutral: [neutralColors[0], neutralColors[1], neutralColors[2]],
|
||||
},
|
||||
},
|
||||
|
||||
text: {
|
||||
main: '#FFFFFF',
|
||||
slightlyMuted: 'rgba(255, 255, 255, 0.72)',
|
||||
muted: 'rgba(255, 255, 255, 0.54)',
|
||||
disabled: 'rgba(255, 255, 255, 0.36)',
|
||||
primaryInverse: '#000000',
|
||||
},
|
||||
|
||||
buttons: {
|
||||
text: '#FFFFFF',
|
||||
textDisabled: 'rgba(255, 255, 255, 0.3)',
|
||||
bgDisabled: 'rgba(255, 255, 255, 0.12)',
|
||||
|
||||
primary: {
|
||||
text: '#000000',
|
||||
default: '#9F85FF',
|
||||
hover: '#B29DFF',
|
||||
active: '#C5B6FF',
|
||||
},
|
||||
|
||||
secondary: {
|
||||
default: 'rgba(255,255,255,0.07)',
|
||||
hover: 'rgba(255,255,255,0.13)',
|
||||
active: 'rgba(255,255,255,0.18)',
|
||||
},
|
||||
|
||||
border: {
|
||||
default: 'rgba(255,255,255,0)',
|
||||
hover: 'rgba(255, 255, 255, 0.07)',
|
||||
active: 'rgba(255, 255, 255, 0.13)',
|
||||
border: 'rgba(255, 255, 255, 0.36)',
|
||||
},
|
||||
|
||||
warning: {
|
||||
text: '#000000',
|
||||
default: '#FF6257',
|
||||
hover: '#FF8179',
|
||||
active: '#FFA19A',
|
||||
},
|
||||
|
||||
trashButton: {
|
||||
default: 'rgba(255, 255, 255, 0.07)',
|
||||
hover: 'rgba(255, 255, 255, 0.13)',
|
||||
},
|
||||
|
||||
link: {
|
||||
default: '#009EFF',
|
||||
hover: '#33B1FF',
|
||||
active: '#66C5FF',
|
||||
},
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
background: 'rgba(255, 255, 255, 0.8)',
|
||||
inverseBackground: 'rgba(0, 0, 0, 0.5)',
|
||||
inverseLinkDefault: '#0073BA',
|
||||
},
|
||||
|
||||
progressBarColor: '#00BFA5',
|
||||
|
||||
error: {
|
||||
main: '#FF6257',
|
||||
hover: '#FF8179',
|
||||
active: '#FFA19A',
|
||||
},
|
||||
|
||||
success: {
|
||||
main: '#00BFA6',
|
||||
hover: '#33CCB8',
|
||||
active: '#66D9CA',
|
||||
},
|
||||
|
||||
warning: {
|
||||
main: '#FFAB00',
|
||||
hover: '#FFBC33',
|
||||
active: '#FFCD66',
|
||||
},
|
||||
|
||||
accent: {
|
||||
main: 'rgba(0, 158, 255, 1)',
|
||||
hover: 'rgba(51, 177, 255, 1)',
|
||||
active: 'rgba(102, 197, 255, 1)',
|
||||
},
|
||||
|
||||
notice: {
|
||||
background: '#344179', // elevated
|
||||
},
|
||||
|
||||
action: {
|
||||
active: '#FFFFFF',
|
||||
hover: 'rgba(255, 255, 255, 0.1)',
|
||||
hoverOpacity: 0.1,
|
||||
selected: 'rgba(255, 255, 255, 0.2)',
|
||||
disabled: 'rgba(255, 255, 255, 0.3)',
|
||||
disabledBackground: 'rgba(255, 255, 255, 0.12)',
|
||||
},
|
||||
|
||||
terminal: {
|
||||
foreground: '#FFF',
|
||||
background: levels.sunken,
|
||||
selectionBackground: 'rgba(255, 255, 255, 0.18)',
|
||||
cursor: '#FFF',
|
||||
cursorAccent: levels.sunken,
|
||||
red: dataVisualisationColors.primary.abbey,
|
||||
green: dataVisualisationColors.primary.caribbean,
|
||||
yellow: dataVisualisationColors.primary.sunflower,
|
||||
blue: dataVisualisationColors.primary.picton,
|
||||
magenta: dataVisualisationColors.primary.purple,
|
||||
cyan: dataVisualisationColors.primary.cyan,
|
||||
brightWhite: lighten(levels.sunken, 0.89),
|
||||
white: lighten(levels.sunken, 0.78),
|
||||
brightBlack: lighten(levels.sunken, 0.61),
|
||||
black: '#000',
|
||||
brightRed: dataVisualisationColors.tertiary.abbey,
|
||||
brightGreen: dataVisualisationColors.tertiary.caribbean,
|
||||
brightYellow: dataVisualisationColors.tertiary.sunflower,
|
||||
brightBlue: dataVisualisationColors.tertiary.picton,
|
||||
brightMagenta: dataVisualisationColors.tertiary.purple,
|
||||
brightCyan: dataVisualisationColors.tertiary.cyan,
|
||||
searchMatch: '#FFD98C',
|
||||
activeSearchMatch: '#FFAB00',
|
||||
},
|
||||
|
||||
editor: {
|
||||
abbey: dataVisualisationColors.tertiary.abbey,
|
||||
purple: dataVisualisationColors.tertiary.purple,
|
||||
cyan: dataVisualisationColors.tertiary.cyan,
|
||||
picton: dataVisualisationColors.tertiary.picton,
|
||||
sunflower: dataVisualisationColors.tertiary.sunflower,
|
||||
caribbean: dataVisualisationColors.tertiary.caribbean,
|
||||
},
|
||||
|
||||
sessionRecording: {
|
||||
resource: '#66C5FF',
|
||||
user: '#C5B6FF',
|
||||
player: {
|
||||
progressBar: {
|
||||
background: 'rgba(255, 255, 255, 0.2)',
|
||||
seeking: 'rgba(255, 255, 255, 0.17)',
|
||||
progress: '#9F85FF',
|
||||
},
|
||||
},
|
||||
riskLevels: {
|
||||
low: '#00BFA6',
|
||||
medium: '#FFAB00',
|
||||
high: '#FF8179',
|
||||
critical: '#FF6257',
|
||||
},
|
||||
},
|
||||
|
||||
sessionRecordingTimeline: {
|
||||
background: '#1f2549',
|
||||
headerBackground: 'rgba(0, 0, 0, 0.13)',
|
||||
frameBorder: 'rgba(255, 255, 255, 0.2)',
|
||||
progressLine: '#E53E3E',
|
||||
border: {
|
||||
default: '#4c516e',
|
||||
hover: '#5f659e',
|
||||
},
|
||||
cursor: 'rgba(255, 255, 255, 0.4)',
|
||||
events: {
|
||||
inactivity: {
|
||||
background: 'rgba(159,133,255, 0.25)',
|
||||
text: 'rgba(255, 255, 255, 0.6)',
|
||||
},
|
||||
resize: {
|
||||
semiBackground: 'rgba(0, 0, 0, 0.8)',
|
||||
background: '#26323c',
|
||||
border: '#86c4ed',
|
||||
text: '#86c4ed',
|
||||
},
|
||||
join: {
|
||||
background: '#009EFF',
|
||||
text: 'rgba(0, 0, 0, 0.87)',
|
||||
},
|
||||
default: {
|
||||
background: 'rgba(255, 255, 255, 0.54)',
|
||||
text: '',
|
||||
},
|
||||
},
|
||||
timeMarks: {
|
||||
primary: '#718096',
|
||||
secondary: '#4A5568',
|
||||
absolute: '#E2E8F0',
|
||||
text: '#A0AEC0',
|
||||
},
|
||||
},
|
||||
|
||||
link: '#009EFF',
|
||||
|
||||
highlightedNavigationItem: 'rgba(255, 255, 255, 0.3)',
|
||||
|
||||
dataVisualisation: dataVisualisationColors,
|
||||
};
|
||||
|
||||
const theme: Theme = {
|
||||
// Color tokens live in the design-system package now, e.g. for teleport
|
||||
// (dark + light):
|
||||
// https://github.com/gravitational/design-system/blob/main/src/themes/teleport/colors.ts
|
||||
// Only legacy styled-components shape lives here.
|
||||
const theme: ThemeDefinition = {
|
||||
...sharedStyles,
|
||||
name: 'dark',
|
||||
type: 'dark',
|
||||
isCustomTheme: false,
|
||||
colors,
|
||||
};
|
||||
|
||||
export default theme;
|
||||
|
||||
@@ -16,328 +16,18 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { blue } from '../palette';
|
||||
import { darken } from '../utils/colorManipulator';
|
||||
import { sharedColors, sharedStyles } from './sharedStyles';
|
||||
import { DataVisualisationColors, Theme, ThemeColors } from './types';
|
||||
import { sharedStyles } from './sharedStyles';
|
||||
import { type ThemeDefinition } from './types';
|
||||
|
||||
const dataVisualisationColors: DataVisualisationColors = {
|
||||
primary: {
|
||||
purple: '#5531D4',
|
||||
wednesdays: '#A70DAF',
|
||||
picton: '#006BB8',
|
||||
sunflower: '#8F5F00',
|
||||
caribbean: '#007562',
|
||||
abbey: '#BF372E',
|
||||
cyan: '#007282',
|
||||
},
|
||||
secondary: {
|
||||
purple: '#6F4CED',
|
||||
wednesdays: '#DC37E5',
|
||||
picton: '#0089DE',
|
||||
sunflower: '#B27800',
|
||||
caribbean: '#009681',
|
||||
abbey: '#D4635B',
|
||||
cyan: '#1792A3',
|
||||
},
|
||||
tertiary: {
|
||||
purple: '#3D1BB2',
|
||||
wednesdays: '#690274',
|
||||
picton: '#004B89',
|
||||
sunflower: '#704B00',
|
||||
caribbean: '#005742',
|
||||
abbey: '#9D0A00',
|
||||
cyan: '#015C6E',
|
||||
},
|
||||
};
|
||||
|
||||
const levels = {
|
||||
deep: '#E6E9EA',
|
||||
|
||||
sunken: '#F1F2F4',
|
||||
|
||||
surface: '#FBFBFC',
|
||||
|
||||
elevated: '#FFFFFF',
|
||||
|
||||
popout: '#FFFFFF',
|
||||
};
|
||||
|
||||
const neutralColors = [
|
||||
'rgba(0,0,0,0.06)',
|
||||
'rgba(0,0,0,0.13)',
|
||||
'rgba(0,0,0,0.18)',
|
||||
];
|
||||
|
||||
const colors: ThemeColors = {
|
||||
...sharedColors,
|
||||
|
||||
levels,
|
||||
|
||||
spotBackground: neutralColors,
|
||||
|
||||
brand: '#512FC9',
|
||||
|
||||
interactive: {
|
||||
solid: {
|
||||
primary: {
|
||||
default: '#512FC9',
|
||||
hover: '#4126A1',
|
||||
active: '#311C79',
|
||||
},
|
||||
success: {
|
||||
default: '#007D6B',
|
||||
hover: '#006456',
|
||||
active: '#004B40',
|
||||
},
|
||||
accent: {
|
||||
default: '#0073BA',
|
||||
hover: '#005C95',
|
||||
active: '#004570',
|
||||
},
|
||||
danger: {
|
||||
default: '#CC372D',
|
||||
hover: '#A32C24',
|
||||
active: '#7A211B',
|
||||
},
|
||||
alert: {
|
||||
default: '#FFAB00',
|
||||
hover: '#CC8900',
|
||||
active: '#996700',
|
||||
},
|
||||
},
|
||||
|
||||
tonal: {
|
||||
primary: [
|
||||
'rgba(81,47,201, 0.1)',
|
||||
'rgba(81,47,201, 0.18)',
|
||||
'rgba(81,47,201, 0.25)',
|
||||
],
|
||||
success: [
|
||||
'rgba(0, 125, 107, 0.1)',
|
||||
'rgba(0, 125, 107, 0.18)',
|
||||
'rgba(0, 125, 107, 0.25)',
|
||||
],
|
||||
danger: [
|
||||
'rgba(204, 55, 45, 0.1)',
|
||||
'rgba(204, 55, 45, 0.18)',
|
||||
'rgba(204, 55, 45, 0.25)',
|
||||
],
|
||||
alert: [
|
||||
'rgba(255, 171, 0, 0.1)',
|
||||
'rgba(255, 171, 0, 0.18)',
|
||||
'rgba(255, 171, 0, 0.25)',
|
||||
],
|
||||
informational: [
|
||||
'rgba(0, 115, 186, 0.1)',
|
||||
'rgba(0, 115, 186, 0.18)',
|
||||
'rgba(0, 115, 186, 0.25)',
|
||||
],
|
||||
neutral: [neutralColors[0], neutralColors[1], neutralColors[2]],
|
||||
},
|
||||
},
|
||||
|
||||
text: {
|
||||
main: '#000000',
|
||||
slightlyMuted: 'rgba(0,0,0,0.72)',
|
||||
muted: 'rgba(0,0,0,0.54)',
|
||||
disabled: 'rgba(0,0,0,0.36)',
|
||||
primaryInverse: '#FFFFFF',
|
||||
},
|
||||
|
||||
buttons: {
|
||||
text: '#000000',
|
||||
textDisabled: 'rgba(0,0,0,0.3)',
|
||||
bgDisabled: 'rgba(0,0,0,0.12)',
|
||||
|
||||
primary: {
|
||||
text: '#FFFFFF',
|
||||
default: '#512FC9',
|
||||
hover: '#4126A1',
|
||||
active: '#311C79',
|
||||
},
|
||||
|
||||
secondary: {
|
||||
default: 'rgba(0,0,0,0.07)',
|
||||
hover: 'rgba(0,0,0,0.13)',
|
||||
active: 'rgba(0,0,0,0.18)',
|
||||
},
|
||||
|
||||
border: {
|
||||
default: 'rgba(255,255,255,0)',
|
||||
hover: 'rgba(0,0,0,0.07)',
|
||||
active: 'rgba(0,0,0,0.13)',
|
||||
border: 'rgba(0,0,0,0.36)',
|
||||
},
|
||||
|
||||
warning: {
|
||||
text: '#FFFFFF',
|
||||
default: '#CC372D',
|
||||
hover: '#A32C24',
|
||||
active: '#7A211B',
|
||||
},
|
||||
|
||||
trashButton: {
|
||||
default: 'rgba(0,0,0,0.07)',
|
||||
hover: 'rgba(0,0,0,0.13)',
|
||||
},
|
||||
|
||||
link: {
|
||||
default: '#0073BA',
|
||||
hover: '#005C95',
|
||||
active: '#004570',
|
||||
},
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
background: 'rgba(0, 0, 0, 0.80)',
|
||||
inverseBackground: 'rgba(255, 255, 255, 0.5)',
|
||||
inverseLinkDefault: '#009EFF',
|
||||
},
|
||||
|
||||
progressBarColor: '#007D6B',
|
||||
|
||||
success: {
|
||||
main: '#007D6B',
|
||||
hover: '#006456',
|
||||
active: '#004B40',
|
||||
},
|
||||
|
||||
error: {
|
||||
main: '#CC372D',
|
||||
hover: '#A32C24',
|
||||
active: '#7A211B',
|
||||
},
|
||||
|
||||
warning: {
|
||||
main: '#FFAB00',
|
||||
hover: '#CC8900',
|
||||
active: '#996700',
|
||||
},
|
||||
|
||||
accent: {
|
||||
main: 'rgba(0, 115, 186, 1)',
|
||||
hover: 'rgba(0, 92, 149, 1)',
|
||||
active: 'rgba(0, 69, 112, 1)',
|
||||
},
|
||||
|
||||
notice: {
|
||||
background: blue[50],
|
||||
},
|
||||
|
||||
action: {
|
||||
active: '#FFFFFF',
|
||||
hover: 'rgba(255, 255, 255, 0.1)',
|
||||
hoverOpacity: 0.1,
|
||||
selected: 'rgba(255, 255, 255, 0.2)',
|
||||
disabled: 'rgba(255, 255, 255, 0.3)',
|
||||
disabledBackground: 'rgba(255, 255, 255, 0.12)',
|
||||
},
|
||||
|
||||
terminal: {
|
||||
foreground: '#000',
|
||||
background: levels.sunken,
|
||||
selectionBackground: 'rgba(0, 0, 0, 0.18)',
|
||||
cursor: '#000',
|
||||
cursorAccent: levels.sunken,
|
||||
red: dataVisualisationColors.tertiary.abbey,
|
||||
green: dataVisualisationColors.tertiary.caribbean,
|
||||
yellow: dataVisualisationColors.tertiary.sunflower,
|
||||
blue: dataVisualisationColors.tertiary.picton,
|
||||
magenta: dataVisualisationColors.tertiary.purple,
|
||||
cyan: dataVisualisationColors.tertiary.cyan,
|
||||
brightWhite: darken(levels.sunken, 0.55),
|
||||
white: darken(levels.sunken, 0.68),
|
||||
brightBlack: darken(levels.sunken, 0.8),
|
||||
black: '#000',
|
||||
brightRed: dataVisualisationColors.primary.abbey,
|
||||
brightGreen: dataVisualisationColors.primary.caribbean,
|
||||
brightYellow: dataVisualisationColors.primary.sunflower,
|
||||
brightBlue: dataVisualisationColors.primary.picton,
|
||||
brightMagenta: dataVisualisationColors.primary.purple,
|
||||
brightCyan: dataVisualisationColors.primary.cyan,
|
||||
searchMatch: '#FFD98C',
|
||||
activeSearchMatch: '#FFAB00',
|
||||
},
|
||||
|
||||
editor: {
|
||||
abbey: dataVisualisationColors.tertiary.abbey,
|
||||
purple: dataVisualisationColors.tertiary.purple,
|
||||
cyan: dataVisualisationColors.tertiary.cyan,
|
||||
picton: dataVisualisationColors.tertiary.picton,
|
||||
sunflower: dataVisualisationColors.tertiary.sunflower,
|
||||
caribbean: dataVisualisationColors.tertiary.caribbean,
|
||||
},
|
||||
|
||||
sessionRecording: {
|
||||
resource: '#004570',
|
||||
user: '#311C79',
|
||||
player: {
|
||||
progressBar: {
|
||||
background: 'rgba(0, 0, 0, 0.1)',
|
||||
seeking: 'rgba(0, 0, 0, 0.15)',
|
||||
progress: '#9F85FF',
|
||||
},
|
||||
},
|
||||
riskLevels: {
|
||||
low: '#007D6B',
|
||||
medium: '#FFAB00',
|
||||
high: '#CC372D',
|
||||
critical: '#A32C24',
|
||||
},
|
||||
},
|
||||
|
||||
sessionRecordingTimeline: {
|
||||
background: levels.deep,
|
||||
headerBackground: 'rgba(0, 0, 0, 0.05)',
|
||||
frameBorder: 'rgba(0, 0, 0, 0.2)',
|
||||
progressLine: '#E53E3E',
|
||||
border: {
|
||||
default: '#9a9dab',
|
||||
hover: '#5f659e',
|
||||
},
|
||||
cursor: 'rgba(0, 0, 0, 0.4)',
|
||||
events: {
|
||||
inactivity: {
|
||||
background: 'rgba(81,47,201, 0.25)',
|
||||
text: 'rgba(0, 0, 0, 0.6)',
|
||||
},
|
||||
resize: {
|
||||
semiBackground: 'rgba(0, 0, 0, 0.8)',
|
||||
border: '#26323c',
|
||||
background: '#86c4ed',
|
||||
text: '#26323c',
|
||||
},
|
||||
join: {
|
||||
background: '#0073BA',
|
||||
text: 'rgba(255, 255, 255, 0.8)',
|
||||
},
|
||||
default: {
|
||||
background: 'rgba(0, 0, 0, 0.54)',
|
||||
text: '#000',
|
||||
},
|
||||
},
|
||||
timeMarks: {
|
||||
primary: 'rgba(0,0,0,0.54)',
|
||||
secondary: 'rgba(0,0,0,0.36)',
|
||||
absolute: 'rgba(0,0,0,0.87)',
|
||||
text: 'rgba(0,0,0,0.87)',
|
||||
},
|
||||
},
|
||||
|
||||
link: '#0073BA',
|
||||
|
||||
highlightedNavigationItem: blue[200],
|
||||
|
||||
dataVisualisation: dataVisualisationColors,
|
||||
};
|
||||
|
||||
const theme: Theme = {
|
||||
// Color tokens live in the design-system package now, e.g. for teleport
|
||||
// (dark + light):
|
||||
// https://github.com/gravitational/design-system/blob/main/src/themes/teleport/colors.ts
|
||||
// Only legacy styled-components shape lives here.
|
||||
const theme: ThemeDefinition = {
|
||||
...sharedStyles,
|
||||
name: 'light',
|
||||
type: 'light',
|
||||
isCustomTheme: false,
|
||||
colors,
|
||||
};
|
||||
|
||||
export default theme;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Teleport
|
||||
* Copyright (C) 2026 Gravitational, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { LEGACY_THEME_COLORS } from '@gravitational/design-system';
|
||||
|
||||
import type { Theme, ThemeDefinition } from './types';
|
||||
|
||||
/**
|
||||
* Combines a `ThemeDefinition` with the legacy color palette to produce a
|
||||
* complete `Theme`.
|
||||
*
|
||||
* "Legacy" here means the styled-components theme that components still
|
||||
* consume via `props.theme`. Its colors are sourced from the new design
|
||||
* system (`LEGACY_THEME_COLORS`) so we don't define them in two places.
|
||||
*
|
||||
* Use this anywhere a styled-components theme is needed outside the runtime
|
||||
* `ThemeProvider` (tests, storybook, error fallbacks rendered above the
|
||||
* runtime provider).
|
||||
*/
|
||||
export function resolveTheme(definition: ThemeDefinition): Theme {
|
||||
return {
|
||||
...definition,
|
||||
colors: LEGACY_THEME_COLORS,
|
||||
};
|
||||
}
|
||||
@@ -17,10 +17,9 @@
|
||||
*/
|
||||
|
||||
import { fonts } from '../fonts';
|
||||
import { blueGrey, lightBlue, yellow } from '../palette';
|
||||
import typography, { fontSizes, fontWeights } from '../typography';
|
||||
import { getContrastRatio } from '../utils/colorManipulator';
|
||||
import { SharedColors, SharedStyles } from './types';
|
||||
import type { SharedStyles } from './types';
|
||||
|
||||
// TODO(bl-nero): use a CSS var for sidebar width and make the breakpoints work
|
||||
// by changing the minimum width on a per-view basis (Main.tsx).
|
||||
@@ -90,21 +89,6 @@ export const sharedStyles: SharedStyles = {
|
||||
bold: fontWeights.bold,
|
||||
};
|
||||
|
||||
// Colors that are shared between all themes, these should be added to the theme.colors object.
|
||||
export const sharedColors: SharedColors = {
|
||||
dark: '#000000',
|
||||
light: '#FFFFFF',
|
||||
interactionHandle: '#FFFFFF',
|
||||
grey: {
|
||||
...blueGrey,
|
||||
},
|
||||
subtle: blueGrey[50],
|
||||
bgTerminal: '#010B1C',
|
||||
highlight: yellow[50],
|
||||
disabled: blueGrey[500],
|
||||
info: lightBlue[600],
|
||||
};
|
||||
|
||||
export function getContrastText(background: string) {
|
||||
// Use the same logic as
|
||||
// Bootstrap: https://github.com/twbs/bootstrap/blob/1d6e3710dd447de1a200f29e8fa521f8a0908f70/scss/_functions.scss#L59
|
||||
|
||||
@@ -16,304 +16,11 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type { LegacyThemeColors } from '@gravitational/design-system';
|
||||
|
||||
import { Fonts } from '../fonts';
|
||||
import { blueGrey } from '../palette';
|
||||
import typography, { fontSizes, fontWeights } from '../typography';
|
||||
|
||||
type InteractiveColorGroup = {
|
||||
default: string;
|
||||
hover: string;
|
||||
active: string;
|
||||
};
|
||||
|
||||
export type ThemeColors = {
|
||||
/**
|
||||
Colors in `levels` are used to reflect the perceived depth of elements in the UI.
|
||||
The further back an element is, the more "sunken" it is, and the more forwards it is, the more "elevated" it is (think CSS z-index).
|
||||
|
||||
A `sunken` color would be used to represent something like the background of the app.
|
||||
While `surface` would be the color of the primary surface where most content is located (such as tables).
|
||||
Any colors more "elevated" than that would be used for things such as popovers, menus, and dialogs.
|
||||
|
||||
For more information on this concept: https://m3.material.io/styles/elevation/applying-elevation
|
||||
*/
|
||||
levels: {
|
||||
deep: string;
|
||||
sunken: string;
|
||||
surface: string;
|
||||
elevated: string;
|
||||
popout: string;
|
||||
};
|
||||
|
||||
/**
|
||||
Spot backgrounds are used as highlights, for example
|
||||
to indicate a hover or active state for an item in a menu.
|
||||
@deprecated Use `interactive.tonal.neutral` instead.
|
||||
*/
|
||||
spotBackground: string[];
|
||||
|
||||
brand: string;
|
||||
|
||||
/**
|
||||
* Interactive colors are used to highlight different actions or states
|
||||
* based on intent.
|
||||
*
|
||||
* For example, primary would be used for as selected states.
|
||||
*/
|
||||
interactive: {
|
||||
solid: {
|
||||
primary: InteractiveColorGroup;
|
||||
success: InteractiveColorGroup;
|
||||
accent: InteractiveColorGroup;
|
||||
danger: InteractiveColorGroup;
|
||||
alert: InteractiveColorGroup;
|
||||
};
|
||||
tonal: {
|
||||
primary: string[];
|
||||
neutral: string[];
|
||||
success: string[];
|
||||
danger: string[];
|
||||
alert: string[];
|
||||
informational: string[];
|
||||
};
|
||||
};
|
||||
|
||||
text: {
|
||||
/** The most important text. */
|
||||
main: string;
|
||||
/** Slightly muted text. */
|
||||
slightlyMuted: string;
|
||||
/** Muted text. Also used as placeholder text in forms. */
|
||||
muted: string;
|
||||
/** Disabled text. */
|
||||
disabled: string;
|
||||
/**
|
||||
For text on a background that is on a color opposite to the theme. For dark theme,
|
||||
this would mean text that is on a light background.
|
||||
*/
|
||||
primaryInverse: string;
|
||||
};
|
||||
|
||||
buttons: {
|
||||
text: string;
|
||||
textDisabled: string;
|
||||
bgDisabled: string;
|
||||
|
||||
/** @deprecated Use `interactive.solid.primary` instead. */
|
||||
primary: {
|
||||
text: string;
|
||||
default: string;
|
||||
hover: string;
|
||||
active: string;
|
||||
};
|
||||
|
||||
/** @deprecated Use `interactive.tonal.neutral` instead. */
|
||||
secondary: {
|
||||
default: string;
|
||||
hover: string;
|
||||
active: string;
|
||||
};
|
||||
|
||||
border: {
|
||||
default: string;
|
||||
hover: string;
|
||||
active: string;
|
||||
border: string;
|
||||
};
|
||||
|
||||
/** @deprecated Use `interactive.solid.danger` instead. */
|
||||
warning: {
|
||||
text: string;
|
||||
default: string;
|
||||
hover: string;
|
||||
active: string;
|
||||
};
|
||||
|
||||
trashButton: { default: string; hover: string };
|
||||
|
||||
/** @deprecated Use `interactive.solid.accent` instead. */
|
||||
link: {
|
||||
default: string;
|
||||
hover: string;
|
||||
active: string;
|
||||
};
|
||||
};
|
||||
|
||||
tooltip: {
|
||||
background: string;
|
||||
inverseBackground: string;
|
||||
inverseLinkDefault: string;
|
||||
};
|
||||
|
||||
progressBarColor: string;
|
||||
|
||||
/** @deprecated Use `interactive.solid.danger` instead. */
|
||||
error: {
|
||||
main: string;
|
||||
hover: string;
|
||||
active: string;
|
||||
};
|
||||
|
||||
/** @deprecated Use `interactive.solid.alert` instead. */
|
||||
warning: {
|
||||
main: string;
|
||||
hover: string;
|
||||
active: string;
|
||||
};
|
||||
|
||||
/** @deprecated Use `interactive.solid.success` instead. */
|
||||
success: {
|
||||
main: string;
|
||||
hover: string;
|
||||
active: string;
|
||||
};
|
||||
|
||||
/** @deprecated Use `interactive.solid.accent` instead. */
|
||||
accent: {
|
||||
main: string;
|
||||
hover: string;
|
||||
active: string;
|
||||
};
|
||||
|
||||
notice: {
|
||||
background: string;
|
||||
};
|
||||
|
||||
action: {
|
||||
active: string;
|
||||
hover: string;
|
||||
hoverOpacity: number;
|
||||
selected: string;
|
||||
disabled: string;
|
||||
disabledBackground: string;
|
||||
};
|
||||
|
||||
terminal: {
|
||||
foreground: string;
|
||||
background: string;
|
||||
selectionBackground: string;
|
||||
cursor: string;
|
||||
cursorAccent: string;
|
||||
red: string;
|
||||
green: string;
|
||||
yellow: string;
|
||||
blue: string;
|
||||
magenta: string;
|
||||
cyan: string;
|
||||
brightWhite: string;
|
||||
white: string;
|
||||
brightBlack: string;
|
||||
black: string;
|
||||
brightRed: string;
|
||||
brightGreen: string;
|
||||
brightYellow: string;
|
||||
brightBlue: string;
|
||||
brightMagenta: string;
|
||||
brightCyan: string;
|
||||
searchMatch: string;
|
||||
activeSearchMatch: string;
|
||||
};
|
||||
|
||||
editor: {
|
||||
abbey: string;
|
||||
purple: string;
|
||||
cyan: string;
|
||||
picton: string;
|
||||
sunflower: string;
|
||||
caribbean: string;
|
||||
};
|
||||
|
||||
sessionRecording: {
|
||||
resource: string;
|
||||
user: string;
|
||||
player: {
|
||||
progressBar: {
|
||||
background: string;
|
||||
seeking: string;
|
||||
progress: string;
|
||||
};
|
||||
};
|
||||
riskLevels: {
|
||||
low: string;
|
||||
medium: string;
|
||||
high: string;
|
||||
critical: string;
|
||||
};
|
||||
};
|
||||
|
||||
sessionRecordingTimeline: {
|
||||
background: string;
|
||||
headerBackground: string;
|
||||
frameBorder: string;
|
||||
progressLine: string;
|
||||
cursor: string;
|
||||
border: {
|
||||
default: string;
|
||||
hover: string;
|
||||
};
|
||||
events: {
|
||||
inactivity: {
|
||||
background: string;
|
||||
text: string;
|
||||
};
|
||||
resize: {
|
||||
semiBackground: string;
|
||||
background: string;
|
||||
border: string;
|
||||
text: string;
|
||||
};
|
||||
join: {
|
||||
background: string;
|
||||
text: string;
|
||||
};
|
||||
default: {
|
||||
background: string;
|
||||
text: string;
|
||||
};
|
||||
};
|
||||
timeMarks: {
|
||||
primary: string;
|
||||
secondary: string;
|
||||
absolute: string;
|
||||
text: string;
|
||||
};
|
||||
};
|
||||
|
||||
link: string;
|
||||
|
||||
highlightedNavigationItem: string;
|
||||
|
||||
dataVisualisation: DataVisualisationColors;
|
||||
} & SharedColors;
|
||||
|
||||
export type SharedColors = {
|
||||
dark: string;
|
||||
light: string;
|
||||
interactionHandle: string;
|
||||
grey: typeof blueGrey;
|
||||
subtle: string;
|
||||
bgTerminal: string;
|
||||
highlight: string;
|
||||
disabled: string;
|
||||
info: string;
|
||||
};
|
||||
|
||||
export type DataVisualisationColors = {
|
||||
primary: VisualisationColors;
|
||||
secondary: VisualisationColors;
|
||||
tertiary: VisualisationColors;
|
||||
};
|
||||
|
||||
type VisualisationColors = {
|
||||
purple: string;
|
||||
wednesdays: string;
|
||||
picton: string;
|
||||
sunflower: string;
|
||||
caribbean: string;
|
||||
abbey: string;
|
||||
cyan: string;
|
||||
};
|
||||
|
||||
export type SharedStyles = {
|
||||
sidebarWidth: number;
|
||||
boxShadow: string[];
|
||||
@@ -356,5 +63,7 @@ export type Theme = {
|
||||
type: 'dark' | 'light';
|
||||
/** Whether this is a custom theme and not Dark Theme/Light Theme. */
|
||||
isCustomTheme: boolean;
|
||||
colors: ThemeColors;
|
||||
colors: LegacyThemeColors;
|
||||
} & SharedStyles;
|
||||
|
||||
export type ThemeDefinition = Omit<Theme, 'colors'>;
|
||||
|
||||
@@ -111,6 +111,10 @@ export function decomposeColor(color) {
|
||||
return decomposeColor(convertHexToRGB(color));
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return { type: 'rgb', values: [0, 0, 0] };
|
||||
}
|
||||
|
||||
const marker = color.indexOf('(');
|
||||
const type = color.substring(0, marker);
|
||||
let values = color.substring(marker + 1, color.length - 1).split(',');
|
||||
@@ -211,26 +215,6 @@ export function emphasize(color, coefficient = 0.15) {
|
||||
: lighten(color, coefficient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the absolute transparency of a color.
|
||||
* Any existing alpha values are overwritten.
|
||||
*
|
||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
||||
* @param {number} value - value to set the alpha channel to in the range 0 -1
|
||||
* @returns {string} A CSS color string. Hex input values are returned as rgb
|
||||
*/
|
||||
export function fade(color, value) {
|
||||
color = decomposeColor(color);
|
||||
value = clamp(value);
|
||||
|
||||
if (color.type === 'rgb' || color.type === 'hsl') {
|
||||
color.type += 'a';
|
||||
}
|
||||
color.values[3] = value;
|
||||
|
||||
return recomposeColor(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Darkens a color.
|
||||
*
|
||||
|
||||
@@ -16,8 +16,13 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import '@testing-library/jest-dom';
|
||||
import {
|
||||
createThemeSystem,
|
||||
TELEPORT_THEME,
|
||||
ThemeProvider as NewThemeProvider,
|
||||
} from '@gravitational/design-system';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
@@ -29,16 +34,18 @@ import {
|
||||
waitForElementToBeRemoved,
|
||||
within,
|
||||
} from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import 'jest-styled-components';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { HttpResponse, JsonBodyType } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { PropsWithChildren, ReactNode } from 'react';
|
||||
import { MemoryRouter, useLocation } from 'react-router';
|
||||
|
||||
import { darkTheme } from 'design/theme';
|
||||
import { darkTheme, resolveTheme } from 'design/theme';
|
||||
import { ConfiguredThemeProvider } from 'design/ThemeProvider';
|
||||
|
||||
const testThemeSystem = createThemeSystem(TELEPORT_THEME.config);
|
||||
|
||||
export const testQueryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
@@ -47,12 +54,16 @@ export const testQueryClient = new QueryClient({
|
||||
},
|
||||
});
|
||||
|
||||
const legacyTheme = resolveTheme(darkTheme);
|
||||
|
||||
export function Providers({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<QueryClientProvider client={testQueryClient}>
|
||||
<ConfiguredThemeProvider theme={darkTheme}>
|
||||
{children}
|
||||
</ConfiguredThemeProvider>
|
||||
<NewThemeProvider system={testThemeSystem} forcedTheme="dark">
|
||||
<ConfiguredThemeProvider theme={legacyTheme}>
|
||||
{children}
|
||||
</ConfiguredThemeProvider>
|
||||
</NewThemeProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
@@ -177,7 +188,8 @@ export {
|
||||
act,
|
||||
screen,
|
||||
fireEvent,
|
||||
darkTheme as theme,
|
||||
legacyTheme as theme,
|
||||
testThemeSystem,
|
||||
tick,
|
||||
render,
|
||||
prettyDOM,
|
||||
|
||||
@@ -19,9 +19,8 @@
|
||||
import { UserEvent } from '@testing-library/user-event';
|
||||
import { ComponentProps, PropsWithChildren } from 'react';
|
||||
|
||||
import darkTheme from 'design/theme/themes/darkTheme';
|
||||
import { ConfiguredThemeProvider } from 'design/ThemeProvider';
|
||||
import { render, screen, userEvent, within } from 'design/utils/testing';
|
||||
import { render, screen, theme, userEvent, within } from 'design/utils/testing';
|
||||
|
||||
import { SortMenu } from './SortMenu';
|
||||
|
||||
@@ -303,7 +302,7 @@ const renderComponent = (options?: {
|
||||
function makeWrapper() {
|
||||
return (props: PropsWithChildren) => {
|
||||
return (
|
||||
<ConfiguredThemeProvider theme={darkTheme}>
|
||||
<ConfiguredThemeProvider theme={theme}>
|
||||
{props.children}
|
||||
</ConfiguredThemeProvider>
|
||||
);
|
||||
|
||||
@@ -93,6 +93,7 @@ export const ViewModeSwitchButton = styled.button<{
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
padding-inline: ${p => p.theme.space[1]}px;
|
||||
transition: outline-width 150ms ease;
|
||||
|
||||
${p =>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { resolveThemeToColors } from '@gravitational/design-system';
|
||||
import { ComponentPropsWithoutRef } from 'react';
|
||||
import styled, { DefaultTheme, useTheme } from 'styled-components';
|
||||
|
||||
@@ -97,8 +98,8 @@ const CircleButton = styled(Box)<{ $color: string }>`
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background-color: ${props => props.$color};
|
||||
border: 1px solid ${props => darken(props.$color, 0.2)};
|
||||
border: 1px solid ${props => darken(resolveThemeToColors(props.$color), 0.2)};
|
||||
`;
|
||||
|
||||
const topBarColor = (theme: DefaultTheme): string =>
|
||||
emphasize(theme.colors.levels.deep, 0.2);
|
||||
emphasize(resolveThemeToColors(theme.colors.levels.deep), 0.2);
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
|
||||
import { screen } from '@testing-library/react';
|
||||
|
||||
import { darkTheme } from 'design/theme';
|
||||
import { fireEvent, render } from 'design/utils/testing';
|
||||
import { fireEvent, render, theme } from 'design/utils/testing';
|
||||
|
||||
import * as useRule from '../Validation/useRule';
|
||||
import FieldInput from './FieldInput';
|
||||
@@ -65,7 +64,7 @@ test('valid values, autofocus, onChange, onKeyPress', () => {
|
||||
|
||||
test('input validation error state', () => {
|
||||
const rule = jest.fn();
|
||||
const errorColor = darkTheme.colors.interactive.solid.danger.default;
|
||||
const errorColor = theme.colors.interactive.solid.danger.default;
|
||||
|
||||
// mock negative validation
|
||||
jest
|
||||
@@ -90,9 +89,4 @@ test('input validation error state', () => {
|
||||
// test !valid values renders with error message
|
||||
const labelEl = screen.getByText('errorMsg');
|
||||
expect(labelEl).toHaveStyle({ color: errorColor });
|
||||
|
||||
// test !valid values renders error colors
|
||||
expect(inputEl).toHaveStyle({
|
||||
'border-color': errorColor,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
import { screen } from '@testing-library/react';
|
||||
import selectEvent from 'react-select-event';
|
||||
|
||||
import { darkTheme } from 'design/theme';
|
||||
import { fireEvent, render } from 'design/utils/testing';
|
||||
import { fireEvent, render, theme } from 'design/utils/testing';
|
||||
|
||||
import useRule from '../Validation/useRule';
|
||||
import { FieldSelect, FieldSelectAsync } from './FieldSelect';
|
||||
@@ -59,7 +58,7 @@ test('valid values and onChange prop', () => {
|
||||
|
||||
test('select element validation error state', () => {
|
||||
const rule = jest.fn();
|
||||
const errorColor = darkTheme.colors.error.main;
|
||||
const errorColor = theme.colors.interactive.solid.danger.default;
|
||||
|
||||
mockedUseRule.mockReturnValue({ valid: false, message: 'errorMsg' });
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { resolveThemeToColors } from '@gravitational/design-system';
|
||||
import { SearchAddon } from '@xterm/addon-search';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import styled, { useTheme } from 'styled-components';
|
||||
@@ -57,8 +58,10 @@ export const TerminalSearch = ({
|
||||
}, [terminalSearcher]);
|
||||
|
||||
const search = (value: string, direction: 'next' | 'previous') => {
|
||||
const match = theme.colors.terminal.searchMatch;
|
||||
const activeMatch = theme.colors.terminal.activeSearchMatch;
|
||||
const match = resolveThemeToColors(theme.colors.terminal.searchMatch);
|
||||
const activeMatch = resolveThemeToColors(
|
||||
theme.colors.terminal.activeSearchMatch
|
||||
);
|
||||
setSearchValue(value);
|
||||
const opts = {
|
||||
regex: true,
|
||||
|
||||
@@ -21,22 +21,22 @@ import { fireEvent, render, screen } from 'design/utils/testing';
|
||||
import Validation, { useRule } from '.';
|
||||
|
||||
test('basic usage', async () => {
|
||||
const utils = render(<Component value="" rule={required} />);
|
||||
render(<Component value="" rule={required} />);
|
||||
|
||||
// expect no errors when not validating
|
||||
expect(utils.container.firstChild).toHaveTextContent('valid');
|
||||
expect(screen.getByTestId('value-box')).toHaveTextContent('valid');
|
||||
|
||||
// trigger validation and expect errors
|
||||
fireEvent.click(screen.getByRole('button'));
|
||||
expect(utils.container.firstChild).toHaveTextContent(
|
||||
expect(screen.getByTestId('value-box')).toHaveTextContent(
|
||||
'this field is required'
|
||||
);
|
||||
});
|
||||
|
||||
test('valid component with no errors', () => {
|
||||
const utils = render(<Component value="123" rule={required} />);
|
||||
render(<Component value="123" rule={required} />);
|
||||
|
||||
expect(utils.container.firstChild).toHaveTextContent('valid');
|
||||
expect(screen.getByTestId('value-box')).toHaveTextContent('valid');
|
||||
});
|
||||
|
||||
test('verify that useRule properly unsubscribes from validation context', () => {
|
||||
@@ -69,10 +69,10 @@ function Component(props) {
|
||||
function ValueBox({ value, rule }) {
|
||||
const utils = useRule(rule(value));
|
||||
return (
|
||||
<>
|
||||
<span data-testid="value-box">
|
||||
{utils.valid && 'valid'}
|
||||
{!utils.valid && utils.message}
|
||||
</>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { act, render } from 'design/utils/testing';
|
||||
import { act, render, screen } from 'design/utils/testing';
|
||||
|
||||
import Store from './store';
|
||||
import useStore from './useStore';
|
||||
@@ -29,9 +29,11 @@ test('components subscribes to store changes and unsubscribes on unmount', async
|
||||
lastname: 'smith',
|
||||
});
|
||||
|
||||
const { unmount, container } = render(<Component store={store} />);
|
||||
const { unmount } = render(<Component store={store} />);
|
||||
|
||||
expect(container.innerHTML).toBe(JSON.stringify(store.state));
|
||||
expect(screen.getByTestId('state')).toHaveTextContent(
|
||||
JSON.stringify(store.state)
|
||||
);
|
||||
|
||||
act(() => {
|
||||
store.setState({
|
||||
@@ -39,7 +41,7 @@ test('components subscribes to store changes and unsubscribes on unmount', async
|
||||
});
|
||||
});
|
||||
|
||||
expect(container.innerHTML).toBe(
|
||||
expect(screen.getByTestId('state')).toHaveTextContent(
|
||||
JSON.stringify({
|
||||
firstname: 'alex',
|
||||
lastname: 'smith',
|
||||
@@ -54,5 +56,5 @@ test('components subscribes to store changes and unsubscribes on unmount', async
|
||||
function Component({ store }) {
|
||||
// subscribes to store updates
|
||||
useStore(store);
|
||||
return <>{JSON.stringify(store.state)}</>;
|
||||
return <span data-testid="state">{JSON.stringify(store.state)}</span>;
|
||||
}
|
||||
|
||||
@@ -17,8 +17,13 @@
|
||||
*/
|
||||
import * as styledComponents from 'styled-components';
|
||||
|
||||
import { lightTheme } from 'design/theme';
|
||||
import { fireEvent, render, screen, waitFor } from 'design/utils/testing';
|
||||
import {
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
theme,
|
||||
waitFor,
|
||||
} from 'design/utils/testing';
|
||||
import { Theme } from 'gen-proto-ts/teleport/userpreferences/v1/theme_pb';
|
||||
import {
|
||||
ToastNotificationProvider,
|
||||
@@ -150,7 +155,7 @@ describe('Account/Preferences', () => {
|
||||
|
||||
it("theme selection isn't shown if a custom theme is set", () => {
|
||||
jest.spyOn(styledComponents, 'useTheme').mockReturnValue({
|
||||
...lightTheme,
|
||||
...theme,
|
||||
isCustomTheme: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -20,9 +20,8 @@ import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { mockIntersectionObserver } from 'jsdom-testing-mocks';
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
import { darkTheme } from 'design/theme';
|
||||
import { ConfiguredThemeProvider } from 'design/ThemeProvider';
|
||||
import { act, screen, testQueryClient } from 'design/utils/testing';
|
||||
import { act, screen, testQueryClient, theme } from 'design/utils/testing';
|
||||
|
||||
import cfg from 'teleport/config';
|
||||
import { createTeleportContext } from 'teleport/mocks/contexts';
|
||||
@@ -157,7 +156,7 @@ function makeWrapper({ ctx }: { ctx: TeleportContext }) {
|
||||
return (
|
||||
<QueryClientProvider client={testQueryClient}>
|
||||
<ContextProvider ctx={ctx}>
|
||||
<ConfiguredThemeProvider theme={darkTheme}>
|
||||
<ConfiguredThemeProvider theme={theme}>
|
||||
{children}
|
||||
</ConfiguredThemeProvider>
|
||||
</ContextProvider>
|
||||
|
||||
@@ -19,13 +19,13 @@
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
import { darkTheme } from 'design/theme';
|
||||
import { ConfiguredThemeProvider } from 'design/ThemeProvider';
|
||||
import {
|
||||
enableMswServer,
|
||||
screen,
|
||||
server,
|
||||
testQueryClient,
|
||||
theme,
|
||||
userEvent,
|
||||
waitFor,
|
||||
waitForElementToBeRemoved,
|
||||
@@ -645,7 +645,7 @@ function makeWrapper(options: { customAcl: ReturnType<typeof makeAcl> }) {
|
||||
});
|
||||
return (
|
||||
<QueryClientProvider client={testQueryClient}>
|
||||
<ConfiguredThemeProvider theme={darkTheme}>
|
||||
<ConfiguredThemeProvider theme={theme}>
|
||||
<InfoGuidePanelProvider data-testid="blah">
|
||||
<ContextProvider ctx={ctx}>{children}</ContextProvider>
|
||||
</InfoGuidePanelProvider>
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ComponentProps, PropsWithChildren } from 'react';
|
||||
|
||||
import { darkTheme } from 'design/theme';
|
||||
import { ConfiguredThemeProvider } from 'design/ThemeProvider';
|
||||
import {
|
||||
enableMswServer,
|
||||
@@ -27,6 +26,7 @@ import {
|
||||
screen,
|
||||
server,
|
||||
testQueryClient,
|
||||
theme,
|
||||
userEvent,
|
||||
waitForElementToBeRemoved,
|
||||
within,
|
||||
@@ -209,7 +209,7 @@ function makeWrapper() {
|
||||
return ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<QueryClientProvider client={testQueryClient}>
|
||||
<ConfiguredThemeProvider theme={darkTheme}>
|
||||
<ConfiguredThemeProvider theme={theme}>
|
||||
{children}
|
||||
</ConfiguredThemeProvider>
|
||||
</QueryClientProvider>
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ComponentProps, PropsWithChildren } from 'react';
|
||||
|
||||
import darkTheme from 'design/theme/themes/darkTheme';
|
||||
import { ConfiguredThemeProvider } from 'design/ThemeProvider';
|
||||
import {
|
||||
enableMswServer,
|
||||
@@ -27,6 +26,7 @@ import {
|
||||
screen,
|
||||
server,
|
||||
testQueryClient,
|
||||
theme,
|
||||
userEvent,
|
||||
waitForElementToBeRemoved,
|
||||
within,
|
||||
@@ -206,7 +206,7 @@ function makeWrapper(options?: { hasBotInstanceReadPermission?: boolean }) {
|
||||
return (
|
||||
<QueryClientProvider client={testQueryClient}>
|
||||
<TeleportProviderBasic teleportCtx={ctx}>
|
||||
<ConfiguredThemeProvider theme={darkTheme}>
|
||||
<ConfiguredThemeProvider theme={theme}>
|
||||
{props.children}
|
||||
</ConfiguredThemeProvider>
|
||||
</TeleportProviderBasic>
|
||||
|
||||
@@ -19,9 +19,8 @@
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { ComponentProps, PropsWithChildren } from 'react';
|
||||
|
||||
import darkTheme from 'design/theme/themes/darkTheme';
|
||||
import { ConfiguredThemeProvider } from 'design/ThemeProvider';
|
||||
import { render, screen, within } from 'design/utils/testing';
|
||||
import { render, screen, theme, within } from 'design/utils/testing';
|
||||
|
||||
import { mockGetBotInstanceResponse } from 'teleport/test/helpers/botInstances';
|
||||
|
||||
@@ -119,7 +118,7 @@ function renderComponent(props?: Partial<ComponentProps<typeof HealthTab>>) {
|
||||
|
||||
function makeWrapper() {
|
||||
return (props: PropsWithChildren) => (
|
||||
<ConfiguredThemeProvider theme={darkTheme}>
|
||||
<ConfiguredThemeProvider theme={theme}>
|
||||
{props.children}
|
||||
</ConfiguredThemeProvider>
|
||||
);
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ComponentProps, PropsWithChildren } from 'react';
|
||||
|
||||
import darkTheme from 'design/theme/themes/darkTheme';
|
||||
import { ConfiguredThemeProvider } from 'design/ThemeProvider';
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
testQueryClient,
|
||||
theme,
|
||||
userEvent,
|
||||
} from 'design/utils/testing';
|
||||
|
||||
@@ -307,7 +307,7 @@ function makeWrapper() {
|
||||
return (
|
||||
<QueryClientProvider client={testQueryClient}>
|
||||
<TeleportProviderBasic teleportCtx={ctx}>
|
||||
<ConfiguredThemeProvider theme={darkTheme}>
|
||||
<ConfiguredThemeProvider theme={theme}>
|
||||
{props.children}
|
||||
</ConfiguredThemeProvider>
|
||||
</TeleportProviderBasic>
|
||||
|
||||
@@ -20,7 +20,6 @@ import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import selectEvent from 'react-select-event';
|
||||
|
||||
import darkTheme from 'design/theme/themes/darkTheme';
|
||||
import { ConfiguredThemeProvider } from 'design/ThemeProvider';
|
||||
import {
|
||||
act,
|
||||
@@ -29,6 +28,7 @@ import {
|
||||
screen,
|
||||
server,
|
||||
testQueryClient,
|
||||
theme,
|
||||
userEvent,
|
||||
within,
|
||||
} from 'design/utils/testing';
|
||||
@@ -278,7 +278,7 @@ function makeWrapper(opts?: {
|
||||
return (
|
||||
<QueryClientProvider client={testQueryClient}>
|
||||
<ContextProvider ctx={ctx}>
|
||||
<ConfiguredThemeProvider theme={darkTheme}>
|
||||
<ConfiguredThemeProvider theme={theme}>
|
||||
<TrackingProvider disabled={disableTracking}>
|
||||
<GitHubK8sFlowProvider intitialState={initialState}>
|
||||
{children}
|
||||
|
||||
@@ -20,7 +20,6 @@ import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ComponentProps, PropsWithChildren } from 'react';
|
||||
import selectEvent from 'react-select-event';
|
||||
|
||||
import darkTheme from 'design/theme/themes/darkTheme';
|
||||
import { ConfiguredThemeProvider } from 'design/ThemeProvider';
|
||||
import {
|
||||
act,
|
||||
@@ -29,6 +28,7 @@ import {
|
||||
screen,
|
||||
server,
|
||||
testQueryClient,
|
||||
theme,
|
||||
userEvent,
|
||||
} from 'design/utils/testing';
|
||||
|
||||
@@ -384,7 +384,7 @@ function makeWrapper(opts?: {
|
||||
return (
|
||||
<QueryClientProvider client={testQueryClient}>
|
||||
<ContextProvider ctx={ctx}>
|
||||
<ConfiguredThemeProvider theme={darkTheme}>
|
||||
<ConfiguredThemeProvider theme={theme}>
|
||||
<TrackingProvider disabled={disableTracking}>
|
||||
<GitHubK8sFlowProvider intitialState={initialState}>
|
||||
{children}
|
||||
|
||||
@@ -21,7 +21,6 @@ import { PropsWithChildren } from 'react';
|
||||
import { MemoryRouter } from 'react-router';
|
||||
import selectEvent from 'react-select-event';
|
||||
|
||||
import darkTheme from 'design/theme/themes/darkTheme';
|
||||
import { ConfiguredThemeProvider } from 'design/ThemeProvider';
|
||||
import {
|
||||
act,
|
||||
@@ -30,6 +29,7 @@ import {
|
||||
screen,
|
||||
server,
|
||||
testQueryClient,
|
||||
theme,
|
||||
userEvent,
|
||||
} from 'design/utils/testing';
|
||||
|
||||
@@ -192,7 +192,7 @@ function makeWrapper(opts?: {
|
||||
return (
|
||||
<QueryClientProvider client={testQueryClient}>
|
||||
<ContextProvider ctx={ctx}>
|
||||
<ConfiguredThemeProvider theme={darkTheme}>
|
||||
<ConfiguredThemeProvider theme={theme}>
|
||||
<TrackingProvider disabled={disableTracking}>
|
||||
<GitHubK8sFlowProvider intitialState={initialState}>
|
||||
<MemoryRouter>{children}</MemoryRouter>
|
||||
|
||||
@@ -21,7 +21,6 @@ import { PropsWithChildren } from 'react';
|
||||
import { MemoryRouter, Route, Routes } from 'react-router';
|
||||
import selectEvent from 'react-select-event';
|
||||
|
||||
import darkTheme from 'design/theme/themes/darkTheme';
|
||||
import { ConfiguredThemeProvider } from 'design/ThemeProvider';
|
||||
import {
|
||||
act,
|
||||
@@ -30,6 +29,7 @@ import {
|
||||
screen,
|
||||
server,
|
||||
testQueryClient,
|
||||
theme,
|
||||
userEvent,
|
||||
} from 'design/utils/testing';
|
||||
import { InfoGuidePanelProvider } from 'shared/components/SlidingSidePanel/InfoGuide';
|
||||
@@ -207,7 +207,7 @@ function makeWrapper(opts: { trackingEventId?: string }) {
|
||||
return (
|
||||
<QueryClientProvider client={testQueryClient}>
|
||||
<ContextProvider ctx={ctx}>
|
||||
<ConfiguredThemeProvider theme={darkTheme}>
|
||||
<ConfiguredThemeProvider theme={theme}>
|
||||
<InfoGuidePanelProvider>
|
||||
<ContentMinWidth>
|
||||
<TrackingProvider initialEventId={trackingEventId}>
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ComponentProps, PropsWithChildren } from 'react';
|
||||
|
||||
import darkTheme from 'design/theme/themes/darkTheme';
|
||||
import { ConfiguredThemeProvider } from 'design/ThemeProvider';
|
||||
import {
|
||||
enableMswServer,
|
||||
@@ -27,6 +26,7 @@ import {
|
||||
screen,
|
||||
server,
|
||||
testQueryClient,
|
||||
theme,
|
||||
userEvent,
|
||||
within,
|
||||
} from 'design/utils/testing';
|
||||
@@ -426,7 +426,7 @@ function makeWrapper(opts?: {
|
||||
return ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<QueryClientProvider client={testQueryClient}>
|
||||
<ConfiguredThemeProvider theme={darkTheme}>
|
||||
<ConfiguredThemeProvider theme={theme}>
|
||||
<ContextProvider ctx={ctx}>
|
||||
<TrackingProvider disabled={disableTracking}>
|
||||
<Validation>{children}</Validation>
|
||||
|
||||
@@ -23,7 +23,6 @@ import { UserEvent } from '@testing-library/user-event';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { MemoryRouter, Route, Routes, useLocation } from 'react-router';
|
||||
|
||||
import darkTheme from 'design/theme/themes/darkTheme';
|
||||
import { ConfiguredThemeProvider } from 'design/ThemeProvider';
|
||||
import {
|
||||
enableMswServer,
|
||||
@@ -31,6 +30,7 @@ import {
|
||||
screen,
|
||||
server,
|
||||
testQueryClient,
|
||||
theme,
|
||||
userEvent,
|
||||
waitFor,
|
||||
waitForElementToBeRemoved,
|
||||
@@ -928,7 +928,7 @@ function makeWrapper(options: { customAcl?: ReturnType<typeof makeAcl> }) {
|
||||
return (
|
||||
<MemoryRouter initialEntries={['/web/bot/test-bot-name']}>
|
||||
<QueryClientProvider client={testQueryClient}>
|
||||
<ConfiguredThemeProvider theme={darkTheme}>
|
||||
<ConfiguredThemeProvider theme={theme}>
|
||||
<ContextProvider ctx={ctx}>
|
||||
<InfoGuidePanelProvider>
|
||||
<LocationState />
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
import darkTheme from 'design/theme/themes/darkTheme';
|
||||
import { ConfiguredThemeProvider } from 'design/ThemeProvider';
|
||||
import {
|
||||
enableMswServer,
|
||||
@@ -27,6 +26,7 @@ import {
|
||||
screen,
|
||||
server,
|
||||
testQueryClient,
|
||||
theme,
|
||||
waitForElementToBeRemoved,
|
||||
} from 'design/utils/testing';
|
||||
|
||||
@@ -139,7 +139,7 @@ function makeWrapper(options?: { customAcl?: ReturnType<typeof makeAcl> }) {
|
||||
});
|
||||
return (
|
||||
<QueryClientProvider client={testQueryClient}>
|
||||
<ConfiguredThemeProvider theme={darkTheme}>
|
||||
<ConfiguredThemeProvider theme={theme}>
|
||||
<ContextProvider ctx={ctx}>{children}</ContextProvider>
|
||||
</ConfiguredThemeProvider>
|
||||
</QueryClientProvider>
|
||||
|
||||
@@ -21,7 +21,6 @@ import { UserEvent } from '@testing-library/user-event';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import selectEvent from 'react-select-event';
|
||||
|
||||
import darkTheme from 'design/theme/themes/darkTheme';
|
||||
import { ConfiguredThemeProvider } from 'design/ThemeProvider';
|
||||
import {
|
||||
act,
|
||||
@@ -30,6 +29,7 @@ import {
|
||||
screen,
|
||||
server,
|
||||
testQueryClient,
|
||||
theme,
|
||||
userEvent,
|
||||
waitForElementToBeRemoved,
|
||||
} from 'design/utils/testing';
|
||||
@@ -467,7 +467,7 @@ function makeWrapper(params?: { customAcl?: ReturnType<typeof makeAcl> }) {
|
||||
});
|
||||
return (
|
||||
<QueryClientProvider client={testQueryClient}>
|
||||
<ConfiguredThemeProvider theme={darkTheme}>
|
||||
<ConfiguredThemeProvider theme={theme}>
|
||||
<ContextProvider ctx={ctx}>{children}</ContextProvider>
|
||||
</ConfiguredThemeProvider>
|
||||
</QueryClientProvider>
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useTheme } from 'styled-components';
|
||||
|
||||
import { Box, Indicator } from 'design';
|
||||
|
||||
@@ -42,7 +41,6 @@ export function DocumentDb({ doc, visible }: Props) {
|
||||
// when switching tabs or closing tabs, focus on visible terminal
|
||||
terminalRef.current?.focus();
|
||||
}, [visible, mfa, status]);
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Document visible={visible} flexDirection="column">
|
||||
@@ -65,8 +63,6 @@ export function DocumentDb({ doc, visible }: Props) {
|
||||
<Terminal
|
||||
ref={terminalRef}
|
||||
tty={tty}
|
||||
fontFamily={theme.fonts.mono}
|
||||
theme={theme.colors.terminal}
|
||||
convertEol
|
||||
// TODO(gabrielcorado): remove this once the server can properly handle it.
|
||||
disableCtrlC={true}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useTheme } from 'styled-components';
|
||||
|
||||
import { Box, Indicator } from 'design';
|
||||
|
||||
@@ -47,17 +46,9 @@ export default function DocumentKubeExec({ doc, visible }: Props) {
|
||||
terminalRef.current?.focus();
|
||||
}
|
||||
}, [visible, mfa.challenge, status]);
|
||||
const theme = useTheme();
|
||||
|
||||
const terminal = (
|
||||
<Terminal
|
||||
ref={terminalRef}
|
||||
tty={tty}
|
||||
fontFamily={theme.fonts.mono}
|
||||
theme={theme.colors.terminal}
|
||||
convertEol
|
||||
disableAutoFocus={true}
|
||||
/>
|
||||
<Terminal ref={terminalRef} tty={tty} convertEol disableAutoFocus={true} />
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
/**
|
||||
* Teleport
|
||||
* Copyright (C) 2023 Gravitational, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { fonts } from 'design/theme/fonts';
|
||||
import {
|
||||
blueGrey,
|
||||
lightBlue,
|
||||
orange,
|
||||
pink,
|
||||
red,
|
||||
teal,
|
||||
yellow,
|
||||
} from 'design/theme/palette';
|
||||
import typography, { fontSizes, fontWeights } from 'design/theme/typography';
|
||||
import { getContrastRatio } from 'design/theme/utils/colorManipulator';
|
||||
|
||||
const space = [0, 4, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80];
|
||||
const contrastThreshold = 3;
|
||||
|
||||
const colors = {
|
||||
/*
|
||||
Colors in `levels` are used to reflect the perceived depth of elements in the UI.
|
||||
The further back an element is, the more "sunken" it is, and the more forwards it is, the more "elevated" it is (think CSS z-index).
|
||||
|
||||
A `sunken` colour would be used to represent something like the background of the app.
|
||||
While `surface` would be the color of the primary surface where most content is located (such as tables).
|
||||
Any colors more "elevated" than that would be used for things such as popovers, menus, and dialogs.
|
||||
|
||||
`...Secondary` colours are used to differentiate different colors that represent the same depth.
|
||||
|
||||
For more information on this concept: https://m3.material.io/styles/elevation/applying-elevation
|
||||
*/
|
||||
levels: {
|
||||
sunken: '#0C143D',
|
||||
sunkenSecondary: '#111B48',
|
||||
|
||||
surface: '#1C254D',
|
||||
surfaceSecondary: '#1C254D',
|
||||
|
||||
elevated: '#2C3A73',
|
||||
|
||||
popout: '#3E4B7E',
|
||||
popoutHighlighted: '#535c8a',
|
||||
},
|
||||
|
||||
brand: {
|
||||
main: '#512FC9',
|
||||
accent: '#651FFF',
|
||||
secondaryAccent: '#354AA4',
|
||||
},
|
||||
|
||||
text: {
|
||||
// The most important text.
|
||||
primary: 'rgba(255,255,255,0.87)',
|
||||
// Secondary text.
|
||||
secondary: 'rgba(255, 255, 255, 0.56)',
|
||||
// Placeholder text for forms.
|
||||
placeholder: 'rgba(255, 255, 255, 0.24)',
|
||||
// Disabled text have even lower visual prominence.
|
||||
disabled: 'rgba(0, 0, 0, 0.24)',
|
||||
// For maximum contrast.
|
||||
contrast: '#FFFFFF',
|
||||
// For text on a background that is on a color opposite to the theme. For dark theme,
|
||||
// this would mean text that is on a light background.
|
||||
primaryInverse: '#324148',
|
||||
},
|
||||
|
||||
buttons: {
|
||||
text: 'rgba(255,255,255,0.87)',
|
||||
textDisabled: 'rgba(255, 255, 255, 0.3)',
|
||||
bgDisabled: 'rgba(255, 255, 255, 0.12)',
|
||||
|
||||
primary: {
|
||||
default: '#512FC9',
|
||||
hover: '#651FFF',
|
||||
active: '#354AA4',
|
||||
},
|
||||
|
||||
secondary: {
|
||||
default: '#222C59',
|
||||
hover: '#2C3A73',
|
||||
},
|
||||
|
||||
border: {
|
||||
default: '#2C3A73',
|
||||
hover: '#2C3A73',
|
||||
border: '#1C254D',
|
||||
borderHover: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
|
||||
warning: {
|
||||
default: '#d50000',
|
||||
hover: '#ff1744',
|
||||
},
|
||||
|
||||
outlinedPrimary: {
|
||||
text: '#651FFF',
|
||||
border: '#512FC9',
|
||||
borderHover: '#651FFF',
|
||||
borderActive: '#354AA4',
|
||||
},
|
||||
|
||||
outlinedDefault: {
|
||||
text: 'rgba(255,255,255,0.87)',
|
||||
textHover: '#FFFFFF',
|
||||
border: 'rgba(255,255,255,0.87)',
|
||||
borderHover: '#FFFFFF',
|
||||
},
|
||||
|
||||
trashButton: {
|
||||
default: '#2e3860',
|
||||
hover: '#414b70',
|
||||
},
|
||||
},
|
||||
|
||||
progressBarColor: '#00BFA5',
|
||||
|
||||
dark: '#000000',
|
||||
light: '#FFFFFF',
|
||||
|
||||
grey: {
|
||||
...blueGrey,
|
||||
},
|
||||
|
||||
error: {
|
||||
light: red['A200'],
|
||||
dark: red['A700'],
|
||||
main: red['A400'],
|
||||
},
|
||||
|
||||
action: {
|
||||
active: '#FFFFFF',
|
||||
hover: 'rgba(255, 255, 255, 0.1)',
|
||||
hoverOpacity: 0.1,
|
||||
selected: 'rgba(255, 255, 255, 0.2)',
|
||||
disabled: 'rgba(255, 255, 255, 0.3)',
|
||||
disabledBackground: 'rgba(255, 255, 255, 0.12)',
|
||||
},
|
||||
|
||||
subtle: blueGrey[50],
|
||||
link: lightBlue[500],
|
||||
bgTerminal: '#010B1C',
|
||||
danger: pink.A400,
|
||||
highlight: yellow[50],
|
||||
disabled: blueGrey[500],
|
||||
info: lightBlue[600],
|
||||
warning: orange.A400,
|
||||
success: teal.A700,
|
||||
};
|
||||
|
||||
const borders = [
|
||||
0,
|
||||
'1px solid',
|
||||
'2px solid',
|
||||
'4px solid',
|
||||
'8px solid',
|
||||
'16px solid',
|
||||
'32px solid',
|
||||
];
|
||||
|
||||
const theme = {
|
||||
colors,
|
||||
typography,
|
||||
font: fonts.sansSerif,
|
||||
fonts: fonts,
|
||||
fontWeights,
|
||||
fontSizes,
|
||||
space,
|
||||
borders,
|
||||
radii: [0, 2, 4, 8, 16, 9999, '100%'],
|
||||
regular: fontWeights.regular,
|
||||
bold: fontWeights.bold,
|
||||
// disabled media queries for styled-system
|
||||
breakpoints: [],
|
||||
};
|
||||
|
||||
export default theme;
|
||||
|
||||
export function getContrastText(background) {
|
||||
// Use the same logic as
|
||||
// Bootstrap: https://github.com/twbs/bootstrap/blob/1d6e3710dd447de1a200f29e8fa521f8a0908f70/scss/_functions.scss#L59
|
||||
// and material-components-web https://github.com/material-components/material-components-web/blob/ac46b8863c4dab9fc22c4c662dc6bd1b65dd652f/packages/mdc-theme/_functions.scss#L54
|
||||
const contrastText =
|
||||
getContrastRatio(background, colors.light) >= contrastThreshold
|
||||
? colors.light
|
||||
: colors.dark;
|
||||
|
||||
return contrastText;
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTheme } from 'styled-components';
|
||||
|
||||
import { Box, Indicator } from 'design';
|
||||
import {
|
||||
@@ -67,8 +66,6 @@ function DocumentSsh({ doc, visible }: PropTypes) {
|
||||
const ft = useFileTransfer(tty, session, doc, mfa);
|
||||
const { openedDialog: ftOpenedDialog } = useFileTransferContext();
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
function handleCloseFileTransfer() {
|
||||
terminalRef.current?.focus();
|
||||
}
|
||||
@@ -101,8 +98,6 @@ function DocumentSsh({ doc, visible }: PropTypes) {
|
||||
<Terminal
|
||||
ref={terminalRef}
|
||||
tty={tty}
|
||||
fontFamily={theme.fonts.mono}
|
||||
theme={theme.colors.terminal}
|
||||
disableCopy={disableCopy}
|
||||
terminalAddons={ref => (
|
||||
<>
|
||||
|
||||
@@ -16,14 +16,19 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
resolveColorTokens,
|
||||
useDesignSystemContext,
|
||||
} from '@gravitational/design-system';
|
||||
import { ITheme } from '@xterm/xterm';
|
||||
import React, {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import styled from 'styled-components';
|
||||
import styled, { useTheme } from 'styled-components';
|
||||
|
||||
import { Flex } from 'design';
|
||||
import { getPlatformType } from 'design/platform';
|
||||
@@ -37,8 +42,6 @@ import StyledXterm from '../../StyledXterm';
|
||||
|
||||
export interface TerminalProps {
|
||||
tty: Tty;
|
||||
fontFamily: string;
|
||||
theme: ITheme;
|
||||
// convertEol when set to true cursor will be set to the beginning of the next line with every received new line symbol.
|
||||
// This is equivalent to replacing each '\n' with '\r\n'.
|
||||
convertEol?: boolean;
|
||||
@@ -56,6 +59,13 @@ export const Terminal = forwardRef<TerminalRef, TerminalProps>((props, ref) => {
|
||||
const toastNotifications = useToastNotifications();
|
||||
// Keeps track of the notification id so that we can ensure there is only maximum one copy block notification showing at a time.
|
||||
const copyBlockedToastIdRef = useRef<string>(undefined);
|
||||
const theme = useTheme();
|
||||
const system = useDesignSystemContext();
|
||||
|
||||
const xtermTheme = useMemo<ITheme>(
|
||||
() => resolveColorTokens(system, theme.colors.terminal, theme.type),
|
||||
[system, theme.colors.terminal, theme.type]
|
||||
);
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
@@ -71,9 +81,9 @@ export const Terminal = forwardRef<TerminalRef, TerminalProps>((props, ref) => {
|
||||
|
||||
const termCtrl = new XTermCtrl(props.tty, {
|
||||
el: elementRef.current,
|
||||
fontFamily: props.fontFamily,
|
||||
fontFamily: theme.fonts.mono,
|
||||
fontSize,
|
||||
theme: props.theme,
|
||||
theme: xtermTheme,
|
||||
convertEol: props.convertEol,
|
||||
disableCopy: props.disableCopy,
|
||||
onCopyBlocked: () => {
|
||||
@@ -92,6 +102,7 @@ export const Terminal = forwardRef<TerminalRef, TerminalProps>((props, ref) => {
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
termCtrlRef.current = termCtrl;
|
||||
|
||||
termCtrl.open();
|
||||
@@ -118,8 +129,8 @@ export const Terminal = forwardRef<TerminalRef, TerminalProps>((props, ref) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
termCtrlRef.current?.updateTheme(props.theme);
|
||||
}, [props.theme]);
|
||||
termCtrlRef.current?.updateTheme(xtermTheme);
|
||||
}, [xtermTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.disableAutoFocus) {
|
||||
|
||||
@@ -41,6 +41,6 @@ export const StatusLight = styled(Box)<{ status: ItemStatus }>`
|
||||
if (status === ItemStatus.Warning) {
|
||||
return theme.colors.warning;
|
||||
}
|
||||
return theme.colors.grey[300]; // Unknown
|
||||
return theme.colors.blueGrey[300]; // Unknown
|
||||
}};
|
||||
`;
|
||||
|
||||
@@ -21,7 +21,6 @@ import { http, HttpResponse } from 'msw';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { MemoryRouter, Route, Routes } from 'react-router';
|
||||
|
||||
import darkTheme from 'design/theme/themes/darkTheme';
|
||||
import { ConfiguredThemeProvider } from 'design/ThemeProvider';
|
||||
import {
|
||||
enableMswServer,
|
||||
@@ -29,6 +28,7 @@ import {
|
||||
screen,
|
||||
server,
|
||||
testQueryClient,
|
||||
theme,
|
||||
userEvent,
|
||||
waitFor,
|
||||
} from 'design/utils/testing';
|
||||
@@ -316,7 +316,7 @@ function makeWrapper(options: {
|
||||
return (
|
||||
<MemoryRouter initialEntries={[initialUrl]}>
|
||||
<QueryClientProvider client={testQueryClient}>
|
||||
<ConfiguredThemeProvider theme={darkTheme}>
|
||||
<ConfiguredThemeProvider theme={theme}>
|
||||
<ContextProvider ctx={ctx}>
|
||||
<Routes>
|
||||
<Route path={cfg.routes.instances} element={children} />
|
||||
|
||||
@@ -68,7 +68,11 @@ export function LoginComponent({
|
||||
|
||||
return (
|
||||
<>
|
||||
<LogoHero />
|
||||
<div>
|
||||
<Box pt={3}>
|
||||
<LogoHero />
|
||||
</Box>
|
||||
</div>
|
||||
{showMotd ? (
|
||||
<Motd message={motd} onClick={acknowledgeMotd} />
|
||||
) : (
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
resolveColorTokens,
|
||||
useDesignSystemContext,
|
||||
} from '@gravitational/design-system';
|
||||
import {
|
||||
useCallback,
|
||||
useMemo,
|
||||
@@ -153,6 +157,7 @@ export function RecordingsList({
|
||||
}: RecordingsListProps) {
|
||||
const ctx = useTeleport();
|
||||
const theme = useTheme();
|
||||
const system = useDesignSystemContext();
|
||||
|
||||
const { clusterId } = useStickyClusterId();
|
||||
|
||||
@@ -241,8 +246,12 @@ export function RecordingsList({
|
||||
);
|
||||
|
||||
const thumbnailStyles = useMemo(
|
||||
() => generateTerminalSVGStyleTag(theme),
|
||||
[theme]
|
||||
() =>
|
||||
generateTerminalSVGStyleTag(
|
||||
resolveColorTokens(system, theme.colors.terminal, theme.type),
|
||||
theme.fonts.mono
|
||||
),
|
||||
[system, theme]
|
||||
);
|
||||
|
||||
const pageRecordings = useMemo(
|
||||
|
||||
@@ -16,8 +16,13 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type { LegacyThemeColors } from '@gravitational/design-system';
|
||||
import { useMemo } from 'react';
|
||||
import { type DefaultTheme } from 'styled-components';
|
||||
|
||||
type ResolvedTerminalColors = Record<
|
||||
keyof LegacyThemeColors['terminal'],
|
||||
string | undefined
|
||||
>;
|
||||
|
||||
export function useThumbnailSvg(svg: string, styles: string) {
|
||||
return useMemo(
|
||||
@@ -36,33 +41,36 @@ export function injectSVGStyles(svg: string, styles: string) {
|
||||
return svg.replace(/<svg[^>]*>/, match => `${match}${styleTag}`);
|
||||
}
|
||||
|
||||
export function generateTerminalSVGStyleTag(theme: DefaultTheme): string {
|
||||
export function generateTerminalSVGStyleTag(
|
||||
terminal: ResolvedTerminalColors,
|
||||
monoFont: string
|
||||
): string {
|
||||
const colorMap = [
|
||||
theme.colors.terminal.black,
|
||||
theme.colors.terminal.red,
|
||||
theme.colors.terminal.green,
|
||||
theme.colors.terminal.yellow,
|
||||
theme.colors.terminal.blue,
|
||||
theme.colors.terminal.magenta,
|
||||
theme.colors.terminal.cyan,
|
||||
theme.colors.terminal.white,
|
||||
theme.colors.terminal.brightBlack,
|
||||
theme.colors.terminal.brightRed,
|
||||
theme.colors.terminal.brightGreen,
|
||||
theme.colors.terminal.brightYellow,
|
||||
theme.colors.terminal.brightBlue,
|
||||
theme.colors.terminal.brightMagenta,
|
||||
theme.colors.terminal.brightCyan,
|
||||
theme.colors.terminal.brightWhite,
|
||||
terminal.black,
|
||||
terminal.red,
|
||||
terminal.green,
|
||||
terminal.yellow,
|
||||
terminal.blue,
|
||||
terminal.magenta,
|
||||
terminal.cyan,
|
||||
terminal.white,
|
||||
terminal.brightBlack,
|
||||
terminal.brightRed,
|
||||
terminal.brightGreen,
|
||||
terminal.brightYellow,
|
||||
terminal.brightBlue,
|
||||
terminal.brightMagenta,
|
||||
terminal.brightCyan,
|
||||
terminal.brightWhite,
|
||||
];
|
||||
|
||||
const rules: string[] = [
|
||||
'.i { font-style: italic; }',
|
||||
'.b { font-weight: bold; }',
|
||||
'.u { text-decoration: underline; }',
|
||||
`* { font-family: ${theme.fonts.mono} }`,
|
||||
`.terminal { fill: ${theme.colors.terminal.foreground}; }`,
|
||||
`.bg-default { fill: ${theme.colors.terminal.background}; }`,
|
||||
`* { font-family: ${monoFont} }`,
|
||||
`.terminal { fill: ${terminal.foreground}; }`,
|
||||
`.bg-default { fill: ${terminal.background}; }`,
|
||||
];
|
||||
|
||||
for (let i = 0; i < colorMap.length; i++) {
|
||||
|
||||
@@ -16,10 +16,15 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
resolveColorTokens,
|
||||
useDesignSystemContext,
|
||||
} from '@gravitational/design-system';
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useRef,
|
||||
type MouseEvent,
|
||||
type RefObject,
|
||||
@@ -127,6 +132,15 @@ export function RecordingTimeline({
|
||||
ref,
|
||||
}: RecordingTimelineProps) {
|
||||
const theme = useTheme();
|
||||
const system = useDesignSystemContext();
|
||||
|
||||
const resolvedTheme = useMemo(
|
||||
() => ({
|
||||
...theme,
|
||||
colors: resolveColorTokens(system, theme.colors, theme.type),
|
||||
}),
|
||||
[system, theme]
|
||||
);
|
||||
|
||||
const [height, setHeight] = useLocalStorage(
|
||||
KeysEnum.SESSION_RECORDING_TIMELINE_HEIGHT,
|
||||
@@ -168,7 +182,8 @@ export function RecordingTimeline({
|
||||
ctxRef.current.imageSmoothingEnabled = false;
|
||||
ctxRef.current.textRendering = 'optimizeLegibility';
|
||||
|
||||
ctxRef.current.fillStyle = theme.colors.sessionRecordingTimeline.background;
|
||||
ctxRef.current.fillStyle =
|
||||
resolvedTheme.colors.sessionRecordingTimeline.background;
|
||||
ctxRef.current.fillRect(0, 0, containerWidth, containerHeight);
|
||||
|
||||
rendererRef.current = new TimelineRenderer(
|
||||
@@ -177,11 +192,11 @@ export function RecordingTimeline({
|
||||
startTime,
|
||||
events,
|
||||
frames,
|
||||
theme,
|
||||
resolvedTheme,
|
||||
containerWidth,
|
||||
containerHeight
|
||||
);
|
||||
}, [duration, events, frames, startTime, theme]);
|
||||
}, [duration, events, frames, startTime, resolvedTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!rendererRef.current) {
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { darkTheme } from 'design/theme';
|
||||
import { theme } from 'design/utils/testing';
|
||||
|
||||
import {
|
||||
SessionRecordingEventType,
|
||||
@@ -54,7 +54,7 @@ function createRenderer(metadata?: Partial<SessionRecordingMetadata>) {
|
||||
...metadata,
|
||||
};
|
||||
|
||||
const renderer = new EventsRenderer(ctx, darkTheme, m.duration, m.events);
|
||||
const renderer = new EventsRenderer(ctx, theme, m.duration, m.events);
|
||||
|
||||
return { ctx, renderer };
|
||||
}
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { darkTheme } from 'design/theme';
|
||||
import { theme } from 'design/utils/testing';
|
||||
|
||||
import { SessionRecordingThumbnail } from 'teleport/services/recordings';
|
||||
import type { TimelineRenderContext } from 'teleport/SessionRecordings/view/Timeline/renderers/TimelineCanvasRenderer';
|
||||
@@ -99,7 +99,7 @@ async function createRenderer(
|
||||
|
||||
const renderer = new FramesRenderer(
|
||||
ctx,
|
||||
darkTheme,
|
||||
theme,
|
||||
duration,
|
||||
frames,
|
||||
initialHeight,
|
||||
|
||||
+4
-1
@@ -86,7 +86,10 @@ export class FramesRenderer extends TimelineCanvasRenderer {
|
||||
) {
|
||||
super(ctx, theme, duration);
|
||||
|
||||
const svgTheme = generateTerminalSVGStyleTag(theme);
|
||||
const svgTheme = generateTerminalSVGStyleTag(
|
||||
theme.colors.terminal,
|
||||
theme.fonts.mono
|
||||
);
|
||||
|
||||
this.frameHeight =
|
||||
initialHeight - eventsHeight - RULER_HEIGHT - EVENT_SECTION_PADDING * 2;
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { darkTheme } from 'design/theme';
|
||||
import { theme } from 'design/utils/testing';
|
||||
|
||||
import { SessionRecordingMetadata } from 'teleport/services/recordings';
|
||||
import { ProgressLineRenderer } from 'teleport/SessionRecordings/view/Timeline/renderers/ProgressLineRenderer';
|
||||
@@ -41,7 +41,7 @@ function createRenderer(duration: number) {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
|
||||
const renderer = new ProgressLineRenderer(ctx, darkTheme, duration);
|
||||
const renderer = new ProgressLineRenderer(ctx, theme, duration);
|
||||
|
||||
return { ctx, renderer };
|
||||
}
|
||||
|
||||
+2
-7
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { darkTheme } from 'design/theme';
|
||||
import { theme } from 'design/utils/testing';
|
||||
|
||||
import { SessionRecordingMetadata } from 'teleport/services/recordings';
|
||||
import type { TimelineRenderContext } from 'teleport/SessionRecordings/view/Timeline/renderers/TimelineCanvasRenderer';
|
||||
@@ -45,12 +45,7 @@ function createRenderer(metadata?: Partial<SessionRecordingMetadata>) {
|
||||
...metadata,
|
||||
};
|
||||
|
||||
const renderer = new TimeMarkersRenderer(
|
||||
ctx,
|
||||
darkTheme,
|
||||
m.duration,
|
||||
m.startTime
|
||||
);
|
||||
const renderer = new TimeMarkersRenderer(ctx, theme, m.duration, m.startTime);
|
||||
|
||||
return { ctx, renderer };
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user