Skip to content

Commit

Permalink
feat(dashboard): add grid settings configuration ux
Browse files Browse the repository at this point in the history
  • Loading branch information
jmbuss committed May 10, 2023
1 parent 600e276 commit f5ca885
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 1 deletion.
12 changes: 11 additions & 1 deletion packages/dashboard/src/components/actions/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { memo } from 'react';
import React, { memo, useState } from 'react';
import { useDispatch } from 'react-redux';

import { Button, SpaceBetween, Box } from '@cloudscape-design/components';
import { onToggleReadOnly } from '~/store/actions';
import type { DashboardState } from '~/store/state';
import { isEqual, pick } from 'lodash';
import { DashboardSave } from '~/types';
import DashboardSettings from './settings';

export type ActionsProps = {
grid: DashboardState['grid'];
Expand All @@ -16,6 +17,7 @@ export type ActionsProps = {
};

const Actions: React.FC<ActionsProps> = ({ dashboardConfiguration, editable, grid, readOnly, onSave }) => {
const [dashboardSettingsVisible, setDashboardSettingsVisible] = useState(false);
const dispatch = useDispatch();

const handleOnSave = () => {
Expand All @@ -34,12 +36,20 @@ const Actions: React.FC<ActionsProps> = ({ dashboardConfiguration, editable, gri
dispatch(onToggleReadOnly());
};

const handleOnClose = () => {
setDashboardSettingsVisible(false);
};

return (
<>
<Box variant='awsui-key-label'>Actions</Box>
<SpaceBetween size='s' direction='horizontal'>
{onSave && <Button onClick={handleOnSave}>Save</Button>}
{editable && <Button onClick={handleOnReadOnly}>{readOnly ? 'Edit' : 'Preview'}</Button>}
{editable && !readOnly && (
<Button onClick={() => setDashboardSettingsVisible(true)} iconName='settings' variant='icon' />
)}
<DashboardSettings isVisible={dashboardSettingsVisible} onClose={handleOnClose} />
</SpaceBetween>
</>
);
Expand Down
61 changes: 61 additions & 0 deletions packages/dashboard/src/components/actions/settings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';

import { Box, Checkbox, Modal, NonCancelableCustomEvent, SpaceBetween } from '@cloudscape-design/components';
import { BaseChangeDetail } from '@cloudscape-design/components/input/interfaces';

import LabeledInput from '../util/labeledInput';
import { useGridSettings } from './useGridSettings';

// Should never return NaN
const numberFromDetail = (event: NonCancelableCustomEvent<BaseChangeDetail>) => parseInt(event.detail.value) || 0;

export type DashboardSettingsProps = {
onClose: () => void;
isVisible: boolean;
};

const DashboardSettings: React.FC<DashboardSettingsProps> = ({ onClose, isVisible }) => {
const {
rows,
columns,
cellSize,
stretchToFit,
onChangeCellSize,
onChangeNumberOfColumns,
onChangeNumberOfRows,
onToggleStretchToFit,
} = useGridSettings();

return (
<Modal onDismiss={onClose} visible={isVisible} closeAriaLabel='Close modal' header='Dashboard Settings'>
<Box>
<SpaceBetween direction='vertical' size='l'>
<Checkbox onChange={({ detail }) => onToggleStretchToFit(detail.checked)} checked={stretchToFit}>
Stretch grid to fit screen size.
</Checkbox>
<LabeledInput
label='Cell Size'
disabled={stretchToFit}
type='number'
value={cellSize.toFixed()}
onChange={(event) => onChangeCellSize(numberFromDetail(event))}
/>
<LabeledInput
label='Number of Rows'
type='number'
value={rows.toFixed()}
onChange={(event) => onChangeNumberOfRows(numberFromDetail(event))}
/>
<LabeledInput
label='Number of Columns'
type='number'
value={columns.toFixed()}
onChange={(event) => onChangeNumberOfColumns(numberFromDetail(event))}
/>
</SpaceBetween>
</Box>
</Modal>
);
};

export default DashboardSettings;
54 changes: 54 additions & 0 deletions packages/dashboard/src/components/actions/useGridSettings.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react';
import { act, renderHook } from '@testing-library/react';
import { Provider } from 'react-redux';

import { configureDashboardStore } from '~/store';
import { useGridSettings } from './useGridSettings';
import type { ReactNode } from 'react';
import { initialState } from '~/store/state';

const TestProvider: React.FC<{
children: ReactNode;
}> = ({ children }) => <Provider store={configureDashboardStore(initialState)}>{children}</Provider>;

it('has initial values', () => {
const { result } = renderHook(() => useGridSettings(), {
wrapper: ({ children }) => <TestProvider children={children} />,
});

expect(result.current.rows).toBe(initialState.grid.height);
expect(result.current.columns).toBe(initialState.grid.width);
expect(result.current.cellSize).toBe(initialState.grid.cellSize);
expect(result.current.stretchToFit).toBe(initialState.grid.stretchToFit);
expect(result.current.stretchToFit).toBe(initialState.grid.stretchToFit);
});

it('has can change values', () => {
const { result } = renderHook(() => useGridSettings(), {
wrapper: ({ children }) => <TestProvider children={children} />,
});

const initialCellSize = initialState.grid.cellSize;
act(() => {
result.current.onChangeCellSize(initialCellSize + 1);
});
expect(result.current.cellSize).toBe(initialCellSize + 1);

const initialHeight = initialState.grid.height;
act(() => {
result.current.onChangeNumberOfRows(initialHeight + 1);
});
expect(result.current.rows).toBe(initialHeight + 1);

const initialWidth = initialState.grid.width;
act(() => {
result.current.onChangeNumberOfColumns(initialWidth + 1);
});
expect(result.current.columns).toBe(initialWidth + 1);

const initialStretchToFit = initialState.grid.stretchToFit;
act(() => {
result.current.onToggleStretchToFit(!initialStretchToFit);
});
expect(result.current.stretchToFit).toBe(!initialStretchToFit);
});
39 changes: 39 additions & 0 deletions packages/dashboard/src/components/actions/useGridSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useDispatch, useSelector } from 'react-redux';

import { DashboardState } from '~/store/state';
import {
onChangeDashboardCellSizeAction,
onChangeDashboardHeightAction,
onChangeDashboardStretchToFitAction,
onChangeDashboardWidthAction,
} from '~/store/actions';

export const useGridSettings = () => {
const dispatch = useDispatch();

const { width, height, cellSize, stretchToFit } = useSelector((state: DashboardState) => state.grid);

const onToggleStretchToFit = (updatedStretchToFit: boolean) => {
dispatch(onChangeDashboardStretchToFitAction({ stretchToFit: updatedStretchToFit }));
};
const onChangeNumberOfColumns = (columns: number) => {
dispatch(onChangeDashboardWidthAction({ width: columns }));
};
const onChangeNumberOfRows = (rows: number) => {
dispatch(onChangeDashboardHeightAction({ height: rows }));
};
const onChangeCellSize = (updatedCellSize: number) => {
dispatch(onChangeDashboardCellSizeAction({ cellSize: updatedCellSize }));
};

return {
rows: height,
columns: width,
cellSize,
stretchToFit,
onToggleStretchToFit,
onChangeCellSize,
onChangeNumberOfColumns,
onChangeNumberOfRows,
};
};
13 changes: 13 additions & 0 deletions packages/dashboard/src/components/util/labeledInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';

import { FormField, Input, InputProps } from '@cloudscape-design/components';

export type LabeledInputProps = InputProps & { label: string };

const LabeledInput: React.FC<LabeledInputProps> = ({ label, ...inputProps }) => (
<FormField label={label}>
<Input {...inputProps} />
</FormField>
);

export default LabeledInput;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { changeDashboardStretchToFit, onChangeDashboardStretchToFitAction } from './changeStretchToFit';
import { initialState } from '../../state';

it('can toggle the dashboard to stretch to fit', () => {
expect(
changeDashboardStretchToFit(
initialState,
onChangeDashboardStretchToFitAction({
stretchToFit: false,
})
).grid.stretchToFit
).toEqual(false);

expect(
changeDashboardStretchToFit(
initialState,
onChangeDashboardStretchToFitAction({
stretchToFit: true,
})
).grid.stretchToFit
).toEqual(true);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { changeGridProperty } from './updateGrid';
import type { Action } from 'redux';
import type { DashboardState } from '../../state';

type ChangeDashboardStretchToFitActionPayload = {
stretchToFit: boolean;
};
export interface ChangeDashboardStretchToFitAction extends Action {
type: 'CHANGE_STRETCH_TO_FIT';
payload: ChangeDashboardStretchToFitActionPayload;
}

export const onChangeDashboardStretchToFitAction = (
payload: ChangeDashboardStretchToFitActionPayload
): ChangeDashboardStretchToFitAction => ({
type: 'CHANGE_STRETCH_TO_FIT',
payload,
});

export const changeDashboardStretchToFit = (
state: DashboardState,
action: ChangeDashboardStretchToFitAction
): DashboardState => changeGridProperty(state, 'stretchToFit', action.payload.stretchToFit);
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './changeHeight';
export * from './changeWidth';
export * from './changeEnabled';
export * from './changeCellSize';
export * from './changeStretchToFit';
2 changes: 2 additions & 0 deletions packages/dashboard/src/store/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
ChangeDashboardGridEnabledAction,
ChangeDashboardHeightAction,
ChangeDashboardWidthAction,
ChangeDashboardStretchToFitAction,
} from './changeDashboardGrid';
import type { DeleteWidgetsAction } from './deleteWidgets';
import type { CopyWidgetsAction } from './copyWidgets';
Expand Down Expand Up @@ -47,4 +48,5 @@ export type DashboardAction =
| ChangeDashboardHeightAction
| ChangeDashboardCellSizeAction
| ChangeDashboardGridEnabledAction
| ChangeDashboardStretchToFitAction
| UpdateViewportAction;
5 changes: 5 additions & 0 deletions packages/dashboard/src/store/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
changeDashboardCellSize,
changeDashboardGridDragEnabled,
changeDashboardHeight,
changeDashboardStretchToFit,
changeDashboardWidth,
copyWidgets,
deleteWidgets,
Expand Down Expand Up @@ -39,6 +40,10 @@ export const dashboardReducer: Reducer<DashboardState, DashboardAction> = (
return changeDashboardCellSize(state, action);
}

case 'CHANGE_STRETCH_TO_FIT': {
return changeDashboardStretchToFit(state, action);
}

case 'CHANGE_ENABLED': {
return changeDashboardGridDragEnabled(state, action);
}
Expand Down

0 comments on commit f5ca885

Please sign in to comment.