Modularisation fixes added

This commit is contained in:
Shaurya Sharma 2025-03-05 01:04:29 +05:30
parent e1abe400b8
commit 18d2860d7b
13 changed files with 178 additions and 343 deletions

View file

@ -1,105 +0,0 @@
import React, { useState } from 'react';
import ToggleGroup from '@/ToolJetUI/SwitchGroup/ToggleGroup';
import ToggleGroupItem from '@/ToolJetUI/SwitchGroup/ToggleGroupItem';
import cx from 'classnames';
import { Color } from './Color';
import CheckIcon from '@/components/ui/Checkbox/CheckboxUtils/CheckIcon';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
export const ColorSwatches = ({
value,
onChange,
pickerStyle = {},
cyLabel,
asBoxShadowPopover = true,
meta,
outerWidth = '142px',
component,
styleDefinition,
}) => {
const [componentType, setComponentType] = useState('color');
const selectedTheme = useStore((state) => state.globalSettings.theme, shallow);
const darkMode = localStorage.getItem('darkMode') === 'true';
const brandColors = selectedTheme?.definition?.brand?.colors || {};
//Todo: Remove all hardcoded brand once all theme values are added.
return (
<Color
value={value}
colorMap={Object.keys(brandColors)?.reduce((acc, colorType) => {
acc[`var(--${colorType}-brand)`] = colorType;
return acc;
}, {})}
onChange={onChange}
pickerStyle={pickerStyle}
cyLabel={cyLabel}
asBoxShadowPopover={asBoxShadowPopover}
meta={meta}
outerWidth={outerWidth}
component={component}
styleDefinition={styleDefinition}
componentType={componentType}
SwatchesToggle={() => (
<>
<SwatchesToggle value={componentType} onChange={setComponentType} />
</>
)}
CustomOptionList={() => (
<div style={{ padding: '8px' }}>
{Object.keys(brandColors)?.map((colorType, index) => (
<CustomOption
color={brandColors[colorType][darkMode ? 'dark' : 'light']}
colorType={colorType}
key={index}
onChange={onChange}
value={value}
darkMode={darkMode}
/>
))}
</div>
)}
/>
);
};
const SwatchesToggle = ({ value, onChange }) => {
return (
<div className={cx('codebuilder-color-swatches-wrapper')}>
<div className={cx('codebuilder-color-swatches')}>
<ToggleGroup
onValueChange={(value) => {
onChange(value);
}}
defaultValue={value}
>
<ToggleGroupItem key={'swatches'} value={'swatches'}>
Swatches
</ToggleGroupItem>
<ToggleGroupItem key={'color'} value={'color'}>
Color picker
</ToggleGroupItem>
</ToggleGroup>
</div>
</div>
);
};
const CustomOption = ({ darkMode, onChange, colorType, color, value }) => {
const isSelected = `var(--${colorType}-brand)` === value;
return (
<div className={cx({ 'dark-theme': darkMode })}>
<div
className="codebuilder-color-swatches-options"
onClick={() => {
onChange(`var(--${colorType}-brand)`);
}}
>
<div className="d-flex align-items-center">
{isSelected && <CheckIcon size="large" fill="#4368E3" />}
<div className="color-icon" style={{ backgroundColor: color, marginLeft: !isSelected && '20px' }} />
<span style={{ marginLeft: '5px' }}>Brand/{colorType.charAt(0).toUpperCase() + colorType.slice(1)}</span>
</div>
</div>
</div>
);
};

View file

@ -18,7 +18,7 @@ import { NumberInput } from '../CodeBuilder/Elements/NumberInput';
import { Datepicker } from '../CodeBuilder/Elements/Datepicker';
import TableRowHeightInput from '../CodeBuilder/Elements/TableRowHeightInput';
import { TimePicker } from '../CodeBuilder/Elements/TimePicker';
import { ColorSwatches } from '../CodeBuilder/Elements/ColorSwatches';
import { ColorSwatches } from '@/modules/Appbuilder/components';
const AllElements = {
Color,

View file

@ -1,233 +0,0 @@
import React, { useEffect, useState } from 'react';
import Select from '@/_ui/Select';
import CheckMark from '@/_ui/Icon/bulkIcons/CheckMark';
import { components } from 'react-select';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
import { useNavigate } from 'react-router-dom';
import { getWorkspaceId } from '@/_helpers/utils';
import { appThemesService } from '../../../../ee/modules/WorkspaceSettings/pages/ManageThemes/service/app_themes.service';
import { LicenseTooltip } from '@/LicenseTooltip';
const ThemeSelect = ({ darkMode }) => {
const [themesList, setThemesList] = useState([]);
const selectedTheme = useStore((state) => state.globalSettings.theme, shallow);
const featureAccess = useStore((state) => state?.license?.featureAccess, shallow);
const licenseValid = !featureAccess?.licenseStatus?.isExpired && featureAccess?.licenseStatus?.isLicenseValid;
const globalSettingsChanged = useStore((state) => state.globalSettingsChanged, shallow);
const workspaceId = getWorkspaceId();
const appId = useStore((state) => state.app.appId, shallow);
const versionId = useStore((state) => state.currentVersionId, shallow);
const isDisabled = !licenseValid || !featureAccess?.customThemes;
const navigate = useNavigate();
const fetchAllThemes = async () => {
const themes = await appThemesService.fetchAllThemes();
const options = themes.map((theme) => ({
value: theme.id,
name: theme.name,
label: theme.name,
color: theme?.definition?.brand?.colors?.primary,
isDefault: theme?.isDefault,
theme: theme,
}));
setThemesList(options);
};
const setTheme = async (themeId) => {
await appThemesService.updateAppTheme(appId, versionId, themeId);
};
useEffect(() => {
fetchAllThemes();
}, []);
const customSelectStyles = {
control: (provided) => ({
...provided,
width: '158px',
height: '32px',
minHeight: '32px',
flexWrap: 'nowrap',
overflow: 'hidden',
}),
input: (provided) => ({
...provided,
width: '150px',
height: 'auto',
padding: '0px',
color: darkMode ? '#fff' : '#000',
}),
singleValue: (provided) => ({
...provided,
color: darkMode ? '#fff' : '#000', // Set selected value color based on darkMode
}),
valueContainer: (provided, _state) => ({
...provided,
fontSize: '12px',
height: '100%',
color: darkMode ? '#fff' : '#000',
}),
menu: (provided) => ({
...provided,
width: '220px',
right: '0',
left: 'auto',
}),
menuList: (provided) => ({
...provided,
width: '220px',
textAlign: 'left',
overflowY: 'auto', // Enable scrolling if needed
scrollbarWidth: 'none', // Hide scrollbar for Firefox
borderRadius: '8px',
}),
menuPortal: (base) => ({
...base,
top: base.top + 2, // Adjust the top position
}),
option: (provided, state) => ({
...provided,
backgroundColor: state.isFocused
? '#f0f0f0' // Hover color
: state.isSelected
? '#e6e6e6' // Selected background color
: 'white',
color: state.isSelected ? '#333' : 'black', // Adjust text color for selected state
padding: '0px',
paddingLeft: '20px',
position: 'relative',
}),
};
const CustomOption = (props) => {
const { data, isSelected } = props;
return (
<components.Option {...props}>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-start',
gap: '2px',
height: '30px',
}}
>
{isSelected && (
<CheckMark width="20px" fill="transparent" fillIcon={'#3e63dd'} className="datepicker-select-check" />
)}
<div
className="color-icon"
style={{
backgroundColor: data?.color?.[darkMode ? 'dark' : 'light'],
marginLeft: isSelected ? '0px' : '22px',
}}
/>
<span style={{ fontSize: '12px', marginLeft: '2px', color: darkMode ? '#fff' : '#000' }}>{data.label}</span>
{data?.isDefault && (
<span
style={{
marginLeft: 'auto',
marginRight: '10px',
display: 'inline-flex', // Enables flexbox on the span
alignItems: 'center', // Vertically centers the text
justifyContent: 'center',
color: darkMode ? '#fff' : '#000',
}}
className="theme-default-pill"
>
Default
</span>
)}
</div>
</components.Option>
);
};
const CustomValueContainer = ({ children, ...props }) => {
return (
<components.ValueContainer {...props}>
<div className="d-flex align-items-center">
<div
className="color-icon"
style={{
backgroundColor: selectedTheme?.definition?.brand?.colors?.primary?.[darkMode ? 'dark' : 'light'],
marginRight: '5px',
}}
/>
{children}
</div>
</components.ValueContainer>
);
};
const CustomMenuList = (props) => {
return (
<components.MenuList {...props}>
<div style={{ marginTop: '14px', marginBottom: '8px' }}>
<span className="theme-custom-menu-list-header" style={{ color: darkMode ? '#fff' : '#000' }}>
On your workspace
</span>
</div>
{props.children}
<div
style={{
display: 'flex',
justifyContent: 'center',
padding: '8px',
borderTop: '1px solid #ccc',
height: '46px',
}}
>
<ButtonSolid
onClick={() => {
navigate(`/${workspaceId}/workspace-settings/themes`);
}}
variant="tertiary"
leftIcon="addrectangle"
fill="#3e63dd"
iconWidth="16"
className="tj-text-xsm theme-create-btn"
>
<span style={{ color: darkMode ? '#fff' : '#000' }}>Create a new theme</span>
</ButtonSolid>
</div>
</components.MenuList>
);
};
return (
<div className="d-flex theme-dropdown-wrapper mb-3">
<div className="d-flex align-items-center ">
<p className="tj-text-xsm color-slate12 w-full m-auto">Theme</p>
</div>
<LicenseTooltip
feature={'Custom themes'}
isAvailable={!isDisabled}
noTooltipIfValid={true}
customMessage={"You don't have access to custom themes. Upgrade your plan to access this feature."}
>
<Select
options={themesList}
value={selectedTheme?.id}
onChange={(themeId) => {
setTheme(themeId);
globalSettingsChanged({ theme: themesList.find((theme) => theme.value === themeId)?.theme });
}}
width={'100%'}
isDisabled={isDisabled}
useMenuPortal={true}
styles={customSelectStyles}
useCustomStyles={true}
components={{ Option: CustomOption, MenuList: CustomMenuList, ValueContainer: CustomValueContainer }}
/>
</LicenseTooltip>
</div>
);
};
export default ThemeSelect;

View file

@ -7,7 +7,7 @@ import AppExport from './AppExport';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
import AppModeToggle from './AppModeToggle';
import ThemeSelect from './ThemeSelect';
import { ThemeSelect } from '@/modules/Appbuilder/components';
import MaintenanceMode from './MaintenanceMode';
import HideHeaderToggle from './HideHeaderToggle';

View file

@ -4407,9 +4407,10 @@ input[type="text"] {
.color-icon {
width: 18px;
height: 18px;
border-radius: 4px;
border: 1.5px solid #CCD1D5;
border-radius: 6px;
background-color: #0091FF;
border: 1px solid var(--Border-default, #CCD1D5);
box-shadow: 0px 1px 0px 0px #e5e5e5;
}
.portal-header {
@ -18718,4 +18719,10 @@ section.ai-message-prompt-input-wrapper {
.codebuilder-color-swatches-options:hover {
background-color: #313b46 !important;
}
}
.codebuilder-color-picker {
.sketch-picker {
border: none !important;
}
}

View file

@ -0,0 +1,9 @@
import React from 'react';
import { withEditionSpecificComponent } from '@/modules/common/helpers';
import BaseColorSwatches from '@/modules/common/components/BaseColorSwatches';
const ColorSwatches = (props) => {
return <BaseColorSwatches {...props} />;
};
export default withEditionSpecificComponent(ColorSwatches, 'Appbuilder');

View file

@ -0,0 +1 @@
export { default } from './ColorSwatches';

View file

@ -0,0 +1,6 @@
import React from 'react';
import { withEditionSpecificComponent } from '@/modules/common/helpers';
const ThemeSelect = () => {
return <></>;
};
export default withEditionSpecificComponent(ThemeSelect, 'Appbuilder');

View file

@ -0,0 +1 @@
export { default } from './ThemeSelect';

View file

@ -2,5 +2,7 @@ import CreateVersionModal from './CreateVersionModal';
import PromoteReleaseButton from './PromoteReleaseButton';
import LogoNavDropdown from './LogoNavDropdown';
import AppEnvironments from './AppEnvironments';
import ThemeSelect from './ThemeSelect';
import ColorSwatches from './ColorSwatches';
export { CreateVersionModal, PromoteReleaseButton, LogoNavDropdown, AppEnvironments };
export { CreateVersionModal, PromoteReleaseButton, LogoNavDropdown, AppEnvironments, ThemeSelect, ColorSwatches };

View file

@ -0,0 +1,144 @@
import React, { useState } from 'react';
import { SketchPicker } from 'react-color';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Popover from 'react-bootstrap/Popover';
import classNames from 'classnames';
import { computeColor } from '@/_helpers/utils';
const BaseColorSwatches = ({
value,
onChange,
pickerStyle = {},
colorMap = {},
cyLabel,
asBoxShadowPopover = true,
meta,
outerWidth = '142px',
component,
styleDefinition,
componentType = 'color',
CustomOptionList = () => {},
SwatchesToggle = () => {},
}) => {
value = component == 'Button' ? computeColor(styleDefinition, value, meta) : value;
const [showPicker, setShowPicker] = useState(false);
const darkMode = localStorage.getItem('darkMode') === 'true';
const colorPickerPosition = meta?.colorPickerPosition ?? '';
const coverStyles = {
position: 'fixed',
top: '0px',
right: '0px',
bottom: '0px',
left: '0px',
};
const outerStyles = {
width: outerWidth,
height: '32px',
borderRadius: ' 6px',
border: 'none',
display: 'flex',
paddingLeft: '4px',
alignItems: 'center',
gap: '4px',
background: showPicker && 'var(--indigo2)',
outline: showPicker && '1px solid var(--indigo9)',
boxShadow: showPicker && '0px 0px 0px 1px #C6D4F9',
};
const decimalToHex = (alpha) => {
let aHex = Math.round(255 * alpha).toString(16);
return alpha === 0 ? '00' : aHex.length < 2 ? `0${aHex}` : aHex;
};
const handleColorChange = (color) => {
const hexCode = `${color.hex}${decimalToHex(color?.rgb?.a ?? 1.0)}`;
onChange(hexCode);
};
const eventPopover = () => {
return (
<Popover
className={classNames(
{ 'dark-theme': darkMode },
// This is fix when color picker don't have much space to open in bottom side
{ 'inspector-color-input-popover': colorPickerPosition === 'top' }
)}
style={{ zIndex: 10000 }}
>
<Popover.Body className={!asBoxShadowPopover && 'boxshadow-picker'} style={{ padding: '0px' }}>
<>{ColorPicker()}</>
</Popover.Body>
</Popover>
);
};
const ColorPicker = () => {
return (
<div className="codebuilder-color-picker">
{SwatchesToggle()}
{showPicker && componentType === 'swatches' && CustomOptionList()}
{showPicker && componentType === 'color' && (
<div>
{/* <div style={coverStyles} onClick={() => setShowPicker(false)} /> */}
<div style={pickerStyle}>
<SketchPicker
onFocus={() => setShowPicker(true)}
color={value}
onChangeComplete={handleColorChange}
style={{ bottom: 0 }}
/>
</div>
</div>
)}
</div>
);
};
const ColorPickerInputBox = () => {
return (
<div
className="row mx-0 color-picker-input d-flex"
onClick={() => setShowPicker(true)}
data-cy={`${String(cyLabel)}-picker`}
style={outerStyles}
>
<div className="color-icon" style={{ backgroundColor: value, marginLeft: '8px' }} />
<div className="col tj-text-xsm p-0 color-slate12" data-cy={`${String(cyLabel)}-value`}>
{colorMap?.[value]
? 'Brand/' + colorMap?.[value]?.charAt(0).toUpperCase() + colorMap?.[value]?.slice(1)
: value}
</div>
</div>
);
};
return (
<div className="row fx-container" data-cy="color-picker-parent">
<div className="col">
<div className="field">
{!asBoxShadowPopover ? (
<>
{ColorPicker()}
{ColorPickerInputBox()}
</>
) : (
<OverlayTrigger
onToggle={(showPicker) => {
setShowPicker(showPicker);
}}
show={showPicker}
trigger="click"
placement={!colorPickerPosition ? 'bottom' : colorPickerPosition}
flip={true}
fallbackPlacements={['top', 'left']}
rootClose={true}
overlay={eventPopover()}
>
{ColorPickerInputBox()}
</OverlayTrigger>
)}
</div>
</div>
</div>
);
};
export default BaseColorSwatches;

View file

@ -0,0 +1 @@
export { default } from './BaseColorSwatches';

View file

@ -25,6 +25,7 @@ import EditRoleErrorModal from './ErrorModal';
import BaseOnboardingQuestions from './BaseOnboardingQuestions';
import BaseSetupAdminPage from './BaseSetupAdminPage';
import UsersTable from './UsersTable';
import BaseColorSwatches from './BaseColorSwatches';
export {
FormTextInput,
@ -32,6 +33,7 @@ export {
GeneralFeatureImage,
SubmitButton,
FormHeader,
BaseColorSwatches,
EmailComponent,
FormDescription,
SSOAuthModule,