mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-24 01:18:23 +00:00
Modularisation fixes added
This commit is contained in:
parent
e1abe400b8
commit
18d2860d7b
13 changed files with 178 additions and 343 deletions
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './ColorSwatches';
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import React from 'react';
|
||||
import { withEditionSpecificComponent } from '@/modules/common/helpers';
|
||||
const ThemeSelect = () => {
|
||||
return <></>;
|
||||
};
|
||||
export default withEditionSpecificComponent(ThemeSelect, 'Appbuilder');
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './ThemeSelect';
|
||||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './BaseColorSwatches';
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue