feat: Adding new UIKit components (#29566)

Co-authored-by: Tiago Evangelista Pinto <17487063+tiagoevanp@users.noreply.github.com>
pull/29893/head
csuadev 3 years ago committed by GitHub
parent 5f7f27547f
commit f9a748526d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      .changeset/loud-sheep-try.md
  2. 20
      packages/fuselage-ui-kit/src/blocks/CalloutBlock.tsx
  3. 4
      packages/fuselage-ui-kit/src/contexts/kitContext.ts
  4. 45
      packages/fuselage-ui-kit/src/elements/CheckboxElement.tsx
  5. 43
      packages/fuselage-ui-kit/src/elements/RadioButtonElement.tsx
  6. 38
      packages/fuselage-ui-kit/src/elements/TimePickerElement.tsx
  7. 45
      packages/fuselage-ui-kit/src/elements/ToggleSwitchElement.tsx
  8. 33
      packages/fuselage-ui-kit/src/hooks/useUiKitState.ts
  9. 1
      packages/fuselage-ui-kit/src/surfaces/FuselageContextualBarRenderer.tsx
  10. 14
      packages/fuselage-ui-kit/src/surfaces/FuselageModalSurfaceRenderer.tsx
  11. 105
      packages/fuselage-ui-kit/src/surfaces/FuselageSurfaceRenderer.tsx
  12. 2
      packages/uikit-playground/src/Components/Preview/Display/RenderPayload/RenderPayload.tsx
  13. 30
      packages/uikit-playground/src/Payload/BlocksTree.ts
  14. 40
      packages/uikit-playground/src/Payload/action/checkbox.ts
  15. 8
      packages/uikit-playground/src/Payload/action/index.ts
  16. 38
      packages/uikit-playground/src/Payload/action/radioButton.ts
  17. 21
      packages/uikit-playground/src/Payload/action/timePicker.ts
  18. 40
      packages/uikit-playground/src/Payload/action/toggleSwitch.ts
  19. 16
      packages/uikit-playground/src/Payload/callout/index.ts
  20. 6
      yarn.lock

@ -0,0 +1,6 @@
---
"@rocket.chat/fuselage-ui-kit": minor
"@rocket.chat/uikit-playground": minor
---
feat: Adding new UIKit components: Callout, Checkbox, Radio Button, Time Picker, Toast Bar, Toggle Switch, Tab Navigation

@ -0,0 +1,20 @@
import { Callout } from '@rocket.chat/fuselage';
import * as UiKit from '@rocket.chat/ui-kit';
import type { ReactElement } from 'react';
import type { BlockProps } from '../utils/BlockProps';
type CalloutBlockProps = BlockProps<UiKit.CalloutBlock>;
const CalloutBlock = ({
block,
surfaceRenderer,
}: CalloutBlockProps): ReactElement => {
return (
<Callout type={block.variant} icon={block.icon} title={block.title?.text}>
{surfaceRenderer.renderTextObject(block.text, 0, UiKit.BlockContext.NONE)}
</Callout>
);
};
export default CalloutBlock;

@ -38,7 +38,9 @@ export const kitContext = createContext<UiKitContext>(defaultContext);
export const useUiKitContext = () => useContext(kitContext);
export const useUiKitStateValue = <T extends string | number | undefined>(
export const useUiKitStateValue = <
T extends string | string[] | number | undefined
>(
actionId: string,
initialValue: T
): {

@ -0,0 +1,45 @@
import { CheckBox, Box } from '@rocket.chat/fuselage';
import * as UiKit from '@rocket.chat/ui-kit';
import type { ReactElement } from 'react';
import { useUiKitState } from '../hooks/useUiKitState';
import type { BlockProps } from '../utils/BlockProps';
type CheckboxElementProps = BlockProps<UiKit.CheckboxElement>;
const CheckboxElement = ({
block,
context,
surfaceRenderer,
}: CheckboxElementProps): ReactElement => {
const [{ loading, value }, action] = useUiKitState(block, context);
const { options } = block;
return (
<Box>
{options.map((option: UiKit.Option) => {
const isChecked = value?.includes(option.value);
return (
<Box key={option.value} pb='x4'>
<CheckBox
disabled={loading}
value={option.value}
checked={isChecked}
onChange={action}
/>
<Box is='label' pis='x8'>
{surfaceRenderer.renderTextObject(
option.text,
0,
UiKit.BlockContext.NONE
)}
</Box>
</Box>
);
})}
</Box>
);
};
export default CheckboxElement;

@ -0,0 +1,43 @@
import { Box, RadioButton } from '@rocket.chat/fuselage';
import * as UiKit from '@rocket.chat/ui-kit';
import type { ReactElement } from 'react';
import { useUiKitState } from '../hooks/useUiKitState';
import type { BlockProps } from '../utils/BlockProps';
type RadioButtonElementProps = BlockProps<UiKit.RadioButtonElement>;
const RadioButtonElement = ({
block,
context,
surfaceRenderer,
}: RadioButtonElementProps): ReactElement => {
const [{ loading, value }, action] = useUiKitState(block, context);
const { options } = block;
return (
<Box>
{options.map((option: UiKit.Option) => {
return (
<Box key={option.value} pb='x4'>
<RadioButton
disabled={loading}
checked={value === option.value}
value={option.value}
onChange={action}
/>
<Box is='label' pis='x8'>
{surfaceRenderer.renderTextObject(
option.text,
0,
UiKit.BlockContext.NONE
)}
</Box>
</Box>
);
})}
</Box>
);
};
export default RadioButtonElement;

@ -0,0 +1,38 @@
import { InputBox } from '@rocket.chat/fuselage';
import type * as UiKit from '@rocket.chat/ui-kit';
import type { ReactElement } from 'react';
import { useUiKitState } from '../hooks/useUiKitState';
import type { BlockProps } from '../utils/BlockProps';
import { fromTextObjectToString } from '../utils/fromTextObjectToString';
type TimePickerElementProps = BlockProps<UiKit.TimePickerElement>;
const TimePickerElement = ({
block,
context,
surfaceRenderer,
}: TimePickerElementProps): ReactElement => {
const [{ loading, value, error }, action] = useUiKitState(block, context);
const { actionId, placeholder } = block;
return (
<InputBox
type='time'
error={error}
value={value}
disabled={loading}
id={actionId}
name={actionId}
rows={6}
placeholder={
placeholder
? fromTextObjectToString(surfaceRenderer, placeholder, 0)
: undefined
}
onInput={action}
/>
);
};
export default TimePickerElement;

@ -0,0 +1,45 @@
import { Box, ToggleSwitch } from '@rocket.chat/fuselage';
import * as UiKit from '@rocket.chat/ui-kit';
import { type ReactElement } from 'react';
import { useUiKitState } from '../hooks/useUiKitState';
import type { BlockProps } from '../utils/BlockProps';
type ToggleSwitchElementProps = BlockProps<UiKit.ToggleSwitchElement>;
const ToggleSwitchElement = ({
block,
context,
surfaceRenderer,
}: ToggleSwitchElementProps): ReactElement => {
const [{ value, loading }, action] = useUiKitState(block, context);
const { options } = block;
return (
<Box>
{options.map((option: UiKit.Option) => {
const isChecked = value.includes(option.value);
return (
<Box key={option.value} pb='x4'>
<ToggleSwitch
disabled={loading}
value={option.value}
checked={isChecked}
onChange={action}
/>
<Box is='label' pis='x8'>
{surfaceRenderer.renderTextObject(
option.text,
0,
UiKit.BlockContext.NONE
)}
</Box>
</Box>
);
})}
</Box>
);
};
export default ToggleSwitchElement;

@ -18,11 +18,24 @@ const hasInitialValue = <TElement extends UiKit.ActionableElement>(
): element is TElement & { initialValue: number | string } =>
'initialValue' in element;
const hasInitialTime = <TElement extends UiKit.ActionableElement>(
element: TElement
): element is TElement & { initialTime: string } => 'initialTime' in element;
const hasInitialDate = <TElement extends UiKit.ActionableElement>(
element: TElement
): element is TElement & { initialDate: string } => 'initialDate' in element;
const hasInitialOption = <TElement extends UiKit.ActionableElement>(
element: TElement
): element is TElement & { initialOption: UiKit.Option } =>
'initialOption' in element;
const hasInitialOptions = <TElement extends UiKit.ActionableElement>(
element: TElement
): element is TElement & { initialOptions: UiKit.Option[] } =>
'initialOptions' in element;
export const useUiKitState: <TElement extends UiKit.ActionableElement>(
element: TElement,
context: UiKit.BlockContext
@ -45,7 +58,11 @@ export const useUiKitState: <TElement extends UiKit.ActionableElement>(
const initialValue =
(hasInitialValue(rest) && rest.initialValue) ||
(hasInitialTime(rest) && rest.initialTime) ||
(hasInitialDate(rest) && rest.initialDate) ||
(hasInitialOption(rest) && rest.initialOption.value) ||
(hasInitialOptions(rest) &&
rest.initialOptions.map((option) => option.value)) ||
undefined;
const { value: _value, error } = useUiKitStateValue(actionId, initialValue);
@ -54,10 +71,22 @@ export const useUiKitState: <TElement extends UiKit.ActionableElement>(
const actionFunction = useMutableCallback(async (e) => {
const {
target: { value },
target: { value: elValue },
} = e;
setLoading(true);
setValue(value);
if (Array.isArray(value)) {
const idx = value.findIndex((value) => value === elValue);
if (idx > -1) {
setValue(value.filter((_, i) => i !== idx));
} else {
setValue([...value, elValue]);
}
} else {
setValue(elValue);
}
state && (await state({ blockId, appId, actionId, value, viewId }, e));
await action(
{

@ -10,6 +10,7 @@ export class FuselageContextualBarSurfaceRenderer extends FuselageSurfaceRendere
'input',
'section',
'preview',
'callout',
]);
}
}

@ -1,8 +1,16 @@
import type { FuselageSurfaceRendererProps } from './FuselageSurfaceRenderer';
import { FuselageSurfaceRenderer } from './FuselageSurfaceRenderer';
export class FuselageModalSurfaceRenderer extends FuselageSurfaceRenderer {
public constructor(allowedBlocks?: FuselageSurfaceRendererProps) {
super(allowedBlocks);
public constructor() {
super([
'actions',
'context',
'divider',
'image',
'input',
'section',
'preview',
'callout',
]);
}
}

@ -16,6 +16,11 @@ import MultiStaticSelectElement from '../elements/MultiStaticSelectElement';
import OverflowElement from '../elements/OverflowElement';
import PlainTextInputElement from '../elements/PlainTextInputElement';
import StaticSelectElement from '../elements/StaticSelectElement';
import ToggleSwitchElement from '../elements/ToggleSwitchElement';
import RadioButtonElement from '../elements/RadioButtonElement';
import CheckboxElement from '../elements/CheckboxElement';
import CalloutBlock from '../blocks/CalloutBlock';
import TimePickerElement from '../elements/TimePickerElement';
import MarkdownTextElement from '../elements/MarkdownTextElement';
import PlainTextElement from '../elements/PlainTextElement';
@ -360,4 +365,104 @@ export class FuselageSurfaceRenderer extends UiKit.SurfaceRenderer<ReactElement>
/>
);
}
toggle_switch(
block: UiKit.ToggleSwitchElement,
context: UiKit.BlockContext,
index: number
): ReactElement | null {
if (context === UiKit.BlockContext.BLOCK) {
return null;
}
return (
<ToggleSwitchElement
key={block.actionId || index}
block={block}
context={context}
index={index}
surfaceRenderer={this}
/>
);
}
radio_button(
block: UiKit.RadioButtonElement,
context: UiKit.BlockContext,
index: number
): ReactElement | null {
if (context === UiKit.BlockContext.BLOCK) {
return null;
}
return (
<RadioButtonElement
key={block.actionId || index}
block={block}
context={context}
index={index}
surfaceRenderer={this}
/>
);
}
checkbox(
block: UiKit.CheckboxElement,
context: UiKit.BlockContext,
index: number
): ReactElement | null {
if (context === UiKit.BlockContext.BLOCK) {
return null;
}
return (
<CheckboxElement
key={block.actionId || index}
block={block}
context={context}
index={index}
surfaceRenderer={this}
/>
);
}
callout(
block: UiKit.CalloutBlock,
context: UiKit.BlockContext,
index: number
): ReactElement | null {
if (context === UiKit.BlockContext.BLOCK) {
return (
<CalloutBlock
key={index}
block={block}
context={context}
index={index}
surfaceRenderer={this}
/>
);
}
return null;
}
time_picker(
block: UiKit.TimePickerElement,
context: UiKit.BlockContext,
index: number
): ReactElement | null {
if (context === UiKit.BlockContext.BLOCK) {
return null;
}
return (
<TimePickerElement
key={block.actionId || index}
block={block}
context={context}
index={index}
surfaceRenderer={this}
/>
);
}
}

@ -3,6 +3,7 @@ import {
UiKitModal as uiKitModal,
UiKitBanner as uiKitBanner,
UiKitMessage as uiKitMessage,
UiKitContextualBar as uiKitContextualBar,
} from '@rocket.chat/fuselage-ui-kit';
import type { LayoutBlock } from '@rocket.chat/ui-kit';
@ -22,6 +23,7 @@ const RenderPayload = ({
'1': () => uiKitMessage(payload),
'2': () => uiKitBanner(payload),
'3': () => uiKitModal(payload),
'4': () => uiKitContextualBar(payload)
};
return (

@ -16,6 +16,10 @@ import {
actionWithMultiStaticSelect,
actionWithDatePicker,
actionWithLinearScale,
actionWithToggleSwitch,
actionWithRadioButton,
actionWithCheckbox,
actionWithTimePicker,
} from './action';
import {
contextWithPlainText,
@ -24,6 +28,7 @@ import {
contextWithAllElements,
} from './context';
import { divider } from './divider';
import { callout } from './callout';
import { imageWithTitle, imageWithoutTitle } from './image';
import {
inputWithSingleLineInput,
@ -118,10 +123,26 @@ const BlocksTree: Item = [
label: 'date Picker',
payload: actionWithDatePicker,
},
{
label: 'time Picker',
payload: actionWithTimePicker,
},
{
label: 'linear scale',
payload: actionWithLinearScale,
},
{
label: 'toggle switch',
payload: actionWithToggleSwitch,
},
{
label: 'radio buttons',
payload: actionWithRadioButton,
},
{
label: 'checkbox',
payload: actionWithCheckbox,
},
],
},
{
@ -298,6 +319,15 @@ const BlocksTree: Item = [
},
],
},
{
label: 'callout',
branches: [
{
label: 'Plain',
payload: callout,
},
],
},
];
export default BlocksTree;

@ -0,0 +1,40 @@
import type { LayoutBlock } from '@rocket.chat/ui-kit';
export const actionWithCheckbox: readonly LayoutBlock[] = [
{
type: 'actions',
elements: [
{
type: 'checkbox',
appId: 'app-id',
blockId: 'block-id',
actionId: 'action-id',
options: [
{
text: {
type: 'plain_text',
text: 'Option 1',
},
value: 'value-1',
},
{
text: {
type: 'plain_text',
text: 'Option initial',
},
value: 'value-2',
},
],
initialOptions: [
{
text: {
type: 'plain_text',
text: 'Option initial',
},
value: 'value-2',
},
],
},
],
},
];

@ -11,3 +11,11 @@ export * from './menu';
export * from './datePicker';
export * from './linearScale';
export * from './toggleSwitch';
export * from './radioButton';
export * from './checkbox';
export * from './timePicker';

@ -0,0 +1,38 @@
import type { LayoutBlock } from '@rocket.chat/ui-kit';
export const actionWithRadioButton: readonly LayoutBlock[] = [
{
type: 'actions',
elements: [
{
type: 'radio_button',
appId: 'app-id',
blockId: 'block-id',
actionId: 'action-id',
options: [
{
text: {
type: 'plain_text',
text: 'Option 1',
},
value: 'value-1',
},
{
text: {
type: 'plain_text',
text: 'Option initial',
},
value: 'value-2',
},
],
initialOption: {
text: {
type: 'plain_text',
text: 'Option initial',
},
value: 'value-2',
},
},
],
},
];

@ -0,0 +1,21 @@
import type { LayoutBlock } from '@rocket.chat/ui-kit';
export const actionWithTimePicker: readonly LayoutBlock[] = [
{
type: 'actions',
elements: [
{
type: 'time_picker',
initialTime: '10:30',
appId: 'app-id',
blockId: 'block-id',
actionId: 'action-id',
placeholder: {
type: 'plain_text',
text: 'Select a time',
emoji: true,
},
},
],
},
];

@ -0,0 +1,40 @@
import type { LayoutBlock } from '@rocket.chat/ui-kit';
export const actionWithToggleSwitch: readonly LayoutBlock[] = [
{
type: 'actions',
elements: [
{
type: 'toggle_switch',
appId: 'app-id',
blockId: 'block-id',
actionId: 'action-id',
options: [
{
text: {
type: 'plain_text',
text: 'Toggle 1',
},
value: 'value-1',
},
{
text: {
type: 'plain_text',
text: 'Toggle initial option',
},
value: 'value-2',
},
],
initialOptions: [
{
text: {
type: 'plain_text',
text: 'Toggle initial option',
},
value: 'value-2',
},
],
},
],
},
];

@ -0,0 +1,16 @@
import type { LayoutBlock } from '@rocket.chat/ui-kit';
export const callout: readonly LayoutBlock[] = [
{
type: 'callout',
title: {
type: 'plain_text',
text: 'Callout Title',
},
text: {
type: 'plain_text',
text: 'Callout Text',
},
icon: 'rocket',
},
];

@ -8806,11 +8806,11 @@ __metadata:
linkType: soft
"@rocket.chat/ui-kit@npm:next":
version: 0.32.0-dev.321
resolution: "@rocket.chat/ui-kit@npm:0.32.0-dev.321"
version: 0.32.0-dev.330
resolution: "@rocket.chat/ui-kit@npm:0.32.0-dev.330"
peerDependencies:
"@rocket.chat/icons": "*"
checksum: 9e237ac9c3553455d2167ef945070fcdfa90c3403599cc0e6a0c0de6d5663851ce29f4cfc9e0b6809a711620431a2a9f69398041ddffcbc41fe99a90aa1ae483
checksum: f43cadeccf12770e9a8f67976deda4d2ebe5ee7af845ef78fb4c5a4bbf046ba15008e8a73a2de56bb5b16138a4722b6f0d02a003d6c6a0219c561c906b58eeb7
languageName: node
linkType: hard

Loading…
Cancel
Save