Merge pull request #15 from microsoft/new-controls

New controls
This commit is contained in:
Nathan Evans 2021-05-06 15:32:53 -07:00 коммит произвёл GitHub
Родитель f28ea7155d 6ba3c7e394
Коммит 03ff6923b3
60 изменённых файлов: 8982 добавлений и 26179 удалений

Просмотреть файл

@ -1,6 +0,0 @@
storybook_static/
build/
lib/
dist/
.yarn/
CODE_OF_CONDUCT.md

Просмотреть файл

@ -1,3 +0,0 @@
{
"allow": ["color", "colors", "hook", "hooks", "periods", "stroke"]
}

21458
.pnp.js сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,36 +0,0 @@
node_modules
http
https
theming
useThematic
d3
e.g.
ThematicProvider
ThemeProvider
FluentTheme
jsx
office-ui-fabric-react
ApplicationStyles
fluentui
monorepo
vega
svg
MarkConfig
blindnesses
npm
webapp
codebase
plotArea
selection.call
attr
README.md
Webpack
filenames
microsoft.com
DotNet
AspNet
Xamarin
msrc
repos
microsoft.github.io
localhost:8080

55
.yarn/releases/yarn-2.3.3.cjs поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

55
.yarn/releases/yarn-2.4.1.cjs поставляемый Executable file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

11
.yarn/versions/fbdb1f25.yml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,11 @@
releases:
"@thematic/color": major
"@thematic/core": major
"@thematic/d3": major
"@thematic/fluent": major
"@thematic/react": major
"@thematic/vega": major
"@thematic/webapp": major
declined:
- "@thematic/root"

Просмотреть файл

@ -1,3 +1,19 @@
changesetBaseRefs:
- main
- origin/main
- upstream/main
changesetIgnorePatterns:
- '**/*.spec.{js,ts,tsx}'
packageExtensions:
'@fluentui/font-icons-mdl2@*':
peerDependencies:
react: '*'
'@fluentui/style-utilities@*':
peerDependencies:
react: '*'
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
spec: '@yarnpkg/plugin-workspace-tools'
@ -8,20 +24,4 @@ plugins:
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
spec: '@yarnpkg/plugin-typescript'
yarnPath: .yarn/releases/yarn-2.3.3.cjs
changesetBaseRefs:
- main
- origin/main
- upstream/main
changesetIgnorePatterns:
- '**/*.spec.{js,ts,tsx}'
packageExtensions:
'@uifabric/icons@*':
peerDependencies:
react: '*'
'@uifabric/styling@*':
peerDependencies:
react: '*'
yarnPath: .yarn/releases/yarn-2.4.1.cjs

11
babel.config.js Normal file
Просмотреть файл

@ -0,0 +1,11 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
'@babel/preset-typescript',
'@babel/preset-react',
],
}

Просмотреть файл

@ -3,4 +3,4 @@
* Licensed under the MIT license. See LICENSE file in the project.
*/
const { configure } = require('@essex/jest-config')
module.exports = configure(['<rootDir>/jest.setup.js'], 'tsconfig.jest.json')
module.exports = configure()

Просмотреть файл

@ -1,9 +0,0 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
require('regenerator-runtime/runtime')
const enzyme = require('enzyme')
const Adapter = require('enzyme-adapter-react-16')
enzyme.configure({ adapter: new Adapter() })

Просмотреть файл

@ -10,12 +10,12 @@
"start_all": "yarn workspaces foreach -piv run start",
"publish_all": "yarn workspaces foreach --exclude '@thematic/root' -pv npm publish --tolerate-republish --access public",
"lint": "essex lint",
"lint:ci": "essex lint --docs",
"lint:fix": "essex lint --fix",
"prettify": "essex prettify",
"test": "essex test",
"test:ci": "essex test --coverage",
"git-is-clean": "essex git-is-clean",
"ci": "npm-run-all -p lint:ci build_all bundle_all test:ci -s git-is-clean",
"ci": "npm-run-all -p lint build_all bundle_all test:ci -s git-is-clean",
"release": "run-s build_all publish_all",
"start": "yarn start:webapp"
},
@ -25,32 +25,29 @@
]
},
"devDependencies": {
"@essex/eslint-config": "^10.0.1",
"@essex/eslint-plugin": "^10.0.1",
"@essex/jest-config": "^10.0.2",
"@babel/core": "^7.14.0",
"@babel/preset-env": "^7.14.1",
"@babel/preset-react": "^7.13.13",
"@babel/preset-typescript": "^7.13.0",
"@essex/eslint-config": "^14.0.0",
"@essex/eslint-plugin": "^14.0.0",
"@essex/jest-config": "^14.0.0",
"@essex/prettier-config": "^10.0.0",
"@essex/scripts": "^11.0.1",
"@essex/scripts": "^14.0.0",
"@types/jest": "^26.0.15",
"@types/node": "^14.14.0",
"@types/prettier": "^2",
"@typescript-eslint/eslint-plugin": "^4.4.1",
"@typescript-eslint/parser": "^4.4.1",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.5",
"eslint-import-resolver-node": "^0.3.4",
"husky": "^4.3.0",
"lint-staged": "^10.4.2",
"npm-run-all": "^4.1.5",
"react": "^17.0.0",
"react-dom": "^17.0.0",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"regenerator-runtime": "^0.13.7",
"ts-jest": "^26.4.1",
"typescript": "^3.9.7"
},
"husky": {
"hooks": {
"pre-commit": "essex pre-commit"
}
},
"prettier": "@essex/prettier-config"
}

Просмотреть файл

@ -19,10 +19,12 @@
"hsluv": "^0.1.0"
},
"devDependencies": {
"@essex/scripts": "^11.0.1",
"@types/node": "^14.14.0"
"@essex/scripts": "^14.0.0",
"@types/node": "^14.14.0",
"core-js": "^3.11.1"
},
"peerDependencies": {
"@types/node": "*"
"@types/node": "*",
"core-js": "^3.11.1"
}
}

Просмотреть файл

@ -14,23 +14,29 @@ import { css2rgbaVector, css2rgbaNumber, css2css, css2hex } from './chroma'
* Note that the naming of these intentionally matches the string values on the ColorSpace enum.
*/
export class Color {
private _css: string
private _raw: string
private _alpha: number
constructor(css: string, alpha?: number) {
this._css = css
this._raw = css
this._alpha = alpha !== undefined ? alpha : 1.0
}
/**
* Direct passthrough of string used to create the color, to avoid any transform.
*/
get raw(): string {
return this._raw
}
hex(alpha?: number): string {
return css2hex(this._css, alpha || this._alpha)
return css2hex(this._raw, alpha || this._alpha)
}
css(alpha?: number): string {
return css2css(this._css, alpha || this._alpha)
return css2css(this._raw, alpha || this._alpha)
}
rgbav(alpha?: number): [number, number, number, number] {
return css2rgbaVector(this._css, alpha || this._alpha)
return css2rgbaVector(this._raw, alpha || this._alpha)
}
rgbaint(alpha?: number): number {
return css2rgbaNumber(this._css, alpha || this._alpha)
return css2rgbaNumber(this._raw, alpha || this._alpha)
}
toString(): string {
return this.hex()

Просмотреть файл

@ -2,7 +2,7 @@
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { ColorMaker, polynomial_scale } from '../HsluvColorLogic'
import { ColorMaker, polynomial_scale } from '../scheme/HsluvColorLogic'
const GREYS = [
[50, 0, 92],

Просмотреть файл

@ -177,6 +177,7 @@ export function css2css(css: string, alpha?: number): string {
* Note that we handle 'none' as 'none' here because it is used frequently to indicate empty fills and strokes in svg,
* even though it is not a valid hex code.
* Note: if you are using this for a css color, use css2css as it will return valid 'transparent' for our empty 'none'.
* Also note that if alpha is passed, chroma-js will create an 8-letter hex string.
* @param css
* @param alpha
*/

Просмотреть файл

@ -6,7 +6,7 @@ export * from './interfaces'
export * from './chroma'
export * from './colorBlindness'
export * from './Color'
export { getScheme } from './getScheme'
export * from './scheme'
// TODO: best would be to update the scheme compute to accept undefined or baseline params and handle an "empty" case, which is maybe grayscale
export const defaultParams = {

Просмотреть файл

@ -3,7 +3,7 @@
* Licensed under the MIT license. See LICENSE file in the project.
*/
/* eslint-disable @typescript-eslint/no-var-requires */
import { Params, Scheme } from './interfaces'
import { Params, Scheme } from '../interfaces'
const chroma = require('chroma-js')
const hsluv = require('hsluv')

Просмотреть файл

@ -0,0 +1,34 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { Color } from '../Color'
import { Scheme } from '../interfaces'
/**
* Extracts a thematic Color using its scheme "path".
* @param scheme
* @param path
*/
export function getNamedSchemeColor(scheme: Scheme, path?: string): Color {
if (!path || path === 'none') {
return new Color('none')
}
const indexed = indexedTest(path)
if (indexed) {
return new Color((scheme as any)[indexed.name][indexed.index])
}
return new Color((scheme as any)[path])
}
function indexedTest(path: string) {
const indexedName = path.match(/(\w+)\[/)
const indexedIndex = path.match(/\[(\d{1,2})\]/)
if (indexedName && indexedIndex) {
return {
name: indexedName[1],
index: +indexedIndex[1],
}
}
}

Просмотреть файл

Просмотреть файл

@ -0,0 +1,7 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
export { getScheme } from './getScheme'
export * from './getNamedSchemeColor'
export * from './isNominal'

Просмотреть файл

@ -0,0 +1,7 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
export function isNominal(name?: string): boolean {
return name === 'nominal' || name === 'nominalBold' || name === 'nominalMuted'
}

Просмотреть файл

@ -26,15 +26,17 @@
"murmurhash-js": "^1.0.0"
},
"devDependencies": {
"@essex/scripts": "^11.0.1",
"@essex/scripts": "^14.0.0",
"@types/d3-scale": "^2.2.4",
"@types/ncp": "^2",
"@types/node": "^14.14.0",
"core-js": "^3.11.1",
"ncp": "^2.0.0",
"npm-run-all": "^4.1.5"
},
"peerDependencies": {
"@types/d3-scale": ">= 2",
"@types/node": "*"
"@types/node": "*",
"core-js": "^3.11.1"
}
}

Просмотреть файл

@ -34,7 +34,7 @@ export const defaultThemes: ThemeListing[] = Object.entries(themes).map(
)
/**
* Load the default Essex theme
* Load the default theme
* Use config to supply overrides to specific definition blocks
* @param config
*/

Просмотреть файл

@ -36,7 +36,7 @@ export function log(domain: number[], clamp = true): (value: number) => number {
// holds an array of numbers along with the bounds
// this replicates the histogram format of d3
interface Bin extends Array<number> {
export interface Bin extends Array<number> {
x0?: number
x1?: number
}

Просмотреть файл

@ -15,9 +15,9 @@ import {
} from '@thematic/color'
// these are static default settings for the marks that are not derived from the computed scheme
const DEFAULT_NOMINAL_ITEMS = 10
const DEFAULT_SEQUENTIAL_ITEMS = 100
/**
* Creates a completed Params block from a ThemeDefinition, making sure missing optional fields are populated.
* @param themeDefinition

Просмотреть файл

@ -6,15 +6,15 @@ export enum ScaleType {
/**
* Maps using a linear transform.
*/
Linear,
Linear = 'linear',
/**
* Maps with a log transform. Useful for wide data ranges, but cannot handle 0 values.
*/
Log,
Log = 'log',
/**
* This creates a set of quantile bins to partition the data into, with an aim for equal size bins to smooth out the scale.
*/
Quantile,
Quantile = 'quantile',
}
/**

Просмотреть файл

@ -19,14 +19,16 @@
"d3-selection": "^1.4.2"
},
"devDependencies": {
"@essex/scripts": "^11.0.1",
"@essex/scripts": "^14.0.0",
"@types/d3-axis": "^1",
"@types/d3-selection": "^1.4.3",
"@types/node": "^14.14.0"
"@types/node": "^14.14.0",
"core-js": "^3.11.1"
},
"peerDependencies": {
"@types/d3-axis": ">= 1",
"@types/d3-selection": ">= 1",
"@types/node": "*"
"@types/node": "*",
"core-js": "^3.11.1"
}
}

Просмотреть файл

@ -198,7 +198,7 @@ function applyBaseOptions(
selection: Selection<Element, any, Element, any>,
options?: SelectionOptions,
): Selection<Element, any, Element, any> {
// TODO: we could enumate all of the core d3-selection functions
// TODO: we could enumerate all of the core d3-selection functions
// html, text, etc., for completeness
return selection
.call(on, options?.on)

Просмотреть файл

@ -22,8 +22,8 @@ Here is an example:
import { load } from '@thematic/core'
import { ThematicProvider } from '@thematic/react'
import { loadFluentTheme } from '@thematic/fluent'
import { initializeIcons } from '@uifabric/icons'
import { ThemeProvider } from '@fluentui/react/lib/Foundation'
import { initializeIcons } from '@fluentui/font-icons-mdl2'
import { ThemeProvider } from '@fluentui/react'
const theme = load()
const fluentTheme = loadFluentTheme(theme)

Просмотреть файл

@ -14,23 +14,28 @@
"start": "essex watch"
},
"dependencies": {
"@essex-js-toolkit/hooks": "^1.1.5",
"@thematic/color": "^0.9.0",
"@thematic/core": "^0.9.0",
"@thematic/react": "^0.9.0"
"@thematic/react": "^0.9.0",
"d3-scale": "^3.3.0"
},
"devDependencies": {
"@essex/scripts": "^11.0.1",
"@fluentui/react": "^7.147.1",
"@essex/scripts": "^14.0.0",
"@fluentui/font-icons-mdl2": "^8.1.0",
"@fluentui/react": "^8.14.2",
"@types/d3-scale": "^3.2.2",
"@types/node": "^14.14.0",
"@types/react": "^16.9.53",
"@uifabric/icons": "^7.5.12",
"core-js": "^3.11.1",
"react": "^16.14.0"
},
"peerDependencies": {
"@fluentui/react": ">= 7",
"@fluentui/react": ">= 8",
"@fluentuit/font-icons-mdl2": ">= 8",
"@types/node": "*",
"@types/react": ">= 16",
"@uifabric/icons": ">= 7",
"core-js": "^3.11.1",
"react": ">= 16.8.0"
}
}

Просмотреть файл

@ -7,9 +7,9 @@ import {
Slider,
IColor,
} from '@fluentui/react'
import * as React from 'react'
import { CSSProperties } from 'react'
import { css2hsluv } from '@thematic/color'
import React, { CSSProperties } from 'react'
import { css2hsluv, Params } from '@thematic/color'
import { Theme } from '@thematic/core'
import { useThematic } from '@thematic/react'
@ -23,21 +23,15 @@ export interface ColorPickerStyles {
slider?: CSSProperties
}
interface ColorPickerProps {
export interface ColorPickerProps {
onChange?: (theme: Theme) => void
/** Optional theme to use, otherwise it will be pulled from context */
theme?: Theme
layout?: ColorPickerLayout
styles?: ColorPickerStyles
}
// TODO: this is a copy of the color/Params interface, but with each item optional
interface PartialParams {
accentHue?: number
accentSaturation?: number
accentLuminance?: number
backgroundHueShift?: number
backgroundLevel?: number
nominalHueStep?: number
}
export type PartialParams = Partial<Params>
/**
* This is a simple ColorPicker that you can show users, allowing them to choose a custom accent color.
@ -48,19 +42,21 @@ interface PartialParams {
*/
export const ColorPicker: React.FC<ColorPickerProps> = ({
onChange,
theme,
layout,
styles,
}) => {
const theme = useThematic()
const contextTheme = useThematic()
const activeTheme = theme || contextTheme
const lyt = layout || ColorPickerLayout.PickerOnly
const updateParams = (params: PartialParams) => {
if (onChange) {
onChange(
theme.clone({
activeTheme.clone({
params: {
...theme.params,
...activeTheme.params,
...params,
},
}),
@ -97,7 +93,7 @@ export const ColorPicker: React.FC<ColorPickerProps> = ({
backgroundLevel,
backgroundHueShift,
nominalHueStep,
} = theme.params
} = activeTheme.params
// TODO: it would be really nice to make these IStyle objects and pass directly to
// child component instead of wrapping them in a div
@ -113,7 +109,7 @@ export const ColorPicker: React.FC<ColorPickerProps> = ({
return (
<div style={{ display: 'flex' }}>
<FluentColorPicker
color={theme.application().accent().hex()}
color={activeTheme.application().accent().hex()}
onChange={handlePickerChange}
alphaType="none"
/>

Просмотреть файл

@ -0,0 +1,5 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
export * from './ColorPicker'

Просмотреть файл

@ -3,10 +3,10 @@
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { IconButton, Label } from '@fluentui/react'
import * as React from 'react'
import { memo, CSSProperties } from 'react'
import { ColorPicker } from './ColorPicker'
import React, { memo, CSSProperties } from 'react'
import { ColorPicker } from '../ColorPicker'
import { Theme } from '@thematic/core'
import { useThematic } from '@thematic/react'
export interface ColorPickerButtonStyles {
label?: CSSProperties
@ -14,6 +14,7 @@ export interface ColorPickerButtonStyles {
export interface ColorPickerButtonProps {
onChange?: (theme: Theme) => void
theme?: Theme
label?: string
styles?: ColorPickerButtonStyles
}
@ -22,7 +23,9 @@ export interface ColorPickerButtonProps {
* This is a dropdown button that displays a thematic ColorPicker.
*/
export const ColorPickerButton: React.FC<ColorPickerButtonProps> = memo(
function ColorPickerButton({ onChange, label, styles }) {
function ColorPickerButton({ onChange, theme, label, styles }) {
const contextTheme = useThematic()
const activeTheme = theme || contextTheme
const labelStyle = {
paddingTop: 0,
paddingBottom: 0,
@ -42,7 +45,9 @@ export const ColorPickerButton: React.FC<ColorPickerButtonProps> = memo(
{
key: 'colorPicker',
// eslint-disable-next-line react/display-name
onRender: () => <ColorPicker onChange={onChange} />,
onRender: () => (
<ColorPicker theme={theme} onChange={onChange} />
),
},
],
}}
@ -50,6 +55,9 @@ export const ColorPickerButton: React.FC<ColorPickerButtonProps> = memo(
root: {
width: 48,
},
icon: {
color: activeTheme.application().accent().hex(),
},
}}
/>
</div>

Просмотреть файл

@ -0,0 +1,5 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
export * from './ColorPickerButton'

Просмотреть файл

@ -0,0 +1,42 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { scaleLinear } from 'd3-scale'
import React, { useMemo } from 'react'
import { ChipsProps } from './types'
export const ColorChips: React.FC<ChipsProps> = ({
scale,
width = 200,
height = 10,
maxItems = 10,
}) => {
const blocks = useMemo(() => {
if (height <= 1) {
return []
}
const r = height / 2 - 1
const x = scaleLinear()
.domain([0, maxItems - 1])
.range([r, width - r])
return scale
.toArray(maxItems)
.map((color, i) => (
<circle
key={`color-chips-${color}-${i}`}
fill={color}
stroke={'none'}
cx={x(i)}
cy={r}
r={r}
height={height}
/>
))
}, [scale, width, height, maxItems])
return (
<svg width={width} height={height}>
{blocks}
</svg>
)
}

Просмотреть файл

@ -0,0 +1,32 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import React, { useMemo } from 'react'
import { ChipsProps } from './types'
export const ContinuousBand: React.FC<ChipsProps> = ({
scale,
width,
height,
}) => {
const blocks = useMemo(() => {
return scale
.toArray(width)
.map((color, i) => (
<rect
key={`continuous-band-${color}-${i}`}
fill={color}
stroke={'none'}
x={i}
width={2}
height={height}
/>
))
}, [scale, width, height])
return (
<svg width={width} height={height}>
{blocks}
</svg>
)
}

Просмотреть файл

@ -0,0 +1,68 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { Dropdown } from '@fluentui/react'
import React, { useCallback, useRef } from 'react'
import { ScaleDropdownItem } from './ScaleDropdownItem'
import {
usePaletteWidth,
usePaletteHeight,
useItemStyle,
useThematicScaleOptions,
} from './hooks/theme'
import { useSafeDimensions } from './hooks/useSafeDimensions'
import { ScaleDropdownProps } from './types'
/**
* Represents a Fluent dropdown of Thematic scale options.
* The scale names can be accompanied by a visual rendering of the scale colors.
* This bascially extends Dropdown, overriding the options and item rendering.
*/
export const ScaleDropdown: React.FC<ScaleDropdownProps> = props => {
const ref = useRef(null)
const { width, height } = useSafeDimensions(ref)
const paletteWidth = usePaletteWidth(width)
const paletteHeight = usePaletteHeight(height, props.label)
const itemStyle = useItemStyle(width)
const options = useThematicScaleOptions()
const handleRenderTitle = useCallback(
([option]) => {
return (
<ScaleDropdownItem
paletteWidth={paletteWidth}
paletteHeight={paletteHeight}
option={option}
/>
)
},
[paletteWidth, paletteHeight],
)
const handleRenderOption = useCallback(
option => {
return (
<ScaleDropdownItem
key={`scale-dropdown-item-${option.key}`}
paletteWidth={paletteWidth}
paletteHeight={paletteHeight}
option={option}
style={itemStyle}
/>
)
},
[paletteWidth, paletteHeight, itemStyle],
)
return (
<div ref={ref}>
<Dropdown
{...props}
options={options}
onRenderTitle={handleRenderTitle}
onRenderOption={handleRenderOption}
/>
</div>
)
}

Просмотреть файл

@ -0,0 +1,39 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import React from 'react'
import { useSafeCollapseDimensions } from './hooks/useSafeDimensions'
import { ScaleDropdownItemProps } from './types'
import { selectColorPalette, useScale } from './util'
export const ScaleDropdownItem: React.FC<ScaleDropdownItemProps> = ({
option,
paletteWidth,
paletteHeight,
style,
}) => {
const { key } = option
const Palette = selectColorPalette(key)
const [width, height] = useSafeCollapseDimensions(paletteWidth, paletteHeight)
const scale = useScale(option.key, paletteWidth)
return (
<div
style={{
...containerStyle,
...style,
}}
>
<div style={{ width: 74 }}>{option.text}</div>
<Palette scale={scale} width={width} height={height} />
</div>
)
}
const containerStyle = {
cursor: 'pointer',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
minWidth: 10,
}

Просмотреть файл

@ -0,0 +1,65 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { IDropdownOption } from '@fluentui/react'
import { useMemo } from 'react'
import { useThematic } from '@thematic/react'
const ITEM_LEFT_PADDING = 8 // defined default in fluent dropdown
const CARET_PADDING = 30 // defined default in fluent dropdown
const TEXT_WIDTH = 80 // TODO: adjust this based on font size/max measured
const LABEL_HEIGHT = 29 // defined default in fluent dropdown
// we may want this to be an optional prop once extracted
// visually we'll keep it lowercase in this app for visual consistency
const TITLE_CASE = false
export function usePaletteWidth(width: number): number {
// subtract space for the caret, left pad, and text
return width - CARET_PADDING - ITEM_LEFT_PADDING - TEXT_WIDTH
}
export function usePaletteHeight(height: number, label?: string): number {
// the measured component dimensions will include the label if present
const root = label ? height - LABEL_HEIGHT : height
return root / 2
}
/**
* This provides unique style overrides for the dropdown items,
* NOT the title. The paddings here are to align the item
* contents visually with the title, so it has a seamless
* appearance whether expanded or not.
*/
export function useItemStyle(width: number): React.CSSProperties {
return useMemo(
() => ({
width: width - CARET_PADDING,
paddingLeft: ITEM_LEFT_PADDING,
paddingRight: CARET_PADDING,
}),
[width],
)
}
// TODO: it would be helpful to provide a filter function so scales can be
// constrained to categorical or sequential when data is string versus numeric
export function useThematicScaleOptions(): IDropdownOption[] {
const theme = useThematic()
return useMemo(() => {
const keys = Object.keys(theme.scales())
return keys.map(key => {
// pretty print the scale names
// (1) uppercase first letter
// (2) use +/- for bold/muted
const text = key
.replace(/^(\w{1})/, c => (TITLE_CASE ? c.toLocaleUpperCase() : c))
.replace('Bold', '+')
.replace('Muted', '-')
return {
key,
text,
}
})
}, [theme])
}

Просмотреть файл

@ -0,0 +1,32 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { Dimensions, useDimensions } from '@essex-js-toolkit/hooks'
import React from 'react'
const DEFAULT_WIDTH = 200
const DEFAULT_HEIGHT = 32
export function useSafeDimensions(
ref: React.RefObject<HTMLDivElement>,
): Dimensions {
const dimensions = useDimensions(ref)
return (
dimensions || {
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT,
}
)
}
/**
* Retrieve a non-zero width/height, because collapsible panels can force invalid color arrays
* @param width
* @param height
*/
export function useSafeCollapseDimensions(
width: number,
height: number,
): [number, number] {
return [width <= 0 ? 1 : width, height <= 0 ? 1 : height]
}

Просмотреть файл

@ -0,0 +1,5 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
export * from './ScaleDropdown'

Просмотреть файл

@ -0,0 +1,25 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { IDropdownProps } from '@fluentui/react'
import {
ContinuousColorScaleFunction,
NominalColorScaleFunction,
} from '@thematic/core'
export type ScaleDropdownProps = Omit<IDropdownProps, 'options'>
export interface ScaleDropdownItemProps {
option: any
paletteWidth: number
paletteHeight: number
style?: React.CSSProperties
}
export interface ChipsProps {
scale: ContinuousColorScaleFunction | NominalColorScaleFunction
width?: number
height?: number
maxItems?: number
}

Просмотреть файл

@ -0,0 +1,62 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { useMemo } from 'react'
import { ColorChips } from './ColorChips'
import { ContinuousBand } from './ContinuousBand'
import { ChipsProps } from './types'
import {
Theme,
NominalColorScaleFunction,
ContinuousColorScaleFunction,
} from '@thematic/core'
import { useThematic } from '@thematic/react'
export function useScale(
key: string,
width: number,
): NominalColorScaleFunction | ContinuousColorScaleFunction {
const theme = useThematic()
const scale = useMemo(() => chooseScale(theme, key, width), [
theme,
key,
width,
])
return scale
}
export function chooseScale(
theme: Theme,
key: string,
width: number,
): NominalColorScaleFunction | ContinuousColorScaleFunction {
const scales = theme.scales()
const domain = [0, width]
switch (key) {
case 'sequential':
return scales.sequential(domain)
case 'sequential2':
return scales.sequential2(domain)
case 'diverging':
return scales.diverging(domain)
case 'diverging2':
return scales.diverging2(domain)
case 'greys':
return scales.greys(domain)
case 'nominalMuted':
return scales.nominalMuted()
case 'nominalBold':
return scales.nominalBold()
case 'nominal':
default:
return scales.nominal()
}
}
export function selectColorPalette(key: string): React.FC<ChipsProps> {
if (key === 'nominal' || key === 'nominalMuted' || key === 'nominalBold') {
return ColorChips
}
return ContinuousBand
}

Просмотреть файл

@ -0,0 +1,82 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import {
ChoiceGroup,
IChoiceGroupOption,
IChoiceGroupStyles,
} from '@fluentui/react'
import React, { useCallback, useMemo } from 'react'
import { ScaleType } from '@thematic/core'
interface ScaleTypeChoiceGroupProps {
selectedType: ScaleType
onChange?: (scaleType: ScaleType) => void
label?: string
suppressQuantile?: boolean
styles?: IChoiceGroupStyles
}
const CHOICE_STYLE = {
flexContainer: { display: 'flex', justifyContent: 'space-around' },
}
/**
* Represents a strongly typed ChoiceGroup for selecting thematic ScaleTypes.
*/
export const ScaleTypeChoiceGroup: React.FC<ScaleTypeChoiceGroupProps> = ({
selectedType,
onChange,
label,
suppressQuantile = false,
styles,
}) => {
const style = useMemo(
() => ({
...CHOICE_STYLE,
...styles,
}),
[styles],
)
const typeOptions = useTypeOptions(suppressQuantile)
const handleTypeChange = useCallback(
(_, option) => {
onChange && onChange(option.key)
},
[onChange],
)
return (
<ChoiceGroup
styles={style}
label={label}
options={typeOptions}
selectedKey={selectedType}
onChange={handleTypeChange}
/>
)
}
function useTypeOptions(suppressQuantile = false): IChoiceGroupOption[] {
return useMemo(() => {
const options = [
{
key: 'linear',
text: 'Linear',
},
{
key: 'log',
text: 'Log',
},
]
if (!suppressQuantile) {
options.push({
key: 'quantile',
text: 'Quantile',
})
}
return options
}, [suppressQuantile])
}

Просмотреть файл

@ -2,10 +2,9 @@
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { ThemeProvider } from '@fluentui/react/lib/Foundation'
import { initializeIcons } from '@uifabric/icons'
import * as React from 'react'
import { useMemo, ReactNode } from 'react'
import { initializeIcons } from '@fluentui/font-icons-mdl2'
import { ThemeProvider } from '@fluentui/react'
import React, { useMemo, ReactNode } from 'react'
import { loadFluentTheme } from './loader'
import { Theme } from '@thematic/core'
import { ThematicProvider } from '@thematic/react'

Просмотреть файл

@ -7,3 +7,5 @@ export * from './loader'
export * from './ColorPicker'
export * from './ColorPickerButton'
export * from './ThematicFluentProvider'
export * from './ScaleDropdown'
export * from './ScaleTypeChoiceGroup'

Просмотреть файл

@ -14,18 +14,20 @@
"start": "essex watch"
},
"dependencies": {
"@essex/scripts": "^11.0.1",
"@essex/scripts": "^14.0.0",
"@thematic/color": "^0.9.0",
"@thematic/core": "^0.9.0"
},
"devDependencies": {
"@types/node": "^14.14.0",
"@types/react": "^16.9.53",
"core-js": "^3.11.1",
"react": "^16.14.0"
},
"peerDependencies": {
"@types/node": "*",
"@types/react": "*",
"core-js": "^3.11.1",
"react": ">= 16.13.1"
}
}

Просмотреть файл

@ -2,7 +2,7 @@
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import * as React from 'react'
import React from 'react'
import { useThematic } from './'
/**

Просмотреть файл

@ -2,7 +2,7 @@
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import * as React from 'react'
import React from 'react'
import { loadById, Theme } from '@thematic/core'
export const defaultTheme = loadById('default') as Theme

Просмотреть файл

@ -2,8 +2,7 @@
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import * as React from 'react'
import { ReactNode, useMemo } from 'react'
import React, { ReactNode, useMemo } from 'react'
import { ThematicContext, defaultTheme } from './ThematicContext'
import { Theme } from '@thematic/core'
@ -19,9 +18,7 @@ export const ThematicProvider: React.FC<ThematicProviderProps> = ({
theme,
children,
}) => {
const t = useMemo(() => {
return theme ? theme : defaultTheme
}, [theme])
const t = useMemo(() => (theme ? theme : defaultTheme), [theme])
return (
<ThematicContext.Provider value={t}>{children}</ThematicContext.Provider>
)

Просмотреть файл

@ -17,11 +17,13 @@
"@thematic/core": "^0.9.0"
},
"devDependencies": {
"@essex/scripts": "^11.0.1",
"@essex/scripts": "^14.0.0",
"@types/node": "^14.14.0",
"core-js": "^3.11.1",
"vega": "^5.17.0"
},
"peerDependencies": {
"core-js": "^3.11.1",
"vega": ">= 5.13.0"
}
}

Просмотреть файл

@ -12,8 +12,9 @@
"clean": "essex clean build"
},
"dependencies": {
"@essex/webpack-config": "^10.0.2",
"@fluentui/react": "^7.147.1",
"@essex/webpack-config": "^14.0.0",
"@fluentui/font-icons-mdl2": "^8.1.0",
"@fluentui/react": "^8.14.2",
"@thematic/color": "^0.9.0",
"@thematic/core": "^0.9.0",
"@thematic/d3": "^0.9.0",
@ -31,10 +32,10 @@
"@types/redux-actions": "^2.6.1",
"@types/redux-logger": "^3.0.8",
"@types/redux-thunk": "^2.1.32",
"core-js": "^3.11.1",
"d3-axis": "^1.0.12",
"d3-scale": "^3.2.3",
"d3-selection": "^1.4.2",
"office-ui-fabric-react": "^7.147.1",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-redux": "^7.2.1",
@ -45,14 +46,14 @@
"vega": "^5.17.0"
},
"devDependencies": {
"@essex/scripts": "^11.0.1",
"@essex/scripts": "^14.0.0",
"raw-loader": "^4.0.2",
"worker-loader": "^2.0.0"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not ie 11",
"not op_mini all"
]
}

Просмотреть файл

@ -2,16 +2,21 @@
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { Dropdown, IDropdownOption, Toggle, SpinButton } from '@fluentui/react'
import { Position } from 'office-ui-fabric-react/lib/utilities/positioning'
import {
Dropdown,
IDropdownOption,
Toggle,
SpinButton,
Position,
} from '@fluentui/react'
import React, { useState, useCallback } from 'react'
import { EnumDropdown } from '../EnumDropdown'
import { ColorBlindnessMode, colorBlindnessInfo } from '@thematic/color'
import { ThemeListing, Theme } from '@thematic/core'
import { ColorPickerButton } from '@thematic/fluent'
// XXX: @fluentui doesn't export everything correctly yet
import './index.css'
export interface ControlPanelProps {
themes: ThemeListing[]
themeInfo: ThemeListing

Просмотреть файл

@ -0,0 +1,121 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import React, { useState, useCallback, useMemo } from 'react'
import { connect } from 'react-redux'
import { themeLoaded } from '../../state/actions'
import { ScaleType, Theme } from '@thematic/core'
import {
ScaleDropdown,
ScaleTypeChoiceGroup,
ColorPicker,
ColorPickerButton,
} from '@thematic/fluent'
import { useThematic } from '@thematic/react'
interface FluentControlsComponentProps {
themeLoaded: (theme: Theme) => void
}
const FluentControlsComponent: React.FC<FluentControlsComponentProps> = ({
themeLoaded,
}) => {
const theme = useThematic()
const [scale, setScale] = useState<string>('<none>')
const handleScaleChange = useCallback((e, option) => setScale(option.key), [])
const [scaleType, setScaleType] = useState<ScaleType>(ScaleType.Linear)
const handleScaleTypeChange = useCallback(type => setScaleType(type), [])
const handlePickerChange = useCallback(t => themeLoaded(t), [themeLoaded])
const labelStyle = useMemo(
() => ({
fontWeight: 'bold' as const,
color: theme.application().accent().hex(),
}),
[theme],
)
const actionStyle = useMemo(
() => ({
fontSize: 12,
fontFamily: 'monospace',
color: theme.application().warning().hex(),
}),
[theme],
)
return (
<div
style={{
fontSize: 14,
overflowY: 'scroll',
}}
>
<p>
The @thematic/fluent package contains a few custom Fluent controls you
can use in your applications to allow Thematic-specific interactions.
</p>
<div style={controlsStyle}>
<div style={controlStyle}>
<p>
<span style={labelStyle}>ScaleDropdown:</span> a Dropdown that
pre-loads Thematic scale options.
</p>
<ScaleDropdown
placeholder={'Choose scale'}
onChange={handleScaleChange}
/>
<p style={actionStyle}> onChange: {scale}</p>
</div>
<div style={controlStyle}>
<p>
<span style={labelStyle}>ScaleTypeChoiceGroup:</span> a ChoiceGroup
that pre-loads Thematic scale types.
</p>
<ScaleTypeChoiceGroup
selectedType={scaleType}
onChange={handleScaleTypeChange}
/>
<p style={actionStyle}> onChange: {scaleType}</p>
</div>
<div style={controlStyle}>
<p>
<span style={labelStyle}>ColorPicker:</span> a ColorPicker that
emits Thematic parameters.
</p>
<ColorPicker theme={theme} onChange={handlePickerChange} />
<p style={actionStyle}>
{' '}
onChange: {theme.application().accent().hex()}
</p>
</div>
<div style={controlStyle}>
<p>
<span style={labelStyle}>ColorPickerButton:</span> a DropdownButton
that hosts a Thematic ColorPicker.
</p>
<ColorPickerButton theme={theme} onChange={handlePickerChange} />
<p style={actionStyle}>
{' '}
onChange: {theme.application().accent().hex()}
</p>
</div>
</div>
</div>
)
}
export const FluentControls = connect(null, {
themeLoaded,
})(FluentControlsComponent)
const controlsStyle = {
display: 'flex' as const,
flexDirection: 'row' as const,
flexWrap: 'wrap' as const,
justifyContent: 'space-around' as const,
}
const controlStyle = {
width: 320,
padding: 8,
margin: 8,
}

Просмотреть файл

@ -22,7 +22,7 @@ export const ThemeEditor: React.FC<ThemeEditorProps> = ({ scaleItemCount }) => {
return (
<div className="editor-wrapper">
<Pivot>
<PivotItem className="tab" headerText="Cooler Picker">
<PivotItem className="tab" headerText="Color Picker">
<CoolerPicker />
</PivotItem>
<PivotItem className="tab" headerText="Marks">

Просмотреть файл

@ -2,8 +2,10 @@
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { Pivot, PivotItem } from '@fluentui/react'
import React from 'react'
import { Graph } from '../../interfaces'
import { FluentControls } from '../FluentControls'
import { D3Chart } from '../charts/D3'
import { VegaChart, charts } from '../charts/Vega'
import { D3Graph } from '../graphs/D3'
@ -55,21 +57,28 @@ export const ThemeViewer: React.FC<ThemeViewerProps> = ({
return (
<div className="theme-wrapper">
<div className="example-grid">
{graphs.map((graph, i) => (
<div className="example-grid-item" key={`example-graph-${i}`}>
{graph}
<Pivot>
<PivotItem headerText="Thematic controls">
<FluentControls />
</PivotItem>
<PivotItem headerText="Example visualizations">
<div className="example-grid">
{graphs.map((graph, i) => (
<div className="example-grid-item" key={`example-graph-${i}`}>
{graph}
</div>
))}
<div className="example-grid-item" key={`example-rar-chart`}>
<D3Chart {...common} />
</div>
{charts.map(chart => (
<div className="example-grid-item" key={`example-chart-${chart}`}>
<VegaChart type={chart} {...common} />
</div>
))}
</div>
))}
<div className="example-grid-item" key={`example-rar-chart`}>
<D3Chart {...common} />
</div>
{charts.map(chart => (
<div className="example-grid-item" key={`example-chart-${chart}`}>
<VegaChart type={chart} {...common} />
</div>
))}
</div>
</PivotItem>
</Pivot>
</div>
)
}

Просмотреть файл

@ -7,7 +7,7 @@
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-evenly;
justify-content: space-around;
}
.example-grid-item {
margin: 20px;

Просмотреть файл

@ -1,10 +0,0 @@
{
"compilerOptions": {
"target": "ES5",
"jsx": "react",
"types": ["node", "jest"],
"allowJs": true,
"resolveJsonModule": true,
"esModuleInterop": true
}
}

12590
yarn.lock

Разница между файлами не показана из-за своего большого размера Загрузить разницу