Mirror of https://github.com/roostorg/osprey
github.com/roostorg/osprey
1import * as React from 'react';
2import classNames from 'classnames';
3import shallow from 'zustand/shallow';
4
5import useQueryStore from '../../stores/QueryStore';
6import OspreyButton, { ButtonColors, ButtonHeights, ButtonWeights } from '../../uikit/OspreyButton';
7import Text, { TextColors, TextSizes, TextWeights } from '../../uikit/Text';
8import { addFeaturesToQueryFilter } from '../../utils/QueryUtils';
9import { openNewQueryWindow } from '../../utils/SearchParamUtils';
10
11import styles from './Feature.module.css';
12
13interface CommonFeatureProps {
14 featureName: string;
15 onClick?: () => void;
16 className?: string;
17 isEntity?: boolean;
18 textSelectable?: boolean;
19 isFeatureNameOnly?: boolean;
20}
21
22type SelectableFeatureProps = CommonFeatureProps & {
23 value: string | null;
24};
25
26const SelectableFeature = ({
27 featureName,
28 value,
29 onClick,
30 className,
31 isEntity = false,
32 textSelectable = true,
33 isFeatureNameOnly = false,
34}: SelectableFeatureProps): React.ReactElement => {
35 const [queuedQueryFilters, updateQueuedQueryFilters] = useQueryStore(
36 (state) => [state.queuedQueryFilters, state.updateQueuedQueryFilters],
37 shallow
38 );
39 const [isSelected, setIsSelected] = React.useState(false);
40
41 React.useEffect(() => {
42 if (isSelected && queuedQueryFilters.length === 0) {
43 setIsSelected(false);
44 }
45 }, [isSelected, queuedQueryFilters.length]);
46
47 const handleFeatureClick = (e: React.MouseEvent<HTMLButtonElement>) => {
48 const feature = { value, featureName };
49
50 if (e.shiftKey) {
51 const filteredFeatures = queuedQueryFilters.filter((queuedFilter) => {
52 return queuedFilter.value !== value || queuedFilter.featureName !== featureName;
53 });
54 const featureNotYetQueued = filteredFeatures.length === queuedQueryFilters.length;
55
56 if (featureNotYetQueued) {
57 setIsSelected(true);
58 updateQueuedQueryFilters([...queuedQueryFilters, feature]);
59 return;
60 }
61 // if queued and selected, deselect. if queued and not yet selected, don't add or remove
62 if (isSelected) {
63 setIsSelected(false);
64 updateQueuedQueryFilters(filteredFeatures);
65 }
66 } else if (e.ctrlKey || e.metaKey) {
67 openNewQueryWindow(addFeaturesToQueryFilter([feature]));
68 } else {
69 onClick?.();
70 }
71
72 return false;
73 };
74
75 const getSelectedClass = (): string => {
76 if (value == null) {
77 return styles.null;
78 }
79
80 if (value.length < 7) {
81 return styles.selectedShort;
82 } else if (value.length < 20) {
83 return styles.selectedMedium;
84 }
85
86 return styles.selectedLong;
87 };
88
89 const renderValue = (value: string | null) => {
90 // If the value is null, we want the ui to render the string "null".
91 if (value == null) return 'null';
92 if (isFeatureNameOnly)
93 return (
94 <Text size={TextSizes.H6} color={TextColors.LIGHT_HEADINGS_PRIMARY} weight={TextWeights.BOLD}>
95 {featureName}
96 </Text>
97 );
98 if (featureName !== 'ActionName') return value;
99
100 return (
101 <Text size={TextSizes.H6} color={TextColors.LIGHT_HEADINGS_PRIMARY} weight={TextWeights.BOLD}>
102 {value}
103 </Text>
104 );
105 };
106
107 const getFeatureTitle = () => {
108 if (isFeatureNameOnly) return featureName;
109 if (isEntity) return '';
110 return value ?? 'null';
111 };
112
113 return (
114 <OspreyButton
115 className={classNames(className, { [getSelectedClass()]: isSelected })}
116 style={{ padding: 0 }}
117 onClick={handleFeatureClick}
118 color={value == null ? ButtonColors.LINK_DISABLED : ButtonColors.LINK_BLUE}
119 height={ButtonHeights.SHORT}
120 weight={isEntity ? ButtonWeights.SEMIBOLD : ButtonWeights.NORMAL}
121 title={getFeatureTitle()}
122 textSelectable={textSelectable}
123 >
124 {renderValue(value)}
125 </OspreyButton>
126 );
127};
128
129type FeatureProps = CommonFeatureProps & {
130 value: unknown;
131};
132
133const Feature = ({ value, ...props }: FeatureProps): React.ReactElement => {
134 return <SelectableFeature value={value == null ? null : String(value)} {...props} />;
135};
136
137export default Feature;