import React from "react"; import BaseControl, { ControlProps } from "./BaseControl"; import { StyledInputGroup, StyledPropertyPaneButton } from "./StyledControls"; import styled from "constants/DefaultTheme"; import { FormIcons } from "icons/FormIcons"; import { ControlIcons } from "icons/ControlIcons"; import { AnyStyledComponent } from "styled-components"; import { generateReactKey } from "utils/generators"; import { DroppableComponent } from "../designSystems/appsmith/DraggableListComponent"; import { getNextEntityName } from "utils/AppsmithUtils"; import _ from "lodash"; import * as Sentry from "@sentry/react"; const StyledDeleteIcon = styled(FormIcons.DELETE_ICON as AnyStyledComponent)` padding: 0; position: relative; margin-left: 15px; cursor: pointer; `; const StyledDragIcon = styled(ControlIcons.DRAG_CONTROL as AnyStyledComponent)` padding: 0; position: relative; margin-right: 15px; cursor: move; svg { path { fill: ${(props) => props.theme.colors.paneSectionLabel}; } } `; const StyledPropertyPaneButtonWrapper = styled.div` display: flex; width: 100%; justify-content: flex-end; margin-top: 10px; `; const ItemWrapper = styled.div` display: flex; justify-content: flex-start; align-items: center; `; const TabsWrapper = styled.div` width: 100%; display: flex; flex-direction: column; `; const StyledOptionControlInputGroup = styled(StyledInputGroup)` margin-right: 2px; &&& { input { border: none; color: ${(props) => props.theme.colors.textOnDarkBG}; background: ${(props) => props.theme.colors.paneInputBG}; &:focus { border: none; color: ${(props) => props.theme.colors.textOnDarkBG}; background: ${(props) => props.theme.colors.paneInputBG}; } } } `; type RenderComponentProps = { index: number; item: { label: string; }; deleteOption: (index: number) => void; updateOption: (index: number, value: string) => void; }; function TabControlComponent(props: RenderComponentProps) { const { deleteOption, updateOption, item, index } = props; return ( ) => { updateOption(index, event.target.value); }} defaultValue={item.label} /> { deleteOption(index); }} /> ); } class TabControl extends BaseControl { componentDidMount() { this.migrateTabData(this.props.propertyValue); } migrateTabData( tabData: Array<{ id: string; label: string; }>, ) { // Added a migration script for older tab data that was strings // deprecate after enough tabs have moved to the new format if (_.isString(tabData)) { try { const parsedData: Array<{ sid: string; label: string; }> = JSON.parse(tabData); this.updateProperty(this.props.propertyName, parsedData); return parsedData; } catch (error) { Sentry.captureException({ message: "Tab Migration Failed", oldData: this.props.propertyValue, }); } } else { return this.props.propertyValue; } } updateItems = (items: Array>) => { this.updateProperty(this.props.propertyName, items); }; render() { const tabs: Array<{ id: string; label: string; }> = _.isString(this.props.propertyValue) ? [] : this.props.propertyValue; return ( ); } deleteOption = (index: number) => { let tabs: Array> = this.props.propertyValue.slice(); if (tabs.length === 1) return; delete tabs[index]; tabs = tabs.filter(Boolean); this.updateProperty(this.props.propertyName, tabs); }; updateOption = (index: number, updatedLabel: string) => { const tabs: Array<{ id: string; label: string; }> = this.props.propertyValue; const updatedTabs = tabs.map((tab, tabIndex) => { if (index === tabIndex) { return { ...tab, label: updatedLabel, }; } return tab; }); this.updateProperty(this.props.propertyName, updatedTabs); }; addOption = () => { let tabs: Array<{ id: string; label: string; widgetId: string; }> = this.props.propertyValue; const newTabId = generateReactKey({ prefix: "tab" }); const newTabLabel = getNextEntityName( "Tab ", tabs.map((tab) => tab.label), ); tabs = [ ...tabs, { id: newTabId, label: newTabLabel, widgetId: generateReactKey() }, ]; this.updateProperty(this.props.propertyName, tabs); }; static getControlType() { return "TABS_INPUT"; } } export default TabControl;