Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions components-sdk/src/Capsule.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,24 @@
color: #dcddde;
background: #36393f;
font-family: "gg sans",Whitney,"Noto Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
overflow-wrap: anywhere;

font-size: 1.6rem;
line-height: 2.2rem;
letter-spacing: normal;
font-weight: 400;
position: relative;

&.modal {
background: #242429;
.header {
font-size: 2.4rem;
color: white;
font-weight: bold;
margin-top: 1rem;
margin-bottom: 24px;
}
}
}

.component {
Expand Down
164 changes: 118 additions & 46 deletions components-sdk/src/Capsule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { StringSelect } from './components/StringSelect';
import { File } from './components/File';
import { CapsuleInner } from './CapsuleInner';
import { generateRandomAnimal, randomSentence, uuidv4 } from './utils/randomGen';
import { addKeyType, appendKeyType, deleteKeyType, stateKeyType, StateManager } from './polyfills/StateManager';
import { stateKeyType, StateManager } from './polyfills/StateManager';
import {
ActionRowComponent,
ButtonComponent,
Expand All @@ -23,22 +23,33 @@ import {
MediaGalleryComponent,
MediaGalleryItem,
PassProps,
RenderMode,
SeparatorComponent,
SeparatorSpacingSize,
StringSelectComponent,
TextDisplayComponent,
ThumbnailComponent,
} from './utils/componentTypes';
import {
FileUploadComponent,
LabelComponent,
ModalStringSelectComponent,
TextInputComponent,
TextInputStyle,
} from './utils/componentTypesModal';

import { DragContextProvider } from './dnd/DragContext';
import { DroppableID } from './dnd/components';
import { KeyToDeleteType } from './dnd/types';
import { BoundariesProps } from './dnd/boundaries';
import { RegenerateContextProvider } from './utils/useRegenerate';
import { useMemo } from 'react';
import { Label } from './components/Label';
import { useTranslation } from 'react-i18next';
import { TextInput } from './components/TextInput';
import { FileUpload } from './components/FileUpload';

const _Button = {
type: 2,
type: ComponentType.BUTTON,
style: ButtonStyle.GREY,
label: '',
emoji: null,
Expand All @@ -54,7 +65,7 @@ const _Image = {
} as MediaGalleryItem

const _StringSelect = () => ({
type: 3,
type: ComponentType.STRING_SELECT,
custom_id: uuidv4(),
options: [
{
Expand All @@ -71,70 +82,119 @@ const _StringSelect = () => ({
disabled: false,
} as StringSelectComponent)

const _TextInput = (style: TextInputStyle) => ({
type: ComponentType.TEXT_INPUT,
custom_id: uuidv4(),
style,
min_length: null,
max_length: null,
required: true,
value: null,
placeholder: null,
});

// We want this app to create components with all properties (except 'id') defined
type Req<T> = Required<Omit<T, 'id'>>;

export const default_settings = {
Button: () => ({
type: 1,
type: ComponentType.ACTION_ROW,
components: [{
..._Button,
custom_id: uuidv4(),
label: generateRandomAnimal()
label: generateRandomAnimal(),
}]
}),
LinkButton: () => ({
type: 1,
type: ComponentType.ACTION_ROW,
components: [{
..._Button,
style: ButtonStyle.URL,
url: 'https://google.com',
label: generateRandomAnimal()
label: generateRandomAnimal(),
}]
}),
StringSelect: () => ({
type: 1,
components: [_StringSelect()]
type: ComponentType.ACTION_ROW,
components: [_StringSelect()],
}),
TextDisplay: () => ({
type: 10,
type: ComponentType.TEXT_DISPLAY,
content: randomSentence(),
}),
Thumbnail: {
type: 11,
..._Image
type: ComponentType.THUMBNAIL,
..._Image,
},
MediaGallery: {
type: 12,
items: [
_Image
]
type: ComponentType.MEDIA_GALLERY,
items: [_Image],
},
File: {
type: 13,
type: ComponentType.FILE,
file: {
url: ''
},
spoiler: false,
},
Separator: {
type: 14,
type: ComponentType.SEPARATOR,
divider: true,
spacing: SeparatorSpacingSize.SMALL
},
Container: {
type: 17,
type: ComponentType.CONTAINER,
accent_color: null,
spoiler: false,
components: [],
},
ModalShortInput: () => ({
type: ComponentType.LABEL,
label: generateRandomAnimal(),
description: null,
component: _TextInput(TextInputStyle.SHORT),
}),
ModalParagraphInput: () => ({
type: ComponentType.LABEL,
label: generateRandomAnimal(),
description: null,
component: _TextInput(TextInputStyle.PARAGRAPH),
}),
ModalStringSelect: () => {
const {disabled, ...other} = _StringSelect();
return {
type: ComponentType.LABEL,
label: generateRandomAnimal(),
description: null,
component: {...other, required: true},
};
},
ModalFileUpload: () => ({
type: ComponentType.LABEL,
label: generateRandomAnimal(),
description: null,
component: {
type: ComponentType.FILE_UPLOAD,
custom_id: uuidv4(),
min_values: 1,
max_values: 1,
required: true,
},
}),
} as {
Button: () => ActionRowComponent<ButtonComponent>,
LinkButton: () => ActionRowComponent<ButtonComponent>,
StringSelect: () => ActionRowComponent<StringSelectComponent>,
TextDisplay: () => TextDisplayComponent,
Thumbnail: ThumbnailComponent,
MediaGallery: MediaGalleryComponent,
Separator: SeparatorComponent,
Container:ContainerComponent,
File: FileComponent
Button: () => Req<ActionRowComponent<Req<ButtonComponent>>>;
LinkButton: () => Req<ActionRowComponent<Req<ButtonComponent>>>;
StringSelect: () => Req<ActionRowComponent<Req<StringSelectComponent>>>;
TextDisplay: () => Req<TextDisplayComponent>;
Thumbnail: Req<ThumbnailComponent>;
MediaGallery: Req<MediaGalleryComponent>;
Separator: Req<SeparatorComponent>;
Container: Req<ContainerComponent>;
File: Req<FileComponent>;
ModalShortInput: () => Req<LabelComponent<Req<TextInputComponent>>>;
ModalParagraphInput: () => Req<LabelComponent<Req<TextInputComponent>>>;
ModalStringSelect: () => Req<LabelComponent<Req<ModalStringSelectComponent>>>;
ModalFileUpload: () => Req<LabelComponent<Req<FileUploadComponent>>>;
}

export type ComponentsProps = {
Expand All @@ -145,23 +205,27 @@ export type ComponentsProps = {
removeKeyParent?: stateKeyType,
dragKeyToDeleteOverwrite?: Omit<KeyToDeleteType, 'sessionId'>, // Available only for Section accessory
droppableId?: DroppableID, // Available only for Section accessory
fromLabel?: boolean // Available only for Label component
errors?: Record<string, any> | null,
actionCallback?: (custom_id: string | null) => void,
}

export const COMPONENTS = {
1: ActionRow,
2: Button,
3: StringSelect,
9: Section,
10: TextDisplay,
11: Thumbnail,
12: MediaGallery,
14: Separator,
17: Container,
13: File,
} as unknown as {
[K: number]: (props: ComponentsProps) => JSX.Element
export const COMPONENTS: {
[K in ComponentType]: (props: Omit<ComponentsProps, 'state'> & { state: any }) => JSX.Element | null;
} = {
[ComponentType.ACTION_ROW]: ActionRow,
[ComponentType.BUTTON]: Button,
[ComponentType.STRING_SELECT]: StringSelect,
[ComponentType.TEXT_INPUT]: TextInput,
[ComponentType.SECTION]: Section,
[ComponentType.TEXT_DISPLAY]: TextDisplay,
[ComponentType.THUMBNAIL]: Thumbnail,
[ComponentType.MEDIA_GALLERY]: MediaGallery,
[ComponentType.SEPARATOR]: Separator,
[ComponentType.CONTAINER]: Container,
[ComponentType.FILE]: File,
[ComponentType.LABEL]: Label,
[ComponentType.FILE_UPLOAD]: FileUpload,
}

export const SECTIONABLE = [
Expand All @@ -175,19 +239,27 @@ export function Capsule(props : {
className?: string | null,
passProps: PassProps,
errors: Record<string, any> | null,
modalTitle?: string
} & BoundariesProps ) {
const isModal = props.passProps.renderMode === RenderMode.MODAL;

const cls = props.className ? ' ' + props.className : '';
const modCls = isModal ? ' ' + Styles.modal : '';

return <div className={Styles.preview + cls}>
<RegenerateContextProvider stateManager={props.stateManager}>{stateMng => <DragContextProvider stateManager={stateMng} boundaries={props.boundaries}>
return <div className={Styles.preview + modCls + cls}>
{!!(isModal && props.modalTitle) && <div className={Styles.header}>
{props.modalTitle}
</div>}
<RegenerateContextProvider stateManager={props.stateManager}>{stateMng => <DragContextProvider stateManager={stateMng} boundaries={props.boundaries} renderMode={props.passProps.renderMode ?? RenderMode.MESSAGE}>
<CapsuleInner
state={props.state}
stateKey={props.stateKey}
stateManager={stateMng}
buttonContext={'main'}
buttonContext={isModal ? 'modal' : 'main'}
passProps={props.passProps}
droppableId={DroppableID.TOP_LEVEL}
droppableId={isModal ? DroppableID.MODAL_TOP_LEVEL : DroppableID.TOP_LEVEL}
errors={props.errors}
showSectionButton={!isModal}
/>
</DragContextProvider>}</RegenerateContextProvider>
</div>
Expand Down
36 changes: 35 additions & 1 deletion components-sdk/src/CapsuleButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ import SeparatorIcon from './icons/Separator.svg';
import ButtonIcon from './icons/Button.svg';
import LinkButtonIcon from './icons/ButtonLink.svg';
import SelectIcon from './icons/Select.svg';
import UploadFile from './icons/UploadFile.svg';
import Input from './icons/Input.svg';
import Textarea from './icons/Textarea.svg';
import { default_settings } from './Capsule';
import { CSSProperties, useRef } from 'react';
import { Component } from './utils/componentTypes';
import { useStateOpen } from './utils/useStateOpen';
import { useTranslation } from 'react-i18next';

export type capsuleButtonCtx = 'main' | 'container' | 'inline' | 'button-row' | 'frame';
export type capsuleButtonCtx = 'main' | 'container' | 'inline' | 'button-row' | 'frame' | 'modal';

type props = {
context: capsuleButtonCtx,
Expand All @@ -39,6 +42,7 @@ export function CapsuleButton({context, callback, className, style, interactiveD
{context === "inline" && t('button.add-inline')}
{context === "container" && t('button.add-content')}
{context === "button-row" && t('button.add-button')}
{context === "modal" && t('button.add-input')}

{ open && <div className={Styles.large_button_ctx} ref={btn_select}>
{['main', 'inline', 'container'].includes(context) && <div className={Styles.large_button_ctx_item} onClick={() => {
Expand Down Expand Up @@ -95,6 +99,36 @@ export function CapsuleButton({context, callback, className, style, interactiveD
<div className={Styles.large_button_ctx_item_img}><img src={MediaGalleryIcon} alt=""/></div>
<div className={Styles.large_button_ctx_item_text}>{t('components.thumbnail')}</div>
</div>}
{['modal'].includes(context) && <div className={Styles.large_button_ctx_item} onClick={() => {
callback(default_settings.ModalShortInput())
}}>
<div className={Styles.large_button_ctx_item_img}><img src={Input} alt=""/></div>
<div className={Styles.large_button_ctx_item_text}>{t('components.modal.short-input')}</div>
</div>}
{['modal'].includes(context) && <div className={Styles.large_button_ctx_item} onClick={() => {
callback(default_settings.ModalParagraphInput())
}}>
<div className={Styles.large_button_ctx_item_img}><img src={Textarea} alt=""/></div>
<div className={Styles.large_button_ctx_item_text}>{t('components.modal.paragraph-input')}</div>
</div>}
{['modal'].includes(context) && <div className={Styles.large_button_ctx_item} onClick={() => {
callback(default_settings.TextDisplay())
}}>
<div className={Styles.large_button_ctx_item_img}><img src={TextDisplayIcon} alt=""/></div>
<div className={Styles.large_button_ctx_item_text}>{t('components.modal.content')}</div>
</div>}
{['modal'].includes(context) && <div className={Styles.large_button_ctx_item} onClick={() => {
callback(default_settings.ModalStringSelect())
}}>
<div className={Styles.large_button_ctx_item_img}><img src={SelectIcon} alt=""/></div>
<div className={Styles.large_button_ctx_item_text}>{t('components.modal.string-select')}</div>
</div>}
{['modal'].includes(context) && <div className={Styles.large_button_ctx_item} onClick={() => {
callback(default_settings.ModalFileUpload())
}}>
<div className={Styles.large_button_ctx_item_img}><img src={UploadFile} alt=""/></div>
<div className={Styles.large_button_ctx_item_text}>{t('components.modal.file-upload')}</div>
</div>}
</div>}
</div>
)
Expand Down
7 changes: 6 additions & 1 deletion components-sdk/src/components/Button.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,16 @@
border: 1px solid rgba(255,255,255, 0.1);
}

.input[type="text"] {
.input[type="text"], .input[type="number"] {
padding: 0.8rem 1.6rem;
width: 200px;
}

textarea.textarea {
padding: 0.8rem 1.6rem;
width: 500px;
}

.link_btn {
line-height: 100%;
.text {
Expand Down
Loading