Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Patch settings bugs #208

Merged
merged 11 commits into from
Apr 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .yarn/versions/69cc8052.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
releases:
"@essex/components": minor
essex-toolkit-stories: patch
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

## SettingConfig.control property

Explicitly set the control to display instead of the default guess.

<b>Signature:</b>

```typescript
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

## SettingConfig.defaultValue property

Default value of the setting.

<b>Signature:</b>

```typescript
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

## SettingConfig.label property

Custom label to use instead of deriving from the setting key.

<b>Signature:</b>

```typescript
Expand Down
9 changes: 5 additions & 4 deletions packages/components/docs/markdown/components.settingconfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ export interface SettingConfig

| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [control?](./components.settingconfig.control.md) | | [ControlType](./components.controltype.md) | <i>(Optional)</i> |
| [defaultValue?](./components.settingconfig.defaultvalue.md) | | any | <i>(Optional)</i> |
| [label?](./components.settingconfig.label.md) | | string | <i>(Optional)</i> |
| [params?](./components.settingconfig.params.md) | | [ControlParams](./components.controlparams.md) | <i>(Optional)</i> |
| [control?](./components.settingconfig.control.md) | | [ControlType](./components.controltype.md) | <i>(Optional)</i> Explicitly set the control to display instead of the default guess. |
| [defaultValue?](./components.settingconfig.defaultvalue.md) | | any | <i>(Optional)</i> Default value of the setting. |
| [label?](./components.settingconfig.label.md) | | string | <i>(Optional)</i> Custom label to use instead of deriving from the setting key. |
| [params?](./components.settingconfig.params.md) | | [ControlParams](./components.controlparams.md) | <i>(Optional)</i> Deeper params for controls that may need extra config (e.g., numeric bounds). |
| [type?](./components.settingconfig.type.md) | | DataType | <i>(Optional)</i> Explicitly set the data type, overriding typeof. Useful when the default value is undefined so type cannot be inferred. |

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

## SettingConfig.params property

Deeper params for controls that may need extra config (e.g., numeric bounds).

<b>Signature:</b>

```typescript
Expand Down
13 changes: 13 additions & 0 deletions packages/components/docs/markdown/components.settingconfig.type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@essex/components](./components.md) &gt; [SettingConfig](./components.settingconfig.md) &gt; [type](./components.settingconfig.type.md)

## SettingConfig.type property

Explicitly set the data type, overriding typeof. Useful when the default value is undefined so type cannot be inferred.

<b>Signature:</b>

```typescript
type?: DataType;
```
36 changes: 32 additions & 4 deletions packages/components/docs/report/components.api.json
Original file line number Diff line number Diff line change
Expand Up @@ -4263,7 +4263,7 @@
{
"kind": "PropertySignature",
"canonicalReference": "@essex/components!SettingConfig#control:member",
"docComment": "",
"docComment": "/**\n * Explicitly set the control to display instead of the default guess.\n */\n",
"excerptTokens": [
{
"kind": "Content",
Expand Down Expand Up @@ -4291,7 +4291,7 @@
{
"kind": "PropertySignature",
"canonicalReference": "@essex/components!SettingConfig#defaultValue:member",
"docComment": "",
"docComment": "/**\n * Default value of the setting.\n */\n",
"excerptTokens": [
{
"kind": "Content",
Expand All @@ -4318,7 +4318,7 @@
{
"kind": "PropertySignature",
"canonicalReference": "@essex/components!SettingConfig#label:member",
"docComment": "",
"docComment": "/**\n * Custom label to use instead of deriving from the setting key.\n */\n",
"excerptTokens": [
{
"kind": "Content",
Expand All @@ -4345,7 +4345,7 @@
{
"kind": "PropertySignature",
"canonicalReference": "@essex/components!SettingConfig#params:member",
"docComment": "",
"docComment": "/**\n * Deeper params for controls that may need extra config (e.g., numeric bounds).\n */\n",
"excerptTokens": [
{
"kind": "Content",
Expand All @@ -4369,6 +4369,34 @@
"startIndex": 1,
"endIndex": 2
}
},
{
"kind": "PropertySignature",
"canonicalReference": "@essex/components!SettingConfig#type:member",
"docComment": "/**\n * Explicitly set the data type, overriding typeof. Useful when the default value is undefined so type cannot be inferred.\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "type?: "
},
{
"kind": "Reference",
"text": "DataType",
"canonicalReference": "@essex/components!DataType:enum"
},
{
"kind": "Content",
"text": ";"
}
],
"isReadonly": false,
"isOptional": true,
"releaseTag": "Public",
"name": "type",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
}
}
],
"extendsTokenRanges": []
Expand Down
6 changes: 2 additions & 4 deletions packages/components/docs/report/components.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -437,14 +437,12 @@ export interface SearchBoxProps {
//
// @public
export interface SettingConfig {
// (undocumented)
control?: ControlType;
// (undocumented)
defaultValue?: any;
// (undocumented)
label?: string;
// (undocumented)
params?: ControlParams;
// Warning: (ae-forgotten-export) The symbol "DataType" needs to be exported by the entry point index.d.ts
type?: DataType;
}

// Warning: (ae-missing-release-tag) "Settings" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
Expand Down
108 changes: 108 additions & 0 deletions packages/components/src/Settings/ArrayControl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import {
Checkbox,
type IDropdownOption,
Label,
TextField,
} from '@fluentui/react'
import { useCallback } from 'react'

import { MultiDropdown } from '../MultiDropdown/MultiDropdown.js'
import { checkboxesStyle } from './Settings.styles.js'
import type { ControlProps } from './Settings.types.js'
import { ControlType } from './Settings.types.js'

/**
* ArrayControl creates either thematic themed Multi-select Dropdown or a list of Checkboxes
* as a Fluent component based on config options
*/
export const ArrayControl = ({
config,
onChange,
}: ControlProps): JSX.Element => {
const { key, value, type, label, control, params } = config
const handleTextChange = useCallback(
(_evt: unknown, text?: string | undefined) =>
onChange?.(key, text?.split(',')),
[key, onChange],
)
const handleMultiChange = useCallback(
(val: any, checked?: boolean) => {
if (onChange) {
if (value) {
if (checked) {
onChange(key, [...value, val])
} else {
onChange(
key,
value.filter((v: any) => v !== val),
)
}
} else {
onChange(key, [val])
}
}
},
[key, value, onChange],
)
const handleAllChange = useCallback(
(_evt: unknown, options?: IDropdownOption[]) =>
onChange?.(key, options?.map((o) => o.key) || []),
[key, onChange],
)
switch (control) {
case ControlType.Textbox:
return (
<TextField
key={`textfield-${key}`}
label={label}
value={value}
onChange={handleTextChange}
/>
)
case ControlType.Dropdown:
if (!params?.options) {
throw new Error('Dropdown control type requires list of options')
}
return (
<MultiDropdown
key={`dropdown-${key}`}
label={label}
multiSelect
selectedKeys={value}
options={params.options.map((opt) => ({
key: opt,
text: opt,
}))}
onChange={(_e, opt) => handleMultiChange(opt?.key, opt?.selected)}
onChangeAll={handleAllChange}
/>
)
case ControlType.Checkbox:
if (!params?.options) {
throw new Error('Checkbox list control type requires list of options')
}
return (
<div>
<Label>{label}</Label>
<div style={checkboxesStyle}>
{params.options.map((opt) => (
<Checkbox
key={`checkbox-${key}-${opt}`}
label={opt}
checked={!!value?.find((v: any) => v === opt)}
onChange={(_e, chk) => handleMultiChange(opt, chk)}
/>
))}
</div>
</div>
)
default:
throw new Error(
`Unsupported control type ${JSON.stringify(control)} for ${type}`,
)
}
}
4 changes: 2 additions & 2 deletions packages/components/src/Settings/NumberControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const NumberControl = ({
<NumberSpinButton
key={`spinner-${key}`}
label={label || ''}
value={value}
value={value === undefined ? '' : value}
labelPosition={Position.top}
incrementButtonAriaLabel={`increment ${JSON.stringify(label)}`}
decrementButtonAriaLabel={`decrement ${JSON.stringify(label)}`}
Expand All @@ -42,7 +42,7 @@ export const NumberControl = ({
<Slider
key={`slider-${key}`}
label={label}
value={value}
value={value === undefined ? '' : value}
{...params}
onChange={handleChange}
/>
Expand Down
51 changes: 42 additions & 9 deletions packages/components/src/Settings/Settings.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import type {
SortedSettings,
SortedSettingsGrouped,
} from './Settings.types.js'
import { ControlType } from './Settings.types.js'
import { ControlType, DataType } from './Settings.types.js'
import { isArray } from 'lodash-es'

/**
* Sorts through settings to determine control type, etc. for each one.
Expand Down Expand Up @@ -51,22 +52,30 @@ const keyToLabel = (str: string): string => {
}

const selectDefaultControl = (
type: string,
type: DataType,
params?: ControlParams,
): string | undefined => {
switch (type) {
case 'string':
case DataType.Number:
return ControlType.Spinner
case DataType.Boolean:
return ControlType.Toggle
case DataType.Array:
if (params?.options) {
if (params.options.length < 4) {
return ControlType.Checkbox
}
return ControlType.Dropdown
}
return ControlType.Textbox
default:
if (params?.options) {
if (params.options.length < 4) {
return ControlType.Radio
}
return ControlType.Dropdown
}
return ControlType.Textbox
case 'number':
return ControlType.Spinner
case 'boolean':
return ControlType.Toggle
}
}

Expand Down Expand Up @@ -96,8 +105,8 @@ const parseSettings = (
const parsed = Object.entries(combined).reduce((acc: any, cur) => {
const [key, conf] = cur
const setting = settings[key]
const value = setting || conf.defaultValue
const type = typeof value
const value = setting !== undefined ? setting : conf.defaultValue
const type = guessType(value, conf)
const entry = {
key,
value: value,
Expand All @@ -111,6 +120,30 @@ const parseSettings = (
return parsed
}

const guessType = (value: any, conf: SettingConfig): DataType => {
const { type, control } = conf
if (type !== undefined) {
return type
}
if (isArray(value)) {
return DataType.Array
}
if (value !== undefined) {
return typeof value as DataType
}
// if we have no value but the user has indicated a control type, key on that as a fallback
switch (control) {
case ControlType.Checkbox:
case ControlType.Toggle:
return DataType.Boolean
case ControlType.Spinner:
case ControlType.Slider:
return DataType.Number
default:
return DataType.String
}
}

const sortIntoGroups = (
parsed: ParsedSettingConfig[],
groups: SettingsGroup[] = [],
Expand Down
Loading