UNPKG

@jupyterlab/ui-components

Version:

JupyterLab - UI components written in React

547 lines (492 loc) 13.4 kB
// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { style as typestyleClass } from 'typestyle/lib'; import { NestedCSSProperties } from 'typestyle/lib/types'; export namespace LabIconStyle { /** * - breadCrumb: The path icons above the filebrowser * - commandPaletteHeader: The icon to the right of palette section headers * - commandPaletteItem: The icon next to a palette item * - launcherCard: The icons for the cards at the bottom of the launcher * - launcherSection: The icons to left of the Launcher section headers * - listing: The icons to the left of the filebrowser listing items * - listingHeaderItem: Caret icons used to show sort order in listing column headers * - mainAreaTab: The icons in the tabs above the main area/the tabManager in the sidebar * - menuItem: The icon next to a menu item * - runningItem: The icon next to an item in the Running sidebar * - select: The caret icon on the left side of a dropdown select element * - settingsEditor: The icons to the left of each section of the settings editor * - sideBar: The icons for the sidebar (default to the left of the main window) * - splash: The icon used for the splash screen * - statusBar: The icons in the status bar * - toolbarButton: The icon shown on a toolbar button */ type IBuiltin = | 'breadCrumb' | 'commandPaletteHeader' | 'commandPaletteItem' | 'launcherCard' | 'launcherSection' | 'listing' | 'listingHeaderItem' | 'mainAreaTab' | 'menuItem' | 'runningItem' | 'select' | 'settingsEditor' | 'sideBar' | 'splash' | 'statusBar' | 'toolbarButton'; type IPosition = | 'center' | 'top' | 'right' | 'bottom' | 'left' | 'top right' | 'bottom right' | 'bottom left' | 'top left'; type ISize = 'small' | 'normal' | 'large' | 'xlarge'; /** * Options that function as a shorthand for compound CSS properties, * such as the set of props required to center an svg inside * of a parent node */ interface ISheetOptions { /** * How to position the inner svg element, * relative to the outer container */ elementPosition?: IPosition; /** * the size of the inner svg element. Can be any of: * - 'small': 14px x 14px * - 'normal': 16px x 16px * - 'large': 20px x 20px * - 'xlarge': 24px x 24px */ elementSize?: ISize; /** * FUTURE: how to position the label element (if any), * relative to the outer container */ // labelPosition?: IPosition; } /** * Stylesheet with a collection of CSS props for each node * in an icon, plus some custom options */ interface ISheet { /** * CSS properties that will be applied to the outer container * element via a typestyle class */ container?: NestedCSSProperties; /** * CSS properties that will be applied to the inner svg * element via a typestyle class */ element?: NestedCSSProperties; /** * Options that function as modifiers for this style's * CSS properties */ options?: ISheetOptions; /** * FUTURE: CSS properties that will be applied to the label * element, if any, via a typestyle class */ // labelCSS?: NestedCSSProperties; } /** * A stylesheet containing only collections of CSS style props that * can be fed directly to typestyle's style() function. A standard * ISheet can be resolved to a "pure" stylesheet by processing and * removing any options */ interface ISheetPure extends ISheet { /** * Options are disallowed */ options?: undefined; } /** * Type to help with resolving a stylesheet that might be a string */ type ISheetResolvable = ISheet | IBuiltin; export interface IProps extends NestedCSSProperties, ISheetOptions { /** * Specify the icon styling. Can be either a string naming one of * the builtin icon stylesheets, a LabIconStyle.ISheet object, or an * array containing any mixture of the two. If an array is provided, * the actual style will be determined by merging the stylesheets in * the array, giving precedence to the rightmost values. */ stylesheet?: ISheetResolvable | ISheetResolvable[]; } /** * The builtin stylesheets */ const builtinSheets: { [k in IBuiltin]: ISheet } = { breadCrumb: { container: { $nest: { // `&` will be substituted for the generated classname (interpolation) '&:first-child svg': { bottom: '1px', marginLeft: '0px', position: 'relative' }, '&:hover': { backgroundColor: 'var(--jp-layout-color2)' }, ['.jp-mod-dropTarget&']: { backgroundColor: 'var(--jp-brand-color2)', opacity: 0.7 } } }, element: { borderRadius: 'var(--jp-border-radius)', cursor: 'pointer', margin: '0px 2px', padding: '0px 2px', height: '16px', width: '16px', verticalAlign: 'middle' } }, commandPaletteHeader: { container: { height: '14px', margin: '0 14px 0 auto' }, element: { height: '14px', width: '14px' }, options: { elementPosition: 'center' } }, commandPaletteItem: { element: { height: '16px', width: '16px' }, options: { elementPosition: 'center' } }, launcherCard: { container: { height: '52px', width: '52px' }, element: { height: '52px', width: '52px' }, options: { elementPosition: 'center' } }, launcherSection: { container: { boxSizing: 'border-box', marginRight: '12px', height: '32px', width: '32px' }, element: { height: '32px', width: '32px' }, options: { elementPosition: 'center' } }, listing: { container: { flex: '0 0 20px', marginRight: '4px', position: 'relative' }, element: { height: '16px', width: '16px' }, options: { elementPosition: 'center' } }, listingHeaderItem: { container: { display: 'inline', height: '16px', width: '16px' }, element: { height: 'auto', margin: '-2px 0 0 0', width: '20px' }, options: { elementPosition: 'center' } }, mainAreaTab: { container: { $nest: { '.lm-DockPanel-tabBar &': { marginRight: '4px' } } }, element: { $nest: { '.lm-DockPanel-tabBar &': { height: '14px', width: '14px' } } }, options: { elementPosition: 'center' } }, menuItem: { container: { display: 'inline-block', verticalAlign: 'middle' }, element: { height: '16px', width: '16px' }, options: { elementPosition: 'center' } }, runningItem: { container: { margin: '0px 4px 0px 4px' }, element: { height: '16px', width: '16px' }, options: { elementPosition: 'center' } }, select: { container: { pointerEvents: 'none' }, element: { position: 'absolute', height: 'auto', width: '16px' } }, settingsEditor: { container: { display: 'flex', flex: '0 0 20px', margin: '0 3px 0 0', position: 'relative', height: '20px', width: '20px' }, element: { height: '16px', width: '16px' }, options: { elementPosition: 'center' } }, sideBar: { element: { height: 'auto', width: '20px' }, options: { elementPosition: 'center' } }, splash: { container: { animation: '0.3s fade-in linear forwards', height: '100%', width: '100%', zIndex: 1 }, element: { // width no height width: '100px' }, options: { elementPosition: 'center' } }, statusBar: { element: { left: '0px', top: '0px', height: '18px', width: '20px', position: 'relative' } }, toolbarButton: { container: { display: 'inline-block', verticalAlign: 'middle' }, element: { height: '16px', width: '16px' }, options: { elementPosition: 'center' } } }; function _elementPositionFactory(extra: NestedCSSProperties): ISheet { return { container: { alignItems: 'center', display: 'flex' }, element: { display: 'block', ...extra } }; } /** * Styles to help with positioning */ const positionSheets: { [k in IPosition]: ISheet } = { center: _elementPositionFactory({ margin: '0 auto', width: '100%' }), top: _elementPositionFactory({ margin: '0 0 auto 0' }), right: _elementPositionFactory({ margin: '0 0 0 auto' }), bottom: _elementPositionFactory({ margin: 'auto 0 0 0' }), left: _elementPositionFactory({ margin: '0 auto 0 0' }), 'top right': _elementPositionFactory({ margin: '0 0 auto auto' }), 'bottom right': _elementPositionFactory({ margin: 'auto 0 0 auto' }), 'bottom left': _elementPositionFactory({ margin: 'auto auto 0 0' }), 'top left': _elementPositionFactory({ margin: '0 auto 0 auto' }) }; function _elementSizeFactory(size: string): ISheet { return { element: { height: size, width: size } }; } /** * sheets that establish some default sizes */ const sizeSheets: { [k in ISize]: ISheet } = { small: _elementSizeFactory('14px'), normal: _elementSizeFactory('16px'), large: _elementSizeFactory('20px'), xlarge: _elementSizeFactory('24px') }; /** * Merge two or more icon sheets into a single "pure" * icon style (ie collections of CSS props only) */ function mergeSheets(sheets: ISheet[]): ISheetPure { return { container: Object.assign({}, ...sheets.map(s => s.container)), element: Object.assign({}, ...sheets.map(s => s.element)) }; } /** * Resolve one or more stylesheets that may just be a string naming * one of the builtin stylesheets to an array of proper ISheet objects */ function resolveSheet( stylesheet: ISheetResolvable | ISheetResolvable[] | undefined ): ISheet[] { if (!stylesheet) { return []; } if (!Array.isArray(stylesheet)) { // wrap in array stylesheet = [stylesheet]; } return stylesheet.map(k => (typeof k === 'string' ? builtinSheets[k] : k)); } /** * Resolve and merge multiple icon stylesheets */ function applySheetOptions(sheets: ISheet[]) { const options: ISheetOptions = Object.assign( {}, ...sheets.map(s => s.options) ); if (options.elementPosition) { sheets.unshift(positionSheets[options.elementPosition]); } if (options.elementSize) { sheets.unshift(sizeSheets[options.elementSize]); } return mergeSheets(sheets); } /** * Resolve a pure icon stylesheet into a typestyle class */ function resolveStyleClass(stylesheet: ISheetPure): string { return typestyleClass({ ...stylesheet.container, $nest: { ...stylesheet.container?.$nest, ['svg']: stylesheet.element } }); } // cache style classes for builtin stylesheets const _styleClassCache = new Map<string, string>(); /** * Get a typestyle class, given a set of icon styling props */ export function styleClass(props?: IProps): string { if (!props || Object.keys(props).length === 0) { // props is empty return ''; } let { elementPosition, elementSize, stylesheet, ...elementCSS } = props; // add option args with defined values to overrides const options = { ...(elementPosition && { elementPosition }), ...(elementSize && { elementSize }) }; // try to look up the style class in the cache const cacheable = typeof stylesheet === 'string' && Object.keys(elementCSS).length === 0; const cacheKey = cacheable ? [stylesheet, elementPosition, elementSize].join(',') : ''; if (cacheable && _styleClassCache.has(cacheKey)) { return _styleClassCache.get(cacheKey)!; } // resolve kind to an array of sheets, then stick overrides on the end const sheets = resolveSheet(stylesheet); sheets.push({ element: elementCSS, options }); // apply style options/merge sheets, then convert to typestyle class const cls = resolveStyleClass(applySheetOptions(sheets)); if (cacheable) { // store in cache for later reuse _styleClassCache.set(cacheKey, cls); } return cls; } }