diff --git a/player/react/src/lib/components/index.ts b/player/react/src/lib/components/index.ts index a94397b61..4b6305640 100644 --- a/player/react/src/lib/components/index.ts +++ b/player/react/src/lib/components/index.ts @@ -17,6 +17,7 @@ import * as progressIndicator from './ui-progress-indicator'; import * as row from './ui-row'; import * as tag from './ui-tag'; import * as searchinput from './ui-search-input'; +import * as tabs from './ui-tabs'; import * as text from './ui-text'; import * as textarea from './ui-text-area'; import * as textinput from './ui-text-input'; @@ -56,6 +57,7 @@ export const allComponents = { row, searchinput, tag, + tabs, text, textarea, textinput, diff --git a/player/react/src/lib/components/ui-tabs.tsx b/player/react/src/lib/components/ui-tabs.tsx new file mode 100644 index 000000000..799835ea3 --- /dev/null +++ b/player/react/src/lib/components/ui-tabs.tsx @@ -0,0 +1,122 @@ +import React from 'react'; +import { Tabs, Tab, TabList, TabPanels, TabPanel } from '@carbon/react'; +import { CssClasses, SendSignal } from '../types'; +import { renderComponents, setItemInState } from '../utils'; +import { cx } from 'emotion'; +import { commonSlots, slotsDisabled } from '../common-slots'; + +export interface TabsState { + type: string; + id: string | number; + tabType: string; + isFollowFocused: boolean; + isCacheActive: boolean; + isNavigation: boolean; + items?: []; + selectedTab: number; + cssClasses?: CssClasses[]; + codeContext?: { + name: string; + }; +} + +export interface TabState { + type: string; + id?: string | number; + disabled?: boolean; + label: string; + items?: any[]; + cssClasses?: CssClasses[]; + codeContext: { + name?: string; + }; +} + +export const type = 'tabs'; + +export const signals = ['click']; + +export const slots = { + ...commonSlots, + ...slotsDisabled, + type: 'string', + isFollowFocused: 'boolean', + followFocus: (state: TabsState) => ({ + ...state, + isFollowFocused: true + }), + deFollowFocus: (state: TabsState) => ({ + ...state, + isFollowFocused: false + }), + toggleFollowFocus: (state: TabsState) => ({ + ...state, + isFollowFocused: !state.isFollowFocused + }), + isCacheActive: 'boolean', + cacheActive: (state: TabsState) => ({ + ...state, + isCacheActive: true + }), + deCacheActive: (state: TabsState) => ({ + ...state, + isCacheActive: false + }), + toggleCacheActive: (state: TabsState) => ({ + ...state, + isCacheActive: !state.isCacheActive + }), + isNavigation: 'boolean', + navigation: (state: TabsState) => ({ + ...state, + isNavigation: true + }), + deNavigation: (state: TabsState) => ({ + ...state, + isNavigation: false + }), + toggleNavigation: (state: TabsState) => ({ + ...state, + isNavigation: !state.isNavigation + }) +}; + +export const UITabs = ({ state, setState, setGlobalState, sendSignal }: { + state: TabsState; + setState: (state: any) => void; + setGlobalState: (state: any) => void; + sendSignal: SendSignal; +}) => { + if (state.type !== 'tabs') { + // eslint-disable-next-line react/jsx-no-useless-fragment + return <>; + } + return + + { + state.items?.map((step: any, index: any) => cc.id).join(' '))} + onClick={(i: any) => state.selectedTab = i} + key= {index} + disabled={step.disabled}> + {step.labelText} + ) + } + + + { + state.items?.map((step: any, index: any) => { + const setTabItem = (i: any) => setItemInState(i, step, setState); + return + { + step.items?.map((element: any) => { + const setItem = (j: any) => setItemInState(j, element, setTabItem); + return renderComponents(element, setItem, setGlobalState, sendSignal); + }) + } + ; + }) + } + + ; +}; diff --git a/player/react/src/lib/utils.tsx b/player/react/src/lib/utils.tsx index 7c335518a..46f5998ee 100644 --- a/player/react/src/lib/utils.tsx +++ b/player/react/src/lib/utils.tsx @@ -35,6 +35,7 @@ import { UITile } from './components/ui-tile'; import { UITileFold } from './components/ui-tile-fold'; import { UIToggle } from './components/ui-toggle'; import { kebabCase } from 'lodash'; +import { UITabs } from './components/ui-tabs'; import { SendSignal } from './types'; export const setItemInState = (item: any, state: any, setState: (state: any) => void) => { @@ -313,6 +314,8 @@ export const renderComponents = ( case 'radio-tile-group': return ; + case 'tabs': + return ; default: break; } diff --git a/sdk/react/src/lib/assets/component-icons/tabs.svg b/sdk/react/src/lib/assets/component-icons/tabs.svg new file mode 100644 index 000000000..3fa79b06d --- /dev/null +++ b/sdk/react/src/lib/assets/component-icons/tabs.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + Tab + + + + diff --git a/sdk/react/src/lib/fragment-components/a-tabs.tsx b/sdk/react/src/lib/fragment-components/a-tabs.tsx new file mode 100644 index 000000000..604c82a7a --- /dev/null +++ b/sdk/react/src/lib/fragment-components/a-tabs.tsx @@ -0,0 +1,357 @@ +import React from 'react'; +import { + Tabs, + Tab, + TabList, + Checkbox, + Dropdown, + TextInput, + TabPanel, + TabPanels +} from '@carbon/react'; +import { AComponent, ComponentInfo } from './a-component'; +import image from '../assets/component-icons/tabs.svg'; +import { APlaceholder } from './a-placeholder'; +import { cx } from 'emotion'; +import { + reactClassNamesFromComponentObj, + angularClassNamesFromComponentObj, + nameStringToVariableString, + updatedState +} from '../helpers/tools'; +import { DraggableTileList } from '../helpers/draggable-list'; + +export const ATabsSettingsUI = ({ selectedComponent, setComponent }: any) => { + + const typeItems = [ + { id: 'line', text: 'Inline' }, + { id: 'contained', text: 'Contained' } + ]; + const updateListItems = (key: string, value: any, index: number) => { + const step = { + ...selectedComponent.items[index], + [key]: value + }; + + setComponent({ + ...selectedComponent, + items: [ + ...selectedComponent.items.slice(0, index), + step, + ...selectedComponent.items.slice(index + 1) + ] + }); + }; + + const template = (item: any, index: number) => { + return <> + { + updateListItems('labelText', event.currentTarget.value, index); + }} /> +
+ updateListItems('disabled', checked, index)} /> +
+ ; + }; + + const updateStepList = (newList: any[]) => { + setComponent({ + ...selectedComponent, + items: newList + }); + }; + + return <> + setComponent({ + ...selectedComponent, + isFollowFocused: checked + })} /> + setComponent({ + ...selectedComponent, + isCacheActive: checked + })} /> + setComponent({ + ...selectedComponent, + isNavigation: checked + })} /> + item.id === selectedComponent.tabType)} + itemToString={(item: any) => (item ? item.text : '')} + onChange={(event: any) => setComponent({ + ...selectedComponent, + tabType: event.selectedItem.id + })} /> + + ; +}; + +export const ATabsCodeUI = ({ selectedComponent, setComponent }: any) => { + setComponent({ + ...selectedComponent, + codeContext: { + ...selectedComponent.codeContext, + name: event.currentTarget.value + } + }); + }} +/>; + +export const ATabs = ({ + children, + componentObj, + fragment, + setFragment, + ...rest +}: any) => { + return ( + + cc.id).join(' ')}> + <> + + { + componentObj.items.map((step: any, index: number) => componentObj.selectedTab = index} + className={cx(step.className, componentObj.cssClasses?.map((cc: any) => cc.id).join(' '))} + disabled={step.disabled} + key={index}> + {step.labelText} + ) + } + + + { + componentObj.items.map((step: any, index: number) => { + return { + event.stopPropagation(); + event.preventDefault(); + const dragObj = JSON.parse(event.dataTransfer.getData('drag-object')); + // update the tab content in the item list for the selected tab + const items = componentObj.items.map((item: any, index: any) => { + if (index === componentObj.selectedTab) { + return { + ...step, + items: [ + ...step.items, + dragObj.component + ] + }; + } + return item; + }); + setFragment({ + ...fragment, + data: updatedState(fragment.data, { + type: 'update', + component: { + ...componentObj, + items + } + }) + }, false); + }}> + { + step.items && step.items.length > 0 + ? children.filter((child: any, index: any) => index === componentObj.selectedTab) + : + } + ; + } + ) + } + + + + + ); +}; + +export const componentInfo: ComponentInfo = { + component: ATabs, + settingsUI: ATabsSettingsUI, + codeUI: ATabsCodeUI, + render: ({ componentObj, select, selected, remove, renderComponents, outline, fragment, setFragment }) => + { + // render the child components within each tab. + componentObj.items.map((tab: any) => { + if (tab.items && tab.items.length > 0) { + return tab.items.map((item: any) => { + return renderComponents(item, outline); + }); + } + }) + } + , + keywords: ['tabs', 'tab'], + name: 'Tabs', + type: 'tabs', + defaultComponentObj: { + type: 'tabs', + items: [ + { + type: 'tab', + labelText: 'Tab 1', + items: [] + } + ] + }, + image, + codeExport: { + angular: { + latest: { + inputs: ({ json }) => ` + @Input() ${nameStringToVariableString(json.codeContext?.name)}FollowFocus = ${ + json.isFollowFocused ? json.isFollowFocused : false}; + @Input() ${nameStringToVariableString(json.codeContext?.name)}CacheActive = ${json.isCacheActive ? json.isCacheActive : false}; + @Input() ${nameStringToVariableString(json.codeContext?.name)}isNavigation = ${json.isNavigation ? json.isNavigation : true}; + @Input() ${nameStringToVariableString(json.codeContext?.name)}TabType: any = "${json.tabType ? json.tabType : 'contained'}";`, + outputs: () => '', + imports: ['TabsModule'], + code: ({ json, fragments, jsonToTemplate, customComponentsCollections }) => { + return ` + ${json.items.map((step: any) => ` + ${step.items && step.items.length > 0 + ? step.items.map((element: any) => + jsonToTemplate(element, fragments, customComponentsCollections)).join('\n') : ''} + ` + ).join('\n')} + `; + } + }, + v10: { + inputs: ({ json }) => ` + @Input() ${nameStringToVariableString(json.codeContext?.name)}FollowFocus = ${json.isFollowFocused ? json.isFollowFocused : false}; + @Input() ${nameStringToVariableString(json.codeContext?.name)}CacheActive = ${json.isCacheActive ? json.isCacheActive : false}; + @Input() ${nameStringToVariableString(json.codeContext?.name)}isNavigation = ${json.isNavigation ? json.isNavigation : true}; + @Input() ${nameStringToVariableString(json.codeContext?.name)}TabType: any = "${json.tabType ? json.tabType : 'contained'}";`, + outputs: () => '', + imports: ['TabsModule'], + code: ({ json, fragments, jsonToTemplate, customComponentsCollections }) => { + return ` + ${json.items.map((step: any) => ` + ${step.items && step.items.length > 0 + ? step.items.map((element: any) => + jsonToTemplate(element, fragments, customComponentsCollections)).join('\n') : ''} + ` + ).join('\n')} + `; + } + } + }, + react: { + latest: { + imports: ['Tabs', 'Tab', 'TabList', 'TabPanels', 'TabPanel'], + code: ({ json, fragments, jsonToTemplate, customComponentsCollections }) => { + return ` + + ${json.items.map((step: any, index: any) => ` handleInputChange({ + target: { + selectedTab: ${index} + } + })} + key= {${index}} + disabled={${step.disabled}}> + ${step.labelText} + ` + ).join('\n')} + + + ${json.items.map((step: any) => ` + ${step.items && step.items.length > 0 + ? step.items.map((element: any) => + jsonToTemplate(element, fragments, customComponentsCollections)).join('\n') : ''} + ` + ).join('\n')} + + `; + } + }, + v10: { + imports: ['Tabs', 'Tab'], + code: ({ json, fragments, jsonToTemplate, customComponentsCollections }) => { + return ` + ${json.items.map((step: any, index: any) => ` handleInputChange({ + target: { + selectedTab: ${index} + } + })} + key= {${index}} + disabled={${step.disabled}} + label="${step.labelText}"> + ${step.items && step.items.length > 0 + ? step.items.map((element: any) => + jsonToTemplate(element, fragments, customComponentsCollections)).join('\n') : ''} + ` + ).join('\n')} + `; + } + } + } + } +}; diff --git a/sdk/react/src/lib/fragment-components/index.ts b/sdk/react/src/lib/fragment-components/index.ts index e4308a3fe..b486671a4 100644 --- a/sdk/react/src/lib/fragment-components/index.ts +++ b/sdk/react/src/lib/fragment-components/index.ts @@ -25,6 +25,7 @@ import * as link from './a-link'; import * as loading from './a-loading'; import * as inlineLoading from './a-inline-loading'; import * as overflowMenu from './a-overflow-menu'; +import * as tabs from './a-tabs'; // Tiles import * as tile from './tiles/a-tile'; import * as toggle from './a-toggle'; @@ -63,7 +64,7 @@ export { ATextInput, ATextInputSettingsUI, ATextInputCodeUI } from './a-text-inp export { AOverflowMenu, AOverflowMenuCodeUI, AOverflowMenuSettingsUI } from './a-overflow-menu'; export { ARadio, ARadioSettingsUI, ARadioCodeUI } from './a-radio'; export { ARadioGroup, ARadioGroupSettingsUI, ARadioGroupCodeUI } from './a-radio-group'; - +export { ATabs, ATabsSettingsUI, ATabsCodeUI } from './a-tabs'; // Tiles export { ATile, ATileCodeUI, ATileSettingsUI } from './tiles/a-tile'; export { AToggle, AToggleSettingsUI, AToggleCodeUI } from './a-toggle'; @@ -76,6 +77,7 @@ export { ARadioTile, ARadioTileCodeUI, ARadioTileSettingsUI } from './tiles/a-ra export { ARadioTileGroup, ARadioTileGroupCodeUI, ARadioTileGroupSettingsUI } from './tiles/a-radio-tile-group'; export const allComponents = { + tabs, accordion, accordionitem, button,