Skip to content

Commit

Permalink
feat: add theming feature to elements (#3821)
Browse files Browse the repository at this point in the history
* feat: add theming to feature to elements

* fix: undefined property

* fix: ignore flow check

* fix: simplify import path

* feat: remove theme generator docs

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
tjuanitas and mergify[bot] authored Jan 16, 2025
1 parent 6655372 commit f39a65e
Show file tree
Hide file tree
Showing 14 changed files with 154 additions and 289 deletions.
10 changes: 10 additions & 0 deletions src/elements/common/theming/ThemingStyles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import useCustomTheming from './useCustomTheming';
import { ThemingProps } from './types';

const ThemingStyles = ({ selector, theme }: ThemingProps) => {
useCustomTheming({ selector, theme });

return null;
};

export default ThemingStyles;
48 changes: 48 additions & 0 deletions src/elements/common/theming/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { convertTokensToCustomProperties } from '../utils';

describe('elements/common/theming/utils', () => {
describe('convertTokensToCustomProperties()', () => {
test('returns correct mapping of tokens to custom properties', () => {
const tokens = {
Label: {
Bold: {
fontSize: '0.625rem',
fontWeight: '700',
letterSpacing: '0.0375rem',
lineHeight: '1rem',
paragraphSpacing: '0',
textCase: 'none',
textDecoration: 'none',
},
Default: {
fontSize: '0.625rem',
fontWeight: '400',
letterSpacing: '0.0375rem',
lineHeight: '1rem',
paragraphSpacing: '0',
textCase: 'none',
textDecoration: 'none',
},
},
};
const output = {
'--label-bold-font-size': '0.625rem',
'--label-bold-font-weight': '700',
'--label-bold-letter-spacing': '0.0375rem',
'--label-bold-line-height': '1rem',
'--label-bold-paragraph-spacing': '0',
'--label-bold-text-case': 'none',
'--label-bold-text-decoration': 'none',
'--label-default-font-size': '0.625rem',
'--label-default-font-weight': '400',
'--label-default-letter-spacing': '0.0375rem',
'--label-default-line-height': '1rem',
'--label-default-paragraph-spacing': '0',
'--label-default-text-case': 'none',
'--label-default-text-decoration': 'none',
};
const result = convertTokensToCustomProperties(tokens);
expect(result).toEqual(output);
});
});
});
2 changes: 2 additions & 0 deletions src/elements/common/theming/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default } from './ThemingStyles';
export * from './types';
8 changes: 8 additions & 0 deletions src/elements/common/theming/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface Theme {
tokens?: Record<string, unknown>;
}

export interface ThemingProps {
selector?: string;
theme?: Theme;
}
25 changes: 25 additions & 0 deletions src/elements/common/theming/useCustomTheming.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useEffect } from 'react';
import { convertTokensToCustomProperties } from './utils';
import { ThemingProps } from './types';

const useCustomTheming = ({ selector, theme = {} }: ThemingProps) => {
const { tokens } = theme;

const customProperties = convertTokensToCustomProperties(tokens);
const styles = Object.entries(customProperties)
.map(([token, value]) => `${token}: ${value}`)
.join(';');

useEffect(() => {
const styleEl = document.createElement('style');
document.head.appendChild(styleEl);

styleEl.sheet.insertRule(`${selector ?? ':root'} { ${styles} }`);

return () => {
document.head.removeChild(styleEl);
};
}, [selector, styles]);
};

export default useCustomTheming;
23 changes: 23 additions & 0 deletions src/elements/common/theming/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import isObject from 'lodash/isObject';
import kebabCase from 'lodash/kebabCase';

const convertTokensToCustomProperties = (tokens = {}, prefix = '') => {
const customProperties = {};

Object.entries(tokens).forEach(([level, value]) => {
const levelName = `${prefix}${kebabCase(level)}`;

if (isObject(value)) {
const properties = convertTokensToCustomProperties(value, `${levelName}-`);
Object.entries(properties).forEach(([tokenName, tokenValue]) => {
customProperties[tokenName] = tokenValue;
});
} else {
customProperties[`--${levelName}`] = value;
}
});

return customProperties;
};

export { convertTokensToCustomProperties };
7 changes: 7 additions & 0 deletions src/elements/content-explorer/ContentExplorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import SubHeader from '../common/sub-header/SubHeader';
import makeResponsive from '../common/makeResponsive';
import openUrlInsideIframe from '../../utils/iframe';
import Internationalize from '../common/Internationalize';
// $FlowFixMe TypeScript file
import ThemingStyles from '../common/theming';
import API from '../../api';
import MetadataQueryAPIHelper from '../../features/metadata-based-view/MetadataQueryAPIHelper';
import Footer from './Footer';
Expand Down Expand Up @@ -67,6 +69,8 @@ import {
VIEW_MODE_GRID,
} from '../../constants';
import type { ViewMode } from '../common/flowTypes';
// $FlowFixMe TypeScript file
import type { Theme } from '../common/theming';
import type { MetadataQuery, FieldsToShow } from '../../common/types/metadataQueries';
import type { MetadataFieldValue } from '../../common/types/metadata';
import type {
Expand Down Expand Up @@ -139,6 +143,7 @@ type Props = {
sortDirection: SortDirection,
staticHost: string,
staticPath: string,
theme?: Theme,
token: Token,
uploadHost: string,
};
Expand Down Expand Up @@ -1615,6 +1620,7 @@ class ContentExplorer extends Component<Props, State> {
staticHost,
staticPath,
previewLibraryVersion,
theme,
token,
uploadHost,
}: Props = this.props;
Expand Down Expand Up @@ -1659,6 +1665,7 @@ class ContentExplorer extends Component<Props, State> {
return (
<Internationalize language={language} messages={messages}>
<div id={this.id} className={styleClassName} ref={measureRef} data-testid="content-explorer">
<ThemingStyles selector={`#${this.id}`} theme={theme} />
<div className="be-app-element" onKeyDown={this.onKeyDown} tabIndex={0}>
{!isDefaultViewMetadata && (
<>
Expand Down
7 changes: 7 additions & 0 deletions src/elements/content-picker/ContentPicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import UploadDialog from '../common/upload-dialog';
import CreateFolderDialog from '../common/create-folder-dialog';
import Internationalize from '../common/Internationalize';
import makeResponsive from '../common/makeResponsive';
// $FlowFixMe TypeScript file
import ThemingStyles from '../common/theming';
import Pagination from '../../features/pagination';
import { isFocusableElement, isInputElement, focus } from '../../utils/dom';
import API from '../../api';
Expand Down Expand Up @@ -51,6 +53,8 @@ import {
VIEW_SELECTED,
} from '../../constants';
import { FILE_SHARED_LINK_FIELDS_TO_FETCH } from '../../utils/fields';
// $FlowFixMe TypeScript file
import type { Theme } from '../common/theming';
import type { ElementsXhrError } from '../../common/types/api';
import type {
View,
Expand Down Expand Up @@ -114,6 +118,7 @@ type Props = {
showSelectedButton: boolean,
sortBy: SortBy,
sortDirection: SortDirection,
theme?: Theme,
token: Token,
type: string,
uploadHost: string,
Expand Down Expand Up @@ -1206,6 +1211,7 @@ class ContentPicker extends Component<Props, State> {
responseInterceptor,
renderCustomActionButtons,
showSelectedButton,
theme,
}: Props = this.props;
const {
view,
Expand Down Expand Up @@ -1234,6 +1240,7 @@ class ContentPicker extends Component<Props, State> {
return (
<Internationalize language={language} messages={messages}>
<div id={this.id} className={styleClassName} ref={measureRef} data-testid="content-picker">
<ThemingStyles selector={`#${this.id}`} theme={theme} />
<div className="be-app-element" onKeyDown={this.onKeyDown} tabIndex={0}>
<Header
view={view}
Expand Down
7 changes: 7 additions & 0 deletions src/elements/content-preview/ContentPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import makeResponsive from '../common/makeResponsive';
import { withNavRouter } from '../common/nav-router';
import Internationalize from '../common/Internationalize';
import AsyncLoad from '../common/async-load';
// $FlowFixMe TypeScript file
import ThemingStyles from '../common/theming';
import TokenService from '../../utils/TokenService';
import { isInputElement, focus } from '../../utils/dom';
import { getTypedFileId } from '../../utils/file';
Expand Down Expand Up @@ -62,6 +64,8 @@ import type { StringMap, Token, BoxItem, BoxItemVersion } from '../../common/typ
import type { VersionChangeCallback } from '../content-sidebar/versions';
import type { FeatureConfig } from '../common/feature-checking';
import type { WithAnnotationsProps, WithAnnotatorContextProps } from '../common/annotator-context';
// $FlowFixMe TypeScript file
import type { Theme } from '../common/theming';
import type APICache from '../../utils/Cache';

import '../common/fonts.scss';
Expand Down Expand Up @@ -123,6 +127,7 @@ type Props = {
showAnnotationsControls?: boolean,
staticHost: string,
staticPath: string,
theme?: Theme,
token: Token,
useHotkeys: boolean,
} & ErrorContextProps &
Expand Down Expand Up @@ -1282,6 +1287,7 @@ class ContentPreview extends React.PureComponent<Props, State> {
sharedLinkPassword,
requestInterceptor,
responseInterceptor,
theme,
}: Props = this.props;

const {
Expand Down Expand Up @@ -1326,6 +1332,7 @@ class ContentPreview extends React.PureComponent<Props, State> {
onKeyDown={this.onKeyDown}
tabIndex={0}
>
<ThemingStyles selector={`#${this.id}`} theme={theme} />
{hasHeader && (
<PreviewHeader
file={file}
Expand Down
5 changes: 5 additions & 0 deletions src/elements/content-sidebar/ContentSidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ import type { MetadataEditor } from '../../common/types/metadata';
import type { StringMap, Token, User, BoxItem } from '../../common/types/core';
import type { AdditionalSidebarTab } from './flowTypes';
import type { FeatureConfig } from '../common/feature-checking';
// $FlowFixMe TypeScript file
import type { Theme } from '../common/theming';
import type APICache from '../../utils/Cache';

import '../common/fonts.scss';
Expand Down Expand Up @@ -84,6 +86,7 @@ type Props = {
responseInterceptor?: Function,
sharedLink?: string,
sharedLinkPassword?: string,
theme?: Theme,
token: Token,
versionsSidebarProps: VersionsSidebarProps,
} & ErrorContextProps &
Expand Down Expand Up @@ -363,6 +366,7 @@ class ContentSidebar extends React.Component<Props, State> {
onPanelChange,
onVersionChange,
onVersionHistoryClick,
theme,
versionsSidebarProps,
}: Props = this.props;
const { file, isLoading, metadataEditors }: State = this.state;
Expand Down Expand Up @@ -404,6 +408,7 @@ class ContentSidebar extends React.Component<Props, State> {
onPanelChange={onPanelChange}
onVersionChange={onVersionChange}
onVersionHistoryClick={onVersionHistoryClick}
theme={theme}
versionsSidebarProps={versionsSidebarProps}
wrappedComponentRef={ref => {
this.sidebarRef = ref;
Expand Down
7 changes: 7 additions & 0 deletions src/elements/content-sidebar/Sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import LocalStore from '../../utils/LocalStore';
import SidebarNav from './SidebarNav';
import SidebarPanels from './SidebarPanels';
import SidebarUtils from './SidebarUtils';
// $FlowFixMe TypeScript file
import ThemingStyles from '../common/theming';
import { withCurrentUser } from '../common/current-user';
import { isFeatureEnabled, withFeatureConsumer } from '../common/feature-checking';
import type { FeatureConfig } from '../common/feature-checking';
Expand All @@ -30,6 +32,8 @@ import type { AdditionalSidebarTab } from './flowTypes';
import type { MetadataEditor } from '../../common/types/metadata';
import type { BoxItem, User } from '../../common/types/core';
import type { Errors } from '../common/flowTypes';
// $FlowFixMe TypeScript file
import type { Theme } from '../common/theming';
import { SIDEBAR_VIEW_DOCGEN } from '../../constants';
import API from '../../api';

Expand Down Expand Up @@ -65,6 +69,7 @@ type Props = {
onPanelChange?: (name: string, isInitialState: boolean) => void,
onVersionChange?: Function,
onVersionHistoryClick?: Function,
theme?: Theme,
versionsSidebarProps: VersionsSidebarProps,
};

Expand Down Expand Up @@ -304,6 +309,7 @@ class Sidebar extends React.Component<Props, State> {
metadataSidebarProps,
onAnnotationSelect,
onVersionChange,
theme,
versionsSidebarProps,
}: Props = this.props;
const isOpen = this.isOpen();
Expand All @@ -321,6 +327,7 @@ class Sidebar extends React.Component<Props, State> {

return (
<aside id={this.id} className={styleClassName} data-testid="preview-sidebar">
<ThemingStyles selector={`#${this.id}`} theme={theme} />
{isLoading ? (
<div className="bcs-loading">
<LoadingIndicator />
Expand Down
5 changes: 5 additions & 0 deletions src/elements/content-uploader/ContentUploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import API from '../../api';
import Browser from '../../utils/Browser';
import Internationalize from '../common/Internationalize';
import makeResponsive from '../common/makeResponsive';
import ThemingStyles, { Theme } from '../common/theming';
import FolderUpload from '../../api/uploads/FolderUpload';
import { getTypedFileId, getTypedFolderId } from '../../utils/file';
import {
Expand Down Expand Up @@ -94,6 +95,7 @@ export interface ContentUploaderProps {
rootFolderId: string;
sharedLink?: string;
sharedLinkPassword?: string;
theme?: Theme;
token?: Token;
uploadHost: string;
useUploadsManager?: boolean;
Expand Down Expand Up @@ -1249,6 +1251,7 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
messages,
onClose,
onUpgradeCTAClick,
theme,
useUploadsManager,
}: ContentUploaderProps = this.props;
const { view, items, errorCode, isUploadsManagerExpanded }: State = this.state;
Expand All @@ -1269,6 +1272,7 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
<TooltipProvider>
{useUploadsManager ? (
<div ref={measureRef} className={styleClassName} id={this.id}>
<ThemingStyles selector={`#${this.id}`} theme={theme} />
<UploadsManager
isDragging={isDraggingItemsToUploadsManager}
isExpanded={isUploadsManagerExpanded}
Expand All @@ -1285,6 +1289,7 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
</div>
) : (
<div ref={measureRef} className={styleClassName} id={this.id}>
<ThemingStyles selector={`#${this.id}`} theme={theme} />
<DroppableContent
addDataTransferItemsToUploadQueue={this.addDroppedItemsToUploadQueue}
addFiles={this.addFilesToUploadQueue}
Expand Down
Loading

0 comments on commit f39a65e

Please sign in to comment.