mirror of
https://github.com/mattermost/focalboard.git
synced 2025-03-06 15:36:17 +02:00
Board calculations (#1464)
* Added menu options to choose calculation * Made calculation option component generic for use in kanban and table * Added property type based calculation option menu * WIP * Prepared submenu * Populated submenu * WIP * WIP * Base implementation complete * Done * minor cleanup * Updating UI for board calculations # Conflicts: # webapp/src/components/kanban/calculation/calculation.tsx * Updating UI for board * Highlighted currently selected option * Fixed existsing tests * Fixed existsing tests * Added tests * Added tests * Fixed some plugin CSS issues * Fixed a unintentional snapshot update * Fixed a test * Fixed a test * Fixed a test * Fixed dashboard tests * Fixed some review comments * Updated snapshots for change in Button classname * Fixed test after syncing with main Co-authored-by: Asaad Mahmood <asaadmahmood@users.noreply.github.com>
This commit is contained in:
parent
0434e7d6b6
commit
c4ee743a10
@ -29,6 +29,7 @@ type BoardFields = {
|
||||
showDescription?: boolean
|
||||
isTemplate?: boolean
|
||||
cardProperties: IPropertyTemplate[]
|
||||
columnCalculations: Record<string, string>
|
||||
}
|
||||
|
||||
type Board = Block & {
|
||||
|
@ -9,6 +9,11 @@ import {FilterGroup, createFilterGroup} from './filterGroup'
|
||||
type IViewType = 'board' | 'table' | 'gallery' // | 'calendar' | 'list'
|
||||
type ISortOption = { propertyId: '__title' | string, reversed: boolean }
|
||||
|
||||
type KanbanCalculationFields = {
|
||||
calculation: string
|
||||
propertyId: string
|
||||
}
|
||||
|
||||
type BoardViewFields = {
|
||||
viewType: IViewType
|
||||
groupById?: string
|
||||
@ -21,6 +26,7 @@ type BoardViewFields = {
|
||||
cardOrder: string[]
|
||||
columnWidths: Record<string, number>
|
||||
columnCalculations: Record<string, string>
|
||||
kanbanCalculations: Record<string, KanbanCalculationFields>
|
||||
defaultTemplateId: string
|
||||
}
|
||||
|
||||
@ -45,6 +51,7 @@ function createBoardView(block?: Block): BoardView {
|
||||
cardOrder: block?.fields.cardOrder?.slice() || [],
|
||||
columnWidths: {...(block?.fields.columnWidths || {})},
|
||||
columnCalculations: {...(block?.fields.columnCalculations) || {}},
|
||||
kanbanCalculations: {...(block?.fields.kanbanCalculations) || {}},
|
||||
defaultTemplateId: block?.fields.defaultTemplateId || '',
|
||||
},
|
||||
}
|
||||
@ -57,4 +64,4 @@ function sortBoardViewsAlphabetically(views: BoardView[]): BoardView[] {
|
||||
}).sort((v1, v2) => v1.title.localeCompare(v2.title)).map((v) => v.view)
|
||||
}
|
||||
|
||||
export {BoardView, IViewType, ISortOption, sortBoardViewsAlphabetically, createBoardView}
|
||||
export {BoardView, IViewType, ISortOption, sortBoardViewsAlphabetically, createBoardView, KanbanCalculationFields}
|
||||
|
@ -45,7 +45,7 @@ exports[`components/propertyValueElement should match snapshot, date, array valu
|
||||
class="DateRange empty octo-propertyvalue"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
|
@ -95,13 +95,13 @@ exports[`components/calculations/Calculation should match snapshot - option chan
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
/>
|
||||
<div
|
||||
class=" css-1s59geg-Control"
|
||||
class="CalculationOptions__control css-1s59geg-Control"
|
||||
>
|
||||
<div
|
||||
class=" css-1mxrbau-ValueContainer"
|
||||
class="CalculationOptions__value-container CalculationOptions__value-container--has-value css-1mxrbau-ValueContainer"
|
||||
>
|
||||
<div
|
||||
class=" css-1brck82-singleValue"
|
||||
class="CalculationOptions__single-value css-1brck82-singleValue"
|
||||
>
|
||||
Calculate
|
||||
</div>
|
||||
@ -115,11 +115,11 @@ exports[`components/calculations/Calculation should match snapshot - option chan
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class=" css-1hb7zxy-IndicatorsContainer"
|
||||
class="CalculationOptions__indicators css-1hb7zxy-IndicatorsContainer"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class=" css-tpaeio-indicatorContainer"
|
||||
class="CalculationOptions__indicator CalculationOptions__clear-indicator css-tpaeio-indicatorContainer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
@ -135,11 +135,11 @@ exports[`components/calculations/Calculation should match snapshot - option chan
|
||||
</svg>
|
||||
</div>
|
||||
<span
|
||||
class=" css-43ykx9-indicatorSeparator"
|
||||
class="CalculationOptions__indicator-separator css-43ykx9-indicatorSeparator"
|
||||
/>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class=" css-wpsttr-indicatorContainer"
|
||||
class="CalculationOptions__indicator CalculationOptions__dropdown-indicator css-wpsttr-indicatorContainer"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-chevron-up ChevronUpIcon"
|
||||
|
@ -21,13 +21,13 @@ exports[`components/calculations/Options should match snapshot 1`] = `
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
class=" css-1s59geg-Control"
|
||||
class="CalculationOptions__control CalculationOptions__control--is-focused css-1s59geg-Control"
|
||||
>
|
||||
<div
|
||||
class=" css-1mxrbau-ValueContainer"
|
||||
class="CalculationOptions__value-container CalculationOptions__value-container--has-value css-1mxrbau-ValueContainer"
|
||||
>
|
||||
<div
|
||||
class=" css-1brck82-singleValue"
|
||||
class="CalculationOptions__single-value css-1brck82-singleValue"
|
||||
>
|
||||
Calculate
|
||||
</div>
|
||||
@ -41,11 +41,11 @@ exports[`components/calculations/Options should match snapshot 1`] = `
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class=" css-1hb7zxy-IndicatorsContainer"
|
||||
class="CalculationOptions__indicators css-1hb7zxy-IndicatorsContainer"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class=" css-13eygzs-indicatorContainer"
|
||||
class="CalculationOptions__indicator CalculationOptions__clear-indicator css-13eygzs-indicatorContainer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
@ -61,11 +61,11 @@ exports[`components/calculations/Options should match snapshot 1`] = `
|
||||
</svg>
|
||||
</div>
|
||||
<span
|
||||
class=" css-43ykx9-indicatorSeparator"
|
||||
class="CalculationOptions__indicator-separator css-43ykx9-indicatorSeparator"
|
||||
/>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class=" css-1f9iddu-indicatorContainer"
|
||||
class="CalculationOptions__indicator CalculationOptions__dropdown-indicator css-1f9iddu-indicatorContainer"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-chevron-up ChevronUpIcon"
|
||||
@ -99,17 +99,17 @@ exports[`components/calculations/Options should match snapshot menu open 1`] = `
|
||||
<span
|
||||
id="aria-context"
|
||||
>
|
||||
10 results available. Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.
|
||||
2 results available. Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
class=" css-1s59geg-Control"
|
||||
class="CalculationOptions__control CalculationOptions__control--is-focused CalculationOptions__control--menu-is-open css-1s59geg-Control"
|
||||
>
|
||||
<div
|
||||
class=" css-1mxrbau-ValueContainer"
|
||||
class="CalculationOptions__value-container CalculationOptions__value-container--has-value css-1mxrbau-ValueContainer"
|
||||
>
|
||||
<div
|
||||
class=" css-1brck82-singleValue"
|
||||
class="CalculationOptions__single-value css-1brck82-singleValue"
|
||||
>
|
||||
Calculate
|
||||
</div>
|
||||
@ -123,11 +123,11 @@ exports[`components/calculations/Options should match snapshot menu open 1`] = `
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class=" css-1hb7zxy-IndicatorsContainer"
|
||||
class="CalculationOptions__indicators css-1hb7zxy-IndicatorsContainer"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class=" css-13eygzs-indicatorContainer"
|
||||
class="CalculationOptions__indicator CalculationOptions__clear-indicator css-13eygzs-indicatorContainer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
@ -143,11 +143,11 @@ exports[`components/calculations/Options should match snapshot menu open 1`] = `
|
||||
</svg>
|
||||
</div>
|
||||
<span
|
||||
class=" css-43ykx9-indicatorSeparator"
|
||||
class="CalculationOptions__indicator-separator css-43ykx9-indicatorSeparator"
|
||||
/>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class=" css-1f9iddu-indicatorContainer"
|
||||
class="CalculationOptions__indicator CalculationOptions__dropdown-indicator css-1f9iddu-indicatorContainer"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-chevron-up ChevronUpIcon"
|
||||
@ -156,81 +156,25 @@ exports[`components/calculations/Options should match snapshot menu open 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class=" css-1kslk4z-menu"
|
||||
class="CalculationOptions__menu css-1rsmi4x-menu"
|
||||
>
|
||||
<div
|
||||
class=" css-g29tl0-MenuList"
|
||||
class="CalculationOptions__menu-list css-g29tl0-MenuList"
|
||||
>
|
||||
<div
|
||||
class=" css-1cnkr6z-option"
|
||||
class="CalculationOptions__option css-14xsrqy-option"
|
||||
id="react-select-3-option-0"
|
||||
tabindex="-1"
|
||||
>
|
||||
None
|
||||
</div>
|
||||
<div
|
||||
class=" css-14xsrqy-option"
|
||||
id="react-select-3-option-1"
|
||||
tabindex="-1"
|
||||
>
|
||||
Count
|
||||
</div>
|
||||
<div
|
||||
class=" css-14xsrqy-option"
|
||||
id="react-select-3-option-2"
|
||||
tabindex="-1"
|
||||
>
|
||||
Count Value
|
||||
</div>
|
||||
<div
|
||||
class=" css-14xsrqy-option"
|
||||
id="react-select-3-option-3"
|
||||
tabindex="-1"
|
||||
>
|
||||
Count Unique Values
|
||||
</div>
|
||||
<div
|
||||
class=" css-14xsrqy-option"
|
||||
id="react-select-3-option-4"
|
||||
tabindex="-1"
|
||||
>
|
||||
Sum
|
||||
</div>
|
||||
<div
|
||||
class=" css-14xsrqy-option"
|
||||
id="react-select-3-option-5"
|
||||
tabindex="-1"
|
||||
>
|
||||
Average
|
||||
</div>
|
||||
<div
|
||||
class=" css-14xsrqy-option"
|
||||
id="react-select-3-option-6"
|
||||
tabindex="-1"
|
||||
>
|
||||
Median
|
||||
</div>
|
||||
<div
|
||||
class=" css-14xsrqy-option"
|
||||
id="react-select-3-option-7"
|
||||
tabindex="-1"
|
||||
>
|
||||
Min
|
||||
</div>
|
||||
<div
|
||||
class=" css-14xsrqy-option"
|
||||
id="react-select-3-option-8"
|
||||
class="CalculationOptions__option css-14xsrqy-option"
|
||||
id="react-select-3-option-1"
|
||||
tabindex="-1"
|
||||
>
|
||||
Max
|
||||
</div>
|
||||
<div
|
||||
class=" css-14xsrqy-option"
|
||||
id="react-select-3-option-9"
|
||||
tabindex="-1"
|
||||
>
|
||||
Range
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
|
@ -9,6 +9,8 @@ import userEvent from '@testing-library/user-event'
|
||||
import {TestBlockFactory} from '../../test/testBlockFactory'
|
||||
import {wrapIntl} from '../../testUtils'
|
||||
|
||||
import {TableCalculationOptions} from '../table/calculation/tableCalculationOptions'
|
||||
|
||||
import Calculation from './calculation'
|
||||
|
||||
describe('components/calculations/Calculation', () => {
|
||||
@ -42,6 +44,7 @@ describe('components/calculations/Calculation', () => {
|
||||
type: 'text',
|
||||
options: [],
|
||||
}}
|
||||
optionsComponent={TableCalculationOptions}
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -67,6 +70,7 @@ describe('components/calculations/Calculation', () => {
|
||||
type: 'text',
|
||||
options: [],
|
||||
}}
|
||||
optionsComponent={TableCalculationOptions}
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -92,6 +96,7 @@ describe('components/calculations/Calculation', () => {
|
||||
type: 'text',
|
||||
options: [],
|
||||
}}
|
||||
optionsComponent={TableCalculationOptions}
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -117,6 +122,7 @@ describe('components/calculations/Calculation', () => {
|
||||
type: 'text',
|
||||
options: [],
|
||||
}}
|
||||
optionsComponent={TableCalculationOptions}
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -146,6 +152,7 @@ describe('components/calculations/Calculation', () => {
|
||||
type: 'text',
|
||||
options: [],
|
||||
}}
|
||||
optionsComponent={TableCalculationOptions}
|
||||
/>,
|
||||
)
|
||||
|
||||
|
@ -9,7 +9,7 @@ import {IPropertyTemplate} from '../../blocks/board'
|
||||
|
||||
import ChevronUp from '../../widgets/icons/chevronUp'
|
||||
|
||||
import {CalculationOptions, Options} from './options'
|
||||
import {CommonCalculationOptionProps, Options} from './options'
|
||||
|
||||
import Calculations from './calculations'
|
||||
import './calculation.scss'
|
||||
@ -25,6 +25,7 @@ type Props = {
|
||||
cards: readonly Card[]
|
||||
property: IPropertyTemplate
|
||||
hovered: boolean
|
||||
optionsComponent: React.ComponentType<CommonCalculationOptionProps>
|
||||
}
|
||||
|
||||
const Calculation = (props: Props): JSX.Element => {
|
||||
@ -32,6 +33,16 @@ const Calculation = (props: Props): JSX.Element => {
|
||||
const valueOption = Options[value]
|
||||
const intl = useIntl()
|
||||
|
||||
const option = (
|
||||
<props.optionsComponent
|
||||
value={value}
|
||||
menuOpen={props.menuOpen}
|
||||
onClose={props.onMenuClose}
|
||||
onChange={props.onChange}
|
||||
property={props.property}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
|
||||
// tabindex is needed to make onBlur work on div.
|
||||
@ -46,14 +57,8 @@ const Calculation = (props: Props): JSX.Element => {
|
||||
>
|
||||
{
|
||||
props.menuOpen && (
|
||||
<div >
|
||||
<CalculationOptions
|
||||
value={value}
|
||||
menuOpen={props.menuOpen}
|
||||
onClose={props.onMenuClose}
|
||||
onChange={props.onChange}
|
||||
property={props.property}
|
||||
/>
|
||||
<div>
|
||||
{option}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -19,6 +19,12 @@ describe('components/calculations/Options', () => {
|
||||
value={'none'}
|
||||
onChange={() => {}}
|
||||
property={property}
|
||||
menuOpen={false}
|
||||
options={[{
|
||||
label: 'Count',
|
||||
value: 'count',
|
||||
displayName: 'Count',
|
||||
}]}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -37,6 +43,18 @@ describe('components/calculations/Options', () => {
|
||||
menuOpen={true}
|
||||
onChange={() => {}}
|
||||
property={property}
|
||||
options={[
|
||||
{
|
||||
label: 'Count',
|
||||
value: 'count',
|
||||
displayName: 'Count',
|
||||
},
|
||||
{
|
||||
label: 'Max',
|
||||
value: 'max',
|
||||
displayName: 'Max',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -16,7 +16,7 @@ type Option = {
|
||||
displayName: string
|
||||
}
|
||||
|
||||
const Options:Record<string, Option> = {
|
||||
export const Options:Record<string, Option> = {
|
||||
none: {value: 'none', label: 'None', displayName: 'Calculate'},
|
||||
count: {value: 'count', label: 'Count', displayName: 'Count'},
|
||||
countValue: {value: 'countValue', label: 'Count Value', displayName: 'Values'},
|
||||
@ -36,7 +36,7 @@ const Options:Record<string, Option> = {
|
||||
dateRange: {value: 'dateRange', label: 'Range', displayName: 'Range'},
|
||||
}
|
||||
|
||||
const optionsByType: Map<string, Option[]> = new Map([
|
||||
export const optionsByType: Map<string, Option[]> = new Map([
|
||||
['common', [Options.none, Options.count, Options.countValue, Options.countUniqueValue]],
|
||||
['checkbox', [Options.countChecked, Options.countUnchecked, Options.percentChecked, Options.percentUnchecked]],
|
||||
['number', [Options.sum, Options.average, Options.median, Options.min, Options.max, Options.range]],
|
||||
@ -45,6 +45,22 @@ const optionsByType: Map<string, Option[]> = new Map([
|
||||
['updatedTime', [Options.earliest, Options.latest, Options.dateRange]],
|
||||
])
|
||||
|
||||
export const typesByOptions: Map<string, string[]> = generateTypesByOption()
|
||||
|
||||
function generateTypesByOption(): Map<string, string[]> {
|
||||
const mapping = new Map<string, string[]>()
|
||||
|
||||
optionsByType.forEach((options, type) => {
|
||||
options.forEach((option) => {
|
||||
const types = mapping.get(option.value) || []
|
||||
types.push(type)
|
||||
mapping.set(option.value, types)
|
||||
})
|
||||
})
|
||||
|
||||
return mapping
|
||||
}
|
||||
|
||||
const baseStyles = getSelectBaseStyle()
|
||||
|
||||
const styles = {
|
||||
@ -65,7 +81,7 @@ const styles = {
|
||||
minWidth: '100%',
|
||||
width: 'max-content',
|
||||
background: 'rgb(var(--center-channel-bg-rgb))',
|
||||
right: '0',
|
||||
left: '0',
|
||||
marginBottom: '0',
|
||||
}),
|
||||
singleValue: (provided: CSSObject): CSSObject => ({
|
||||
@ -90,20 +106,22 @@ const DropdownIndicator = (props: IndicatorProps<Option, false>) => {
|
||||
)
|
||||
}
|
||||
|
||||
type Props = {
|
||||
// Calculation option props shared by all implementations of calculation options
|
||||
type CommonCalculationOptionProps = {
|
||||
value: string,
|
||||
menuOpen?: boolean
|
||||
menuOpen: boolean
|
||||
onClose?: () => void
|
||||
onChange: (value: string) => void
|
||||
property: IPropertyTemplate
|
||||
components?: {[key:string]: (props: any) => JSX.Element}
|
||||
onChange: (data: any) => void
|
||||
property?: IPropertyTemplate
|
||||
}
|
||||
|
||||
const CalculationOptions = (props: Props): JSX.Element => {
|
||||
const options = [...optionsByType.get('common')!]
|
||||
if (optionsByType.get(props.property.type)) {
|
||||
options.push(...optionsByType.get(props.property.type)!)
|
||||
}
|
||||
// Props used by the base calculation option component
|
||||
type BaseCalculationOptionProps = CommonCalculationOptionProps & {
|
||||
options: Option[]
|
||||
}
|
||||
|
||||
const CalculationOptions = (props: BaseCalculationOptionProps): JSX.Element => {
|
||||
return (
|
||||
<Select
|
||||
styles={styles}
|
||||
@ -112,10 +130,11 @@ const CalculationOptions = (props: Props): JSX.Element => {
|
||||
isClearable={true}
|
||||
name={'calculation_options'}
|
||||
className={'CalculationOptions'}
|
||||
options={options}
|
||||
classNamePrefix={'CalculationOptions'}
|
||||
options={props.options}
|
||||
menuPlacement={'auto'}
|
||||
isSearchable={false}
|
||||
components={{DropdownIndicator}}
|
||||
components={{DropdownIndicator, ...(props.components || {})}}
|
||||
defaultMenuIsOpen={props.menuOpen}
|
||||
autoFocus={true}
|
||||
formatOptionLabel={(option: Option, meta) => {
|
||||
@ -137,6 +156,6 @@ const CalculationOptions = (props: Props): JSX.Element => {
|
||||
|
||||
export {
|
||||
CalculationOptions,
|
||||
Options,
|
||||
Option,
|
||||
CommonCalculationOptionProps,
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ exports[`components/cardDetail/cardDetailContentsMenu return cardDetailContentsM
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -162,7 +162,7 @@ exports[`components/cardDetail/cardDetailContentsMenu return cardDetailContentsM
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -313,7 +313,7 @@ exports[`components/cardDetail/cardDetailContentsMenu return cardDetailContentsM
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
|
@ -17,7 +17,7 @@ exports[`components/cardDetail/CardDetailProperties should match snapshot 1`] =
|
||||
class="octo-propertyname"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -46,7 +46,7 @@ exports[`components/cardDetail/CardDetailProperties should match snapshot 1`] =
|
||||
class="octo-propertyname add-property"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
|
@ -0,0 +1,167 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/kanban/calculation/KanbanCalculation base case 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="KanbanCalculation"
|
||||
>
|
||||
<button
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
3
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/kanban/calculation/KanbanCalculation calculations menu open 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="KanbanCalculation"
|
||||
>
|
||||
<button
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
3
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
class="CalculationOptions css-2b097c-container"
|
||||
>
|
||||
<span
|
||||
aria-atomic="false"
|
||||
aria-live="polite"
|
||||
aria-relevant="additions text"
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
>
|
||||
<span
|
||||
id="aria-selection"
|
||||
/>
|
||||
<span
|
||||
id="aria-context"
|
||||
>
|
||||
3 results available. Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
class="CalculationOptions__control CalculationOptions__control--is-focused CalculationOptions__control--menu-is-open css-1s59geg-Control"
|
||||
>
|
||||
<div
|
||||
class="CalculationOptions__value-container CalculationOptions__value-container--has-value css-1mxrbau-ValueContainer"
|
||||
>
|
||||
<div
|
||||
class="CalculationOptions__single-value css-1brck82-singleValue"
|
||||
>
|
||||
Count
|
||||
</div>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
class="css-wmatm6-dummyInput-DummyInput"
|
||||
id="react-select-2-input"
|
||||
readonly=""
|
||||
tabindex="0"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="CalculationOptions__indicators css-1hb7zxy-IndicatorsContainer"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="CalculationOptions__indicator CalculationOptions__clear-indicator css-13eygzs-indicatorContainer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="css-tj5bde-Svg"
|
||||
focusable="false"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
>
|
||||
<path
|
||||
d="M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<span
|
||||
class="CalculationOptions__indicator-separator css-43ykx9-indicatorSeparator"
|
||||
/>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="CalculationOptions__indicator CalculationOptions__dropdown-indicator css-1f9iddu-indicatorContainer"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-chevron-up ChevronUpIcon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="CalculationOptions__menu css-1rsmi4x-menu"
|
||||
>
|
||||
<div
|
||||
class="CalculationOptions__menu-list css-g29tl0-MenuList"
|
||||
>
|
||||
<div
|
||||
class="KanbanCalculationOptions_CustomOption active"
|
||||
>
|
||||
<span>
|
||||
Count
|
||||
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="KanbanCalculationOptions_CustomOption "
|
||||
>
|
||||
<span>
|
||||
Count Value
|
||||
|
||||
<i
|
||||
class="CompassIcon icon-chevron-right ChevronRightIcon"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="KanbanCalculationOptions_CustomOption "
|
||||
>
|
||||
<span>
|
||||
Count Unique Values
|
||||
|
||||
<i
|
||||
class="CompassIcon icon-chevron-right ChevronRightIcon"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
name="calculation_options"
|
||||
type="hidden"
|
||||
value="count"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/kanban/calculation/KanbanCalculation no menu should appear in readonly mode 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="KanbanCalculation"
|
||||
>
|
||||
<button
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
3
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -0,0 +1,355 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/kanban/calculations/KanbanCalculationOptions base case 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="CalculationOptions css-2b097c-container"
|
||||
>
|
||||
<span
|
||||
aria-atomic="false"
|
||||
aria-live="polite"
|
||||
aria-relevant="additions text"
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
>
|
||||
<span
|
||||
id="aria-selection"
|
||||
/>
|
||||
<span
|
||||
id="aria-context"
|
||||
>
|
||||
Select is focused , press Down to open the menu,
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
class="CalculationOptions__control CalculationOptions__control--is-focused css-1s59geg-Control"
|
||||
>
|
||||
<div
|
||||
class="CalculationOptions__value-container CalculationOptions__value-container--has-value css-1mxrbau-ValueContainer"
|
||||
>
|
||||
<div
|
||||
class="CalculationOptions__single-value css-1brck82-singleValue"
|
||||
>
|
||||
Count
|
||||
</div>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
class="css-wmatm6-dummyInput-DummyInput"
|
||||
id="react-select-2-input"
|
||||
readonly=""
|
||||
tabindex="0"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="CalculationOptions__indicators css-1hb7zxy-IndicatorsContainer"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="CalculationOptions__indicator CalculationOptions__clear-indicator css-13eygzs-indicatorContainer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="css-tj5bde-Svg"
|
||||
focusable="false"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
>
|
||||
<path
|
||||
d="M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<span
|
||||
class="CalculationOptions__indicator-separator css-43ykx9-indicatorSeparator"
|
||||
/>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="CalculationOptions__indicator CalculationOptions__dropdown-indicator css-1f9iddu-indicatorContainer"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-chevron-up ChevronUpIcon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
name="calculation_options"
|
||||
type="hidden"
|
||||
value="count"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/kanban/calculations/KanbanCalculationOptions with menu open 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="CalculationOptions css-2b097c-container"
|
||||
>
|
||||
<span
|
||||
aria-atomic="false"
|
||||
aria-live="polite"
|
||||
aria-relevant="additions text"
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
>
|
||||
<span
|
||||
id="aria-selection"
|
||||
/>
|
||||
<span
|
||||
id="aria-context"
|
||||
>
|
||||
3 results available. Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
class="CalculationOptions__control CalculationOptions__control--is-focused CalculationOptions__control--menu-is-open css-1s59geg-Control"
|
||||
>
|
||||
<div
|
||||
class="CalculationOptions__value-container CalculationOptions__value-container--has-value css-1mxrbau-ValueContainer"
|
||||
>
|
||||
<div
|
||||
class="CalculationOptions__single-value css-1brck82-singleValue"
|
||||
>
|
||||
Count
|
||||
</div>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
class="css-wmatm6-dummyInput-DummyInput"
|
||||
id="react-select-3-input"
|
||||
readonly=""
|
||||
tabindex="0"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="CalculationOptions__indicators css-1hb7zxy-IndicatorsContainer"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="CalculationOptions__indicator CalculationOptions__clear-indicator css-13eygzs-indicatorContainer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="css-tj5bde-Svg"
|
||||
focusable="false"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
>
|
||||
<path
|
||||
d="M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<span
|
||||
class="CalculationOptions__indicator-separator css-43ykx9-indicatorSeparator"
|
||||
/>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="CalculationOptions__indicator CalculationOptions__dropdown-indicator css-1f9iddu-indicatorContainer"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-chevron-up ChevronUpIcon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="CalculationOptions__menu css-1rsmi4x-menu"
|
||||
>
|
||||
<div
|
||||
class="CalculationOptions__menu-list css-g29tl0-MenuList"
|
||||
>
|
||||
<div
|
||||
class="KanbanCalculationOptions_CustomOption active"
|
||||
>
|
||||
<span>
|
||||
Count
|
||||
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="KanbanCalculationOptions_CustomOption "
|
||||
>
|
||||
<span>
|
||||
Count Value
|
||||
|
||||
<i
|
||||
class="CompassIcon icon-chevron-right ChevronRightIcon"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="KanbanCalculationOptions_CustomOption "
|
||||
>
|
||||
<span>
|
||||
Count Unique Values
|
||||
|
||||
<i
|
||||
class="CompassIcon icon-chevron-right ChevronRightIcon"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
name="calculation_options"
|
||||
type="hidden"
|
||||
value="count"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/kanban/calculations/KanbanCalculationOptions with submenu open 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="CalculationOptions css-2b097c-container"
|
||||
>
|
||||
<span
|
||||
aria-atomic="false"
|
||||
aria-live="polite"
|
||||
aria-relevant="additions text"
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
>
|
||||
<span
|
||||
id="aria-selection"
|
||||
/>
|
||||
<span
|
||||
id="aria-context"
|
||||
>
|
||||
3 results available. Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
class="CalculationOptions__control CalculationOptions__control--is-focused CalculationOptions__control--menu-is-open css-1s59geg-Control"
|
||||
>
|
||||
<div
|
||||
class="CalculationOptions__value-container CalculationOptions__value-container--has-value css-1mxrbau-ValueContainer"
|
||||
>
|
||||
<div
|
||||
class="CalculationOptions__single-value css-1brck82-singleValue"
|
||||
>
|
||||
Count
|
||||
</div>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
class="css-wmatm6-dummyInput-DummyInput"
|
||||
id="react-select-4-input"
|
||||
readonly=""
|
||||
tabindex="0"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="CalculationOptions__indicators css-1hb7zxy-IndicatorsContainer"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="CalculationOptions__indicator CalculationOptions__clear-indicator css-13eygzs-indicatorContainer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="css-tj5bde-Svg"
|
||||
focusable="false"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
>
|
||||
<path
|
||||
d="M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<span
|
||||
class="CalculationOptions__indicator-separator css-43ykx9-indicatorSeparator"
|
||||
/>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="CalculationOptions__indicator CalculationOptions__dropdown-indicator css-1f9iddu-indicatorContainer"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-chevron-up ChevronUpIcon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="CalculationOptions__menu css-1rsmi4x-menu"
|
||||
>
|
||||
<div
|
||||
class="CalculationOptions__menu-list css-g29tl0-MenuList"
|
||||
>
|
||||
<div
|
||||
class="KanbanCalculationOptions_CustomOption active"
|
||||
>
|
||||
<span>
|
||||
Count
|
||||
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="KanbanCalculationOptions_CustomOption "
|
||||
>
|
||||
<span>
|
||||
Count Value
|
||||
|
||||
<i
|
||||
class="CompassIcon icon-chevron-right ChevronRightIcon"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="KanbanCalculationOptions_CustomOption "
|
||||
>
|
||||
<span>
|
||||
Count Unique Values
|
||||
|
||||
<i
|
||||
class="CompassIcon icon-chevron-right ChevronRightIcon"
|
||||
/>
|
||||
</span>
|
||||
<div
|
||||
class="dropdown-submenu"
|
||||
>
|
||||
<div
|
||||
class="drops "
|
||||
>
|
||||
<span>
|
||||
Status
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="drops active"
|
||||
>
|
||||
<span>
|
||||
Property 1
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="drops "
|
||||
>
|
||||
<span>
|
||||
Property 2
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="drops "
|
||||
>
|
||||
<span>
|
||||
Property 3
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
name="calculation_options"
|
||||
type="hidden"
|
||||
value="count"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -0,0 +1,17 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/kanban/calculations/Option base case 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="KanbanCalculationOptions_CustomOption "
|
||||
>
|
||||
<span>
|
||||
Count Unique Values
|
||||
|
||||
<i
|
||||
class="CompassIcon icon-chevron-right ChevronRightIcon"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
14
webapp/src/components/kanban/calculation/calculation.scss
Normal file
14
webapp/src/components/kanban/calculation/calculation.scss
Normal file
@ -0,0 +1,14 @@
|
||||
.KanbanCalculation {
|
||||
position: relative;
|
||||
|
||||
button {
|
||||
cursor: pointer !important;
|
||||
height: 24px;
|
||||
padding: 0 6px;
|
||||
min-width: 24px;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(var(--center-channel-color-rgb), 0.1);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react'
|
||||
|
||||
import {render} from '@testing-library/react'
|
||||
|
||||
import {TestBlockFactory} from '../../../test/testBlockFactory'
|
||||
|
||||
import {wrapIntl} from '../../../testUtils'
|
||||
|
||||
import {KanbanCalculation} from './calculation'
|
||||
|
||||
describe('components/kanban/calculation/KanbanCalculation', () => {
|
||||
const board = TestBlockFactory.createBoard()
|
||||
const cards = [
|
||||
TestBlockFactory.createCard(board),
|
||||
TestBlockFactory.createCard(board),
|
||||
TestBlockFactory.createCard(board),
|
||||
]
|
||||
|
||||
test('base case', () => {
|
||||
const component = wrapIntl((
|
||||
<KanbanCalculation
|
||||
cards={cards}
|
||||
cardProperties={board.fields.cardProperties}
|
||||
menuOpen={false}
|
||||
onMenuClose={() => {}}
|
||||
onMenuOpen={() => {}}
|
||||
onChange={() => {}}
|
||||
value={'count'}
|
||||
property={board.fields.cardProperties[0]}
|
||||
readonly={false}
|
||||
/>
|
||||
))
|
||||
|
||||
const {container} = render(component)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('calculations menu open', () => {
|
||||
const component = wrapIntl((
|
||||
<KanbanCalculation
|
||||
cards={cards}
|
||||
cardProperties={board.fields.cardProperties}
|
||||
menuOpen={true}
|
||||
onMenuClose={() => {}}
|
||||
onMenuOpen={() => {}}
|
||||
onChange={() => {}}
|
||||
value={'count'}
|
||||
property={board.fields.cardProperties[0]}
|
||||
readonly={false}
|
||||
/>
|
||||
))
|
||||
|
||||
const {container} = render(component)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('no menu should appear in readonly mode', () => {
|
||||
const component = wrapIntl((
|
||||
<KanbanCalculation
|
||||
cards={cards}
|
||||
cardProperties={board.fields.cardProperties}
|
||||
menuOpen={true}
|
||||
onMenuClose={() => {}}
|
||||
onMenuOpen={() => {}}
|
||||
onChange={() => {}}
|
||||
value={'count'}
|
||||
property={board.fields.cardProperties[0]}
|
||||
readonly={true}
|
||||
/>
|
||||
))
|
||||
|
||||
const {container} = render(component)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
})
|
59
webapp/src/components/kanban/calculation/calculation.tsx
Normal file
59
webapp/src/components/kanban/calculation/calculation.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react'
|
||||
import {useIntl} from 'react-intl'
|
||||
|
||||
import {Card} from '../../../blocks/card'
|
||||
import Button from '../../../widgets/buttons/button'
|
||||
import './calculation.scss'
|
||||
import {IPropertyTemplate} from '../../../blocks/board'
|
||||
|
||||
import Calculations from '../../calculations/calculations'
|
||||
|
||||
import {KanbanCalculationOptions} from './calculationOptions'
|
||||
|
||||
type Props = {
|
||||
cards: Card[]
|
||||
cardProperties: IPropertyTemplate[]
|
||||
menuOpen: boolean
|
||||
onMenuClose: () => void
|
||||
onMenuOpen: () => void
|
||||
onChange: (data: { calculation: string, propertyId: string }) => void
|
||||
value: string
|
||||
property: IPropertyTemplate
|
||||
readonly: boolean
|
||||
}
|
||||
|
||||
function KanbanCalculation(props: Props): JSX.Element {
|
||||
const intl = useIntl()
|
||||
|
||||
return (
|
||||
<div className='KanbanCalculation'>
|
||||
<Button
|
||||
onClick={() => (props.menuOpen ? props.onMenuClose : props.onMenuOpen)()}
|
||||
onBlur={props.onMenuClose}
|
||||
>
|
||||
{Calculations[props.value] ? Calculations[props.value](props.cards, props.property, intl) : ''}
|
||||
</Button>
|
||||
|
||||
{
|
||||
!props.readonly && props.menuOpen && (
|
||||
<KanbanCalculationOptions
|
||||
value={props.value}
|
||||
property={props.property}
|
||||
menuOpen={props.menuOpen}
|
||||
onChange={(data: { calculation: string, propertyId: string }) => {
|
||||
props.onChange(data)
|
||||
props.onMenuClose()
|
||||
}}
|
||||
cardProperties={props.cardProperties}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
KanbanCalculation,
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
.KanbanCalculationOptions_CustomOption {
|
||||
color: rgba(var(--center-channel-color-rgb), 1);
|
||||
min-height: 24px;
|
||||
max-width: 220px;
|
||||
width: 200px;
|
||||
padding: 2px 12px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
background: rgba(var(--center-channel-color-rgb), 0.1);
|
||||
}
|
||||
|
||||
span {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.drops {
|
||||
padding: 2px 14px;
|
||||
width: 200px;
|
||||
|
||||
&.active,
|
||||
&.active:hover {
|
||||
background-color: rgb(var(--sidebar-bg-rgb));
|
||||
color: rgb(var(--sidebar-text-rgb));
|
||||
}
|
||||
}
|
||||
|
||||
.customs:hover,
|
||||
.drops:hover {
|
||||
background: rgba(var(--center-channel-color-rgb), 0.1);
|
||||
}
|
||||
|
||||
.dropdown-submenu {
|
||||
position: fixed;
|
||||
overflow: auto;
|
||||
background: rgb(var(--center-channel-bg-rgb));
|
||||
border: 0;
|
||||
box-shadow: 0 0 0 1px hsla(0, 0%, 0%, 0.1), 0 4px 11px hsla(0, 0%, 0%, 0.1);
|
||||
z-index: 999;
|
||||
padding: 6px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.CompassIcon.icon-chevron-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: rgb(var(--sidebar-bg-rgb));
|
||||
|
||||
> span {
|
||||
color: rgb(var(--sidebar-text-rgb));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.CalculationOptions__menu {
|
||||
.CalculationOptions__menu-list {
|
||||
max-height: 400px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react'
|
||||
|
||||
import {render} from '@testing-library/react'
|
||||
|
||||
import userEvent from '@testing-library/user-event'
|
||||
|
||||
import {TestBlockFactory} from '../../../test/testBlockFactory'
|
||||
|
||||
import {KanbanCalculationOptions} from './calculationOptions'
|
||||
|
||||
describe('components/kanban/calculations/KanbanCalculationOptions', () => {
|
||||
const board = TestBlockFactory.createBoard()
|
||||
|
||||
test('base case', () => {
|
||||
const component = (
|
||||
<KanbanCalculationOptions
|
||||
value={'count'}
|
||||
property={board.fields.cardProperties[1]}
|
||||
menuOpen={false}
|
||||
onChange={() => {}}
|
||||
cardProperties={board.fields.cardProperties}
|
||||
/>
|
||||
)
|
||||
|
||||
const {container} = render(component)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('with menu open', () => {
|
||||
const component = (
|
||||
<KanbanCalculationOptions
|
||||
value={'count'}
|
||||
property={board.fields.cardProperties[1]}
|
||||
menuOpen={true}
|
||||
onChange={() => {}}
|
||||
cardProperties={board.fields.cardProperties}
|
||||
/>
|
||||
)
|
||||
|
||||
const {container} = render(component)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('with submenu open', () => {
|
||||
const component = (
|
||||
<KanbanCalculationOptions
|
||||
value={'count'}
|
||||
property={board.fields.cardProperties[1]}
|
||||
menuOpen={true}
|
||||
onChange={() => {}}
|
||||
cardProperties={board.fields.cardProperties}
|
||||
/>
|
||||
)
|
||||
|
||||
const {container, getByText} = render(component)
|
||||
const countUniqueValuesOption = getByText('Count Unique Values')
|
||||
expect(countUniqueValuesOption).toBeDefined()
|
||||
userEvent.hover(countUniqueValuesOption)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
})
|
@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react'
|
||||
|
||||
import {
|
||||
CalculationOptions,
|
||||
CommonCalculationOptionProps,
|
||||
optionsByType,
|
||||
} from '../../calculations/options'
|
||||
import {IPropertyTemplate} from '../../../blocks/board'
|
||||
|
||||
import './calculationOption.scss'
|
||||
import {Option, OptionProps} from './kanbanOption'
|
||||
|
||||
type Props = CommonCalculationOptionProps & {
|
||||
cardProperties: IPropertyTemplate[]
|
||||
onChange: (data: {calculation: string, propertyId: string}) => void
|
||||
}
|
||||
|
||||
export const KanbanCalculationOptions = (props: Props): JSX.Element => {
|
||||
const options: OptionProps[] = []
|
||||
|
||||
// Show common options, first,
|
||||
// followed by type-specific functions
|
||||
optionsByType.get('common')!.forEach((typeOption) => {
|
||||
if (typeOption.value !== 'none') {
|
||||
options.push({
|
||||
...typeOption,
|
||||
cardProperties: props.cardProperties,
|
||||
onChange: props.onChange,
|
||||
activeValue: props.value,
|
||||
activeProperty: props.property!,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
props.cardProperties.
|
||||
map((property) => optionsByType.get(property.type) || []).
|
||||
forEach((typeOptions) => {
|
||||
typeOptions.forEach((typeOption) => {
|
||||
options.push({
|
||||
...typeOption,
|
||||
cardProperties: props.cardProperties,
|
||||
onChange: props.onChange,
|
||||
activeValue: props.value,
|
||||
activeProperty: props.property!,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<CalculationOptions
|
||||
value={props.value}
|
||||
menuOpen={props.menuOpen}
|
||||
onClose={props.onClose}
|
||||
onChange={props.onChange}
|
||||
property={props.property}
|
||||
options={options}
|
||||
components={{Option}}
|
||||
/>
|
||||
)
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react'
|
||||
|
||||
import {render} from '@testing-library/react'
|
||||
|
||||
import {TestBlockFactory} from '../../../test/testBlockFactory'
|
||||
|
||||
import {Option} from './kanbanOption'
|
||||
|
||||
describe('components/kanban/calculations/Option', () => {
|
||||
const board = TestBlockFactory.createBoard()
|
||||
|
||||
test('base case', () => {
|
||||
const component = (
|
||||
<Option
|
||||
data={{
|
||||
label: 'Count Unique Values',
|
||||
displayName: 'Unique',
|
||||
value: 'countUniqueValue',
|
||||
cardProperties: board.fields.cardProperties,
|
||||
onChange: () => {},
|
||||
activeValue: 'count',
|
||||
activeProperty: board.fields.cardProperties[1],
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
const {container} = render(component)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
})
|
102
webapp/src/components/kanban/calculation/kanbanOption.tsx
Normal file
102
webapp/src/components/kanban/calculation/kanbanOption.tsx
Normal file
@ -0,0 +1,102 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React, {useState} from 'react'
|
||||
|
||||
import {Option as SelectOption, typesByOptions} from '../../calculations/options'
|
||||
import {IPropertyTemplate} from '../../../blocks/board'
|
||||
import ChevronRight from '../../../widgets/icons/chevronRight'
|
||||
import {Constants} from '../../../constants'
|
||||
|
||||
type OptionProps = SelectOption & {
|
||||
cardProperties: IPropertyTemplate[]
|
||||
onChange: (data: {calculation: string, propertyId: string}) => void
|
||||
activeValue: string
|
||||
activeProperty: IPropertyTemplate
|
||||
}
|
||||
|
||||
const Option = (props: {data: OptionProps}): JSX.Element => {
|
||||
const [submenu, setSubmenu] = useState(false)
|
||||
const [height, setHeight] = useState(0)
|
||||
const [menuOptionRight, setMenuOptionRight] = useState(0)
|
||||
const [calculationToProperties, setCalculationToProperties] = useState<Map<string, IPropertyTemplate[]>>(new Map())
|
||||
|
||||
const toggleOption = (e: any) => {
|
||||
if (submenu) {
|
||||
setSubmenu(false)
|
||||
} else {
|
||||
const rect = e.target.getBoundingClientRect()
|
||||
setHeight(rect.y)
|
||||
setMenuOptionRight(rect.x + rect.width)
|
||||
setSubmenu(true)
|
||||
}
|
||||
}
|
||||
|
||||
if (!calculationToProperties.get(props.data.value)) {
|
||||
const supportedPropertyTypes = new Map<string, boolean>([])
|
||||
if (typesByOptions.get(props.data.value)) {
|
||||
(typesByOptions.get(props.data.value) || []).
|
||||
forEach((propertyType) => supportedPropertyTypes.set(propertyType, true))
|
||||
}
|
||||
|
||||
const supportedProperties = props.data.cardProperties.
|
||||
filter((property) => supportedPropertyTypes.get(property.type) || supportedPropertyTypes.get('common'))
|
||||
|
||||
calculationToProperties.set(props.data.value, supportedProperties)
|
||||
setCalculationToProperties(calculationToProperties)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`KanbanCalculationOptions_CustomOption ${props.data.activeValue === props.data.value ? 'active' : ''}`}
|
||||
onMouseEnter={toggleOption}
|
||||
onMouseLeave={toggleOption}
|
||||
onClick={() => {
|
||||
if (props.data.value !== 'count') {
|
||||
return
|
||||
}
|
||||
|
||||
props.data.onChange({
|
||||
calculation: 'count',
|
||||
propertyId: Constants.titleColumnId,
|
||||
})
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
{props.data.label} {props.data.value !== 'count' && <ChevronRight/>}
|
||||
</span>
|
||||
|
||||
{
|
||||
submenu && props.data.value !== 'count' && (
|
||||
<div
|
||||
className='dropdown-submenu'
|
||||
style={{top: `${height - 10}px`, left: `${menuOptionRight}px`}}
|
||||
>
|
||||
|
||||
{
|
||||
calculationToProperties.get(props.data.value) &&
|
||||
calculationToProperties.get(props.data.value)!.map((property) => (
|
||||
<div
|
||||
key={property.id}
|
||||
className={`drops ${props.data.activeProperty.id === property.id ? 'active' : ''}`}
|
||||
onClick={() => {
|
||||
props.data.onChange({
|
||||
calculation: props.data.value,
|
||||
propertyId: property.id,
|
||||
})
|
||||
}}
|
||||
>
|
||||
<span>{property.name}</span>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Option,
|
||||
OptionProps,
|
||||
}
|
@ -29,8 +29,6 @@
|
||||
}
|
||||
|
||||
> div {
|
||||
margin-right: 8px;
|
||||
|
||||
&:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
@ -47,6 +45,7 @@
|
||||
.Label {
|
||||
max-width: 165px;
|
||||
margin-right: 5px;
|
||||
margin-bottom: 0;
|
||||
|
||||
.Editable {
|
||||
background: transparent;
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
/* eslint-disable max-lines */
|
||||
import React, {useCallback} from 'react'
|
||||
import React, {useCallback, useState} from 'react'
|
||||
import {FormattedMessage, injectIntl, IntlShape} from 'react-intl'
|
||||
|
||||
import {Board, IPropertyOption, IPropertyTemplate, BoardGroup} from '../../blocks/board'
|
||||
@ -166,6 +166,13 @@ const Kanban = (props: Props) => {
|
||||
})
|
||||
}, [cards, activeView, groupByProperty, props.selectedCardIds])
|
||||
|
||||
const [showCalculationsMenu, setShowCalculationsMenu] = useState<Map<string, boolean>>(new Map<string, boolean>())
|
||||
const toggleOptions = (templateId: string, show: boolean) => {
|
||||
const newShowOptions = new Map<string, boolean>(showCalculationsMenu)
|
||||
newShowOptions.set(templateId, show)
|
||||
setShowCalculationsMenu(newShowOptions)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='Kanban'>
|
||||
<div
|
||||
@ -186,6 +193,9 @@ const Kanban = (props: Props) => {
|
||||
readonly={props.readonly}
|
||||
propertyNameChanged={propertyNameChanged}
|
||||
onDropToColumn={onDropToColumn}
|
||||
calculationMenuOpen={showCalculationsMenu.get(group.option.id) || false}
|
||||
onCalculationMenuOpen={() => toggleOptions(group.option.id, true)}
|
||||
onCalculationMenuClose={() => toggleOptions(group.option.id, false)}
|
||||
/>
|
||||
))}
|
||||
|
||||
|
@ -10,7 +10,6 @@ import {IPropertyOption, IPropertyTemplate, Board, BoardGroup} from '../../block
|
||||
import {BoardView} from '../../blocks/boardView'
|
||||
import {Card} from '../../blocks/card'
|
||||
import mutator from '../../mutator'
|
||||
import Button from '../../widgets/buttons/button'
|
||||
import IconButton from '../../widgets/buttons/iconButton'
|
||||
import AddIcon from '../../widgets/icons/add'
|
||||
import DeleteIcon from '../../widgets/icons/delete'
|
||||
@ -21,6 +20,8 @@ import MenuWrapper from '../../widgets/menuWrapper'
|
||||
import Editable from '../../widgets/editable'
|
||||
import Label from '../../widgets/label'
|
||||
|
||||
import {KanbanCalculation} from './calculation/calculation'
|
||||
|
||||
type Props = {
|
||||
board: Board
|
||||
activeView: BoardView
|
||||
@ -31,8 +32,16 @@ type Props = {
|
||||
addCard: (groupByOptionId?: string) => Promise<void>
|
||||
propertyNameChanged: (option: IPropertyOption, text: string) => Promise<void>
|
||||
onDropToColumn: (srcOption: IPropertyOption, card?: Card, dstOption?: IPropertyOption) => void
|
||||
calculationMenuOpen: boolean
|
||||
onCalculationMenuOpen: () => void
|
||||
onCalculationMenuClose: () => void
|
||||
}
|
||||
|
||||
const defaultCalculation = 'count'
|
||||
const defaultProperty: IPropertyTemplate = {
|
||||
id: Constants.titleColumnId,
|
||||
} as IPropertyTemplate
|
||||
|
||||
export default function KanbanColumnHeader(props: Props): JSX.Element {
|
||||
const {board, activeView, intl, group, groupByProperty} = props
|
||||
const [groupTitle, setGroupTitle] = useState(group.option.value)
|
||||
@ -66,6 +75,10 @@ export default function KanbanColumnHeader(props: Props): JSX.Element {
|
||||
className += ' dragover'
|
||||
}
|
||||
|
||||
const groupCalculation = props.activeView.fields.kanbanCalculations[props.group.option.id]
|
||||
const calculationValue = groupCalculation ? groupCalculation.calculation : defaultCalculation
|
||||
const calculationProperty = groupCalculation ? props.board.fields.cardProperties.find((property) => property.id === groupCalculation.propertyId) || defaultProperty : defaultProperty
|
||||
|
||||
return (
|
||||
<div
|
||||
key={group.option.id || 'empty'}
|
||||
@ -108,7 +121,31 @@ export default function KanbanColumnHeader(props: Props): JSX.Element {
|
||||
spellCheck={true}
|
||||
/>
|
||||
</Label>}
|
||||
<Button>{`${group.cards.length}`}</Button>
|
||||
<KanbanCalculation
|
||||
cards={group.cards}
|
||||
menuOpen={props.calculationMenuOpen}
|
||||
value={calculationValue}
|
||||
property={calculationProperty}
|
||||
onMenuClose={props.onCalculationMenuClose}
|
||||
onMenuOpen={props.onCalculationMenuOpen}
|
||||
cardProperties={board.fields.cardProperties}
|
||||
readonly={props.readonly}
|
||||
onChange={(data: {calculation: string, propertyId: string}) => {
|
||||
if (data.calculation === calculationValue && data.propertyId === calculationProperty.id) {
|
||||
return
|
||||
}
|
||||
|
||||
const newCalculations = {
|
||||
...props.activeView.fields.kanbanCalculations,
|
||||
}
|
||||
newCalculations[props.group.option.id] = {
|
||||
calculation: data.calculation,
|
||||
propertyId: data.propertyId,
|
||||
}
|
||||
|
||||
mutator.changeViewKanbanCalculations(props.activeView.id, props.activeView.fields.kanbanCalculations, newCalculations)
|
||||
}}
|
||||
/>
|
||||
<div className='octo-spacer'/>
|
||||
{!props.readonly &&
|
||||
<>
|
||||
|
@ -6,7 +6,7 @@ exports[`components/properties/dateRange cancel set via text input 1`] = `
|
||||
class="DateRange octo-propertyvalue"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -23,7 +23,7 @@ exports[`components/properties/dateRange handle clear 1`] = `
|
||||
class="DateRange octo-propertyvalue"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -40,7 +40,7 @@ exports[`components/properties/dateRange returns default correctly 1`] = `
|
||||
class="DateRange empty octo-propertyvalue"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span />
|
||||
@ -55,7 +55,7 @@ exports[`components/properties/dateRange returns local correctly - es local 1`]
|
||||
class="DateRange octo-propertyvalue"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -72,7 +72,7 @@ exports[`components/properties/dateRange set via text input 1`] = `
|
||||
class="DateRange octo-propertyvalue"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -89,7 +89,7 @@ exports[`components/properties/dateRange set via text input, es locale 1`] = `
|
||||
class="DateRange octo-propertyvalue"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
|
@ -165,7 +165,7 @@ exports[`components/table/Table extended should match snapshot with CreatedBy 1`
|
||||
class="open-button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -253,7 +253,7 @@ exports[`components/table/Table extended should match snapshot with CreatedBy 1`
|
||||
class="open-button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -553,7 +553,7 @@ exports[`components/table/Table extended should match snapshot with CreatedBy 2`
|
||||
class="open-button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -641,7 +641,7 @@ exports[`components/table/Table extended should match snapshot with CreatedBy 2`
|
||||
class="open-button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -941,7 +941,7 @@ exports[`components/table/Table extended should match snapshot with UpdatedAt 1`
|
||||
class="open-button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -1029,7 +1029,7 @@ exports[`components/table/Table extended should match snapshot with UpdatedAt 1`
|
||||
class="open-button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -1329,7 +1329,7 @@ exports[`components/table/Table extended should match snapshot with UpdatedBy 1`
|
||||
class="open-button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -1417,7 +1417,7 @@ exports[`components/table/Table extended should match snapshot with UpdatedBy 1`
|
||||
class="open-button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -1693,7 +1693,7 @@ exports[`components/table/Table should match snapshot 1`] = `
|
||||
class="open-button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -1950,7 +1950,7 @@ exports[`components/table/Table should match snapshot with GroupBy 1`] = `
|
||||
|
||||
</div>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -2168,7 +2168,7 @@ exports[`components/table/Table should match snapshot, read-only 1`] = `
|
||||
class="open-button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
|
@ -39,7 +39,7 @@ exports[`should match snapshot on read only 1`] = `
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -88,7 +88,7 @@ exports[`should match snapshot with Group 1`] = `
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -159,7 +159,7 @@ exports[`should match snapshot, add new 1`] = `
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -230,7 +230,7 @@ exports[`should match snapshot, edit title 1`] = `
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -301,7 +301,7 @@ exports[`should match snapshot, hide group 1`] = `
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -368,7 +368,7 @@ exports[`should match snapshot, no groups 1`] = `
|
||||
|
||||
</div>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
|
@ -32,7 +32,7 @@ exports[`components/table/TableRow should match snapshot 1`] = `
|
||||
class="open-button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -77,7 +77,7 @@ exports[`components/table/TableRow should match snapshot, collapsed tree 1`] = `
|
||||
class="open-button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -122,7 +122,7 @@ exports[`components/table/TableRow should match snapshot, display properties 1`]
|
||||
class="open-button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -205,7 +205,7 @@ exports[`components/table/TableRow should match snapshot, isSelected 1`] = `
|
||||
class="open-button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -251,7 +251,7 @@ exports[`components/table/TableRow should match snapshot, read-only 1`] = `
|
||||
class="open-button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -296,7 +296,7 @@ exports[`components/table/TableRow should match snapshot, resizing column 1`] =
|
||||
class="open-button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
|
@ -32,7 +32,7 @@ exports[`components/table/TableRows should match snapshot, fire events 1`] = `
|
||||
class="open-button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
|
@ -14,6 +14,8 @@ import {BoardView} from '../../../blocks/boardView'
|
||||
import {Card} from '../../../blocks/card'
|
||||
import {Options} from '../../calculations/options'
|
||||
|
||||
import {TableCalculationOptions} from './tableCalculationOptions'
|
||||
|
||||
type Props = {
|
||||
board: Board
|
||||
cards: Card[]
|
||||
@ -76,6 +78,7 @@ const CalculationRow = (props: Props): JSX.Element => {
|
||||
cards={props.cards}
|
||||
property={template}
|
||||
hovered={hovered}
|
||||
optionsComponent={TableCalculationOptions}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react'
|
||||
|
||||
import {CalculationOptions, CommonCalculationOptionProps, optionsByType} from '../../calculations/options'
|
||||
|
||||
export const TableCalculationOptions = (props: CommonCalculationOptionProps): JSX.Element => {
|
||||
const options = [...optionsByType.get('common')!]
|
||||
if (props.property && optionsByType.get(props.property.type)) {
|
||||
options.push(...optionsByType.get(props.property.type)!)
|
||||
}
|
||||
|
||||
return (
|
||||
<CalculationOptions
|
||||
value={props.value}
|
||||
menuOpen={props.menuOpen}
|
||||
onClose={props.onClose}
|
||||
onChange={props.onChange}
|
||||
property={props.property}
|
||||
options={options}
|
||||
/>
|
||||
)
|
||||
}
|
@ -31,7 +31,7 @@ exports[`components/viewHeader/filterComponent return filterComponent 1`] = `
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -149,7 +149,7 @@ exports[`components/viewHeader/filterComponent return filterComponent 1`] = `
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -161,7 +161,7 @@ exports[`components/viewHeader/filterComponent return filterComponent 1`] = `
|
||||
class="octo-spacer"
|
||||
/>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -171,7 +171,7 @@ exports[`components/viewHeader/filterComponent return filterComponent 1`] = `
|
||||
</div>
|
||||
<br />
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -214,7 +214,7 @@ exports[`components/viewHeader/filterComponent return filterComponent and add Fi
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -332,7 +332,7 @@ exports[`components/viewHeader/filterComponent return filterComponent and add Fi
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -344,7 +344,7 @@ exports[`components/viewHeader/filterComponent return filterComponent and add Fi
|
||||
class="octo-spacer"
|
||||
/>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -354,7 +354,7 @@ exports[`components/viewHeader/filterComponent return filterComponent and add Fi
|
||||
</div>
|
||||
<br />
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -397,7 +397,7 @@ exports[`components/viewHeader/filterComponent return filterComponent and click
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -411,7 +411,7 @@ exports[`components/viewHeader/filterComponent return filterComponent and click
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -527,7 +527,7 @@ exports[`components/viewHeader/filterComponent return filterComponent and click
|
||||
class="octo-spacer"
|
||||
/>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -537,7 +537,7 @@ exports[`components/viewHeader/filterComponent return filterComponent and click
|
||||
</div>
|
||||
<br />
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -580,7 +580,7 @@ exports[`components/viewHeader/filterComponent return filterComponent and filter
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -698,7 +698,7 @@ exports[`components/viewHeader/filterComponent return filterComponent and filter
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -710,7 +710,7 @@ exports[`components/viewHeader/filterComponent return filterComponent and filter
|
||||
class="octo-spacer"
|
||||
/>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -720,7 +720,7 @@ exports[`components/viewHeader/filterComponent return filterComponent and filter
|
||||
</div>
|
||||
<br />
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
|
@ -11,7 +11,7 @@ exports[`components/viewHeader/filterEntry return filterEntry 1`] = `
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -129,7 +129,7 @@ exports[`components/viewHeader/filterEntry return filterEntry 1`] = `
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -141,7 +141,7 @@ exports[`components/viewHeader/filterEntry return filterEntry 1`] = `
|
||||
class="octo-spacer"
|
||||
/>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -163,7 +163,7 @@ exports[`components/viewHeader/filterEntry return filterEntry and click on delet
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -177,7 +177,7 @@ exports[`components/viewHeader/filterEntry return filterEntry and click on delet
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -293,7 +293,7 @@ exports[`components/viewHeader/filterEntry return filterEntry and click on delet
|
||||
class="octo-spacer"
|
||||
/>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -315,7 +315,7 @@ exports[`components/viewHeader/filterEntry return filterEntry and click on doesn
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -329,7 +329,7 @@ exports[`components/viewHeader/filterEntry return filterEntry and click on doesn
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -445,7 +445,7 @@ exports[`components/viewHeader/filterEntry return filterEntry and click on doesn
|
||||
class="octo-spacer"
|
||||
/>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -467,7 +467,7 @@ exports[`components/viewHeader/filterEntry return filterEntry and click on inclu
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -481,7 +481,7 @@ exports[`components/viewHeader/filterEntry return filterEntry and click on inclu
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -597,7 +597,7 @@ exports[`components/viewHeader/filterEntry return filterEntry and click on inclu
|
||||
class="octo-spacer"
|
||||
/>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -619,7 +619,7 @@ exports[`components/viewHeader/filterEntry return filterEntry and click on is em
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -633,7 +633,7 @@ exports[`components/viewHeader/filterEntry return filterEntry and click on is em
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -749,7 +749,7 @@ exports[`components/viewHeader/filterEntry return filterEntry and click on is em
|
||||
class="octo-spacer"
|
||||
/>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -771,7 +771,7 @@ exports[`components/viewHeader/filterEntry return filterEntry and click on is no
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -785,7 +785,7 @@ exports[`components/viewHeader/filterEntry return filterEntry and click on is no
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -901,7 +901,7 @@ exports[`components/viewHeader/filterEntry return filterEntry and click on is no
|
||||
class="octo-spacer"
|
||||
/>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -923,7 +923,7 @@ exports[`components/viewHeader/filterEntry return filterEntry and click on statu
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -1041,7 +1041,7 @@ exports[`components/viewHeader/filterEntry return filterEntry and click on statu
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -1053,7 +1053,7 @@ exports[`components/viewHeader/filterEntry return filterEntry and click on statu
|
||||
class="octo-spacer"
|
||||
/>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
|
@ -8,7 +8,7 @@ exports[`components/viewHeader/filterValue return filterValue 1`] = `
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -82,7 +82,7 @@ exports[`components/viewHeader/filterValue return filterValue and click Status 1
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -101,7 +101,7 @@ exports[`components/viewHeader/filterValue return filterValue and click Status w
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
|
@ -35,7 +35,7 @@ exports[`components/viewHeader/viewHeader return viewHeader 1`] = `
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -49,7 +49,7 @@ exports[`components/viewHeader/viewHeader return viewHeader 1`] = `
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -66,7 +66,7 @@ exports[`components/viewHeader/viewHeader return viewHeader 1`] = `
|
||||
class="ModalWrapper"
|
||||
>
|
||||
<button
|
||||
class="Button active "
|
||||
class="Button active"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -80,7 +80,7 @@ exports[`components/viewHeader/viewHeader return viewHeader 1`] = `
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button active "
|
||||
class="Button active"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -89,7 +89,7 @@ exports[`components/viewHeader/viewHeader return viewHeader 1`] = `
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -171,7 +171,7 @@ exports[`components/viewHeader/viewHeader return viewHeader readonly 1`] = `
|
||||
class="octo-spacer"
|
||||
/>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
|
@ -8,7 +8,7 @@ exports[`components/viewHeader/viewHeaderGroupByMenu return groupBy menu 1`] = `
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -142,7 +142,7 @@ exports[`components/viewHeader/viewHeaderGroupByMenu return groupBy menu and gro
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -166,7 +166,7 @@ exports[`components/viewHeader/viewHeaderGroupByMenu return groupBy menu and ung
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
|
@ -8,7 +8,7 @@ exports[`components/viewHeader/viewHeaderPropertiesMenu return properties menu 1
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -139,7 +139,7 @@ exports[`components/viewHeader/viewHeaderPropertiesMenu return properties menu w
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
|
@ -13,7 +13,7 @@ exports[`components/viewHeader/ViewHeaderSearch return input after click on sear
|
||||
exports[`components/viewHeader/ViewHeaderSearch return search menu 1`] = `
|
||||
<div>
|
||||
<button
|
||||
class="Button "
|
||||
class="Button"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
|
@ -8,7 +8,7 @@ exports[`components/viewHeader/viewHeaderSortMenu return sort menu 1`] = `
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button active "
|
||||
class="Button active"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -194,7 +194,7 @@ exports[`components/viewHeader/viewHeaderSortMenu return sort menu and do Name s
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button active "
|
||||
class="Button active"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -213,7 +213,7 @@ exports[`components/viewHeader/viewHeaderSortMenu return sort menu and do manual
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button active "
|
||||
class="Button active"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
@ -232,7 +232,7 @@ exports[`components/viewHeader/viewHeaderSortMenu return sort menu and do revert
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="Button active "
|
||||
class="Button active"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
|
@ -3,7 +3,7 @@
|
||||
import {BlockIcons} from './blockIcons'
|
||||
import {Block} from './blocks/block'
|
||||
import {Board, IPropertyOption, IPropertyTemplate, PropertyType, createBoard} from './blocks/board'
|
||||
import {BoardView, ISortOption, createBoardView} from './blocks/boardView'
|
||||
import {BoardView, ISortOption, createBoardView, KanbanCalculationFields} from './blocks/boardView'
|
||||
import {Card, createCard} from './blocks/card'
|
||||
import {FilterGroup} from './blocks/filterGroup'
|
||||
import octoClient, {OctoClient} from './octoClient'
|
||||
@ -562,6 +562,19 @@ class Mutator {
|
||||
)
|
||||
}
|
||||
|
||||
async changeViewKanbanCalculations(viewId: string, oldCalculations: Record<string, KanbanCalculationFields>, calculations: Record<string, KanbanCalculationFields>, description = 'updated kanban calculations'): Promise<void> {
|
||||
await undoManager.perform(
|
||||
async () => {
|
||||
await octoClient.patchBlock(viewId, {updatedFields: {kanbanCalculations: calculations}})
|
||||
},
|
||||
async () => {
|
||||
await octoClient.patchBlock(viewId, {updatedFields: {kanbanCalculations: oldCalculations}})
|
||||
},
|
||||
description,
|
||||
this.undoGroupId,
|
||||
)
|
||||
}
|
||||
|
||||
async hideViewColumn(view: BoardView, columnOptionId: string): Promise<void> {
|
||||
if (view.fields.hiddenOptionIds.includes(columnOptionId)) {
|
||||
return
|
||||
|
@ -579,6 +579,10 @@ class Utils {
|
||||
const readToken = queryString.get('r') || ''
|
||||
return readToken
|
||||
}
|
||||
|
||||
static generateClassName(conditions: Record<string, boolean>): string {
|
||||
return Object.entries(conditions).map(([className, condition]) => (condition ? className : '')).filter((className) => className !== '').join(' ')
|
||||
}
|
||||
}
|
||||
|
||||
export {Utils, IDType}
|
||||
|
@ -3,9 +3,11 @@
|
||||
import React from 'react'
|
||||
|
||||
import './button.scss'
|
||||
import {Utils} from '../../utils'
|
||||
|
||||
type Props = {
|
||||
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void
|
||||
onBlur?: (e: React.FocusEvent<HTMLButtonElement>) => void
|
||||
children?: React.ReactNode
|
||||
title?: string
|
||||
icon?: React.ReactNode
|
||||
@ -14,15 +16,26 @@ type Props = {
|
||||
submit?: boolean
|
||||
emphasis?: string
|
||||
size?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
function Button(props: Props): JSX.Element {
|
||||
const classNames: Record<string, boolean> = {
|
||||
Button: true,
|
||||
active: Boolean(props.active),
|
||||
filled: Boolean(props.filled),
|
||||
}
|
||||
classNames[`emphasis--${props.emphasis}`] = Boolean(props.emphasis)
|
||||
classNames[`size--${props.size}`] = Boolean(props.size)
|
||||
classNames[`${props.className}`] = Boolean(props.className)
|
||||
|
||||
return (
|
||||
<button
|
||||
type={props.submit ? 'submit' : 'button'}
|
||||
onClick={props.onClick}
|
||||
className={`Button ${props.active ? 'active' : ''} ${props.filled ? 'filled' : ''} ${props.emphasis ? 'emphasis--' + props.emphasis : ''} ${props.size ? 'size--' + props.size : ''}`}
|
||||
className={Utils.generateClassName(classNames)}
|
||||
title={props.title}
|
||||
onBlur={props.onBlur}
|
||||
>
|
||||
{props.icon}
|
||||
<span>{props.children}</span>
|
||||
|
15
webapp/src/widgets/icons/chevronRight.tsx
Normal file
15
webapp/src/widgets/icons/chevronRight.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import CompassIcon from './compassIcon'
|
||||
|
||||
export default function ChevronRight(): JSX.Element {
|
||||
return (
|
||||
<CompassIcon
|
||||
icon='chevron-right'
|
||||
className='ChevronRightIcon'
|
||||
/>
|
||||
)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user