diff --git a/frontend/common/types/responses.ts b/frontend/common/types/responses.ts index fc7a46f390c0..da429293fe81 100644 --- a/frontend/common/types/responses.ts +++ b/frontend/common/types/responses.ts @@ -34,6 +34,8 @@ export type Operator = { hideValue?: boolean warning?: string valuePlaceholder?: string + append?: string + type?: string } export type ChangeRequestSummary = { id: number diff --git a/frontend/web/components/base/select/MultiSelect.tsx b/frontend/web/components/base/select/MultiSelect.tsx deleted file mode 100644 index 1aa0438dc3c8..000000000000 --- a/frontend/web/components/base/select/MultiSelect.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import React from 'react' -import { MultiValueProps } from 'react-select/lib/components/MultiValue' - -export interface MultiSelectOption { - label: string - value: string -} - -export interface MultiSelectProps { - selectedValues: string[] - onSelectionChange: (selectedValues: string[]) => void - options: MultiSelectOption[] - colorMap?: Map - label?: string - placeholder?: string - className?: string - disabled?: boolean - size?: 'small' | 'default' | 'large' -} - -const CustomMultiValue = ({ - color, - data, - removeProps, -}: MultiValueProps & { color?: string }) => { - return ( -
- - {data.label} - - removeProps?.onClick?.(data)} - style={{ - cursor: 'pointer', - fontSize: '14px', - lineHeight: '1', - }} - > - × - -
- ) -} - -const CustomOption = ({ - children, - color, - ...props -}: any & { color?: string }) => { - return ( -
- {color && ( -
- )} - {children} -
- ) -} - -const MultiSelect: React.FC = ({ - className = '', - colorMap, - disabled = false, - label, - onSelectionChange, - options, - placeholder = 'Select options...', - selectedValues, - size = 'default', -}) => { - return ( -
- {label && } - { + const values = selectedOptions + ? selectedOptions.map((opt: MultiSelectOption) => opt.value) + : [] + onSelectionChange(values) + }} + components={{ + MultiValue: (props: MultiValueProps) => ( + + ), + ...(colorMap ? {Option: (props: OptionProps) => } : {}), + }} + value={selectedValues.map((value) => ({ + label: options.find((opt) => opt.value === value)?.label || value, + value, + }))} + options={options} + className='react-select react-select__extensible w-100' + styles={{ + control: (base: any) => ({ + ...base, + cursor: 'pointer', + }), + multiValue: (base: any) => ({ + ...base, + flexShrink: 0, + margin: '2px', + }), + valueContainer: (base: any) => ({ + ...base, + flexWrap: 'nowrap', + gap: '2px', + paddingBottom: '6px', + paddingTop: '6px', + flex: 1 + }), + input: (base: any) => ({ + ...base, + margin: 0, + paddingBottom: 0, + paddingTop: 0, + }), + }} + /> +
+ ) +} diff --git a/frontend/web/components/base/select/multi-select/index.ts b/frontend/web/components/base/select/multi-select/index.ts new file mode 100644 index 000000000000..d22634c13fed --- /dev/null +++ b/frontend/web/components/base/select/multi-select/index.ts @@ -0,0 +1 @@ +export { MultiSelect } from './MultiSelect' diff --git a/frontend/web/components/modals/AssociatedSegmentOverrides.js b/frontend/web/components/modals/AssociatedSegmentOverrides.js index 8a5b17c4bbba..fe81ab4fdba6 100644 --- a/frontend/web/components/modals/AssociatedSegmentOverrides.js +++ b/frontend/web/components/modals/AssociatedSegmentOverrides.js @@ -157,6 +157,7 @@ class TheComponent extends Component { />
= ({ } > -
+
= ({
-
+
{ setValueChanged(true) @@ -539,7 +539,7 @@ const CreateSegment: FC = ({
-
+
= ({ } > -
{MetadataTab}
+
{MetadataTab}
)} diff --git a/frontend/web/components/modals/CreateSegmentRulesTabForm.tsx b/frontend/web/components/modals/CreateSegmentRulesTabForm.tsx index d088a56496e2..22a6d521e635 100644 --- a/frontend/web/components/modals/CreateSegmentRulesTabForm.tsx +++ b/frontend/web/components/modals/CreateSegmentRulesTabForm.tsx @@ -131,10 +131,9 @@ const CreateSegmentRulesTabForm: React.FC = ({ Utils.displayLimitAlert('segments', segmentsLimitAlert.percentage)}
)} - -
- - +
+
+ = ({ type='text' placeholder='E.g. power_users' /> - +
+ {!condensed && ( + { + setValueChanged(true) + setDescription(Utils.safeParseEventValue(e)) + }} + isValid={name && name.length} + type='text' + title='Description' + placeholder="e.g. 'People who have spent over $100' " + /> + )}
- {!condensed && ( - { - setValueChanged(true) - setDescription(Utils.safeParseEventValue(e)) - }} - isValid={name && name.length} - type='text' - title='Description' - placeholder="e.g. 'People who have spent over $100' " - /> - )}
diff --git a/frontend/web/components/modals/CreateSegmentUsersTabContent.tsx b/frontend/web/components/modals/CreateSegmentUsersTabContent.tsx index 841eff433820..2ee99ba03ebd 100644 --- a/frontend/web/components/modals/CreateSegmentUsersTabContent.tsx +++ b/frontend/web/components/modals/CreateSegmentUsersTabContent.tsx @@ -98,6 +98,7 @@ const CreateSegmentUsersTabContent: React.FC< = ({ operator: operatorValue?.split(':')[0], } - if (newOperator?.hideValue) { - updates.value = null - } - + // Handle append changes first if (prevOperator?.append !== newOperator?.append) { - const cleanValue = splitIfValue(condition?.value, prevOperator?.append)[0] + const conditionValue = typeof condition?.value === 'boolean' ? String(condition.value) : condition?.value + const cleanValue = splitIfValue(conditionValue, prevOperator?.append || '')[0] updates.value = cleanValue + (newOperator?.append || '') } + // Clear value when changing to or from IN operator (after append handling) + const isChangingToIn = operatorValue === 'IN' + const isChangingFromIn = condition?.operator === 'IN' + if (isChangingToIn || isChangingFromIn) { + updates.value = '' + } + + // Set null for hideValue operators (takes precedence over everything) + if (newOperator?.hideValue) { + updates.value = null + } else if (prevOperator?.hideValue && !updates.hasOwnProperty('value')) { + // When changing FROM a hideValue operator, initialize value to empty string + updates.value = '' + } + if (shouldAutoSetIdentityKey(operatorValue, condition.property)) { updates.property = RuleContextValues.IDENTITY_KEY } diff --git a/frontend/web/components/segments/Rule/components/RuleConditionRow.tsx b/frontend/web/components/segments/Rule/components/RuleConditionRow.tsx index 4c02181e2134..f71b524909f5 100644 --- a/frontend/web/components/segments/Rule/components/RuleConditionRow.tsx +++ b/frontend/web/components/segments/Rule/components/RuleConditionRow.tsx @@ -60,7 +60,7 @@ const RuleConditionRow: React.FC = ({ const { displayValue, operator, operatorObj, valuePlaceholder } = useRuleOperator(rule, operators) - const { allowedContextValues, isValueFromContext, showEnvironmentDropdown } = + const { allowedContextValues, isValueFromContext } = useRuleContext(operator, rule.property) if (rule.delete) { @@ -84,74 +84,81 @@ const RuleConditionRow: React.FC = ({ )} - - - {readOnly ? ( - !!find(operators, { value: operator })?.label - ) : ( - { + setRuleProperty(ruleIndex, 'operator', value) + }} + options={operators} + className="col-10 col-sm-3 col-md-3" + /> + )} + { + setRuleProperty(ruleIndex, 'value', { + value: + operatorObj && operatorObj.append + ? `${value}${operatorObj.append}` + : value, + }) }} - options={operators} - style={{ width: '190px' }} + isValid={Utils.validateRule(rule) && !ruleErrors?.value} + className='col-10 col-sm-4 col-md-4' /> - )} - { - setRuleProperty(ruleIndex, 'value', { - value: - operatorObj && operatorObj.append - ? `${value}${operatorObj.append}` - : value, - }) - }} - isValid={Utils.validateRule(rule) && !ruleErrors?.value} - /> - {isLastRule && !readOnly ? ( - + ) : ( +
+ )} + - ) : ( -
- )} - - + + +
+
{showDescription && (