Skip to content

Commit c2cacf7

Browse files
committed
replaced visibility checkbox with dropdown
1 parent 65d3021 commit c2cacf7

File tree

9 files changed

+340
-76
lines changed

9 files changed

+340
-76
lines changed

client/images/checkmark.svg

Lines changed: 13 additions & 0 deletions
Loading

client/images/lock.svg

Lines changed: 3 additions & 13 deletions
Loading

client/modules/IDE/components/Header/Toolbar.jsx

Lines changed: 25 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect } from 'react';
1+
import React, { useState, useEffect, useCallback } from 'react';
22
import classNames from 'classnames';
33
import PropTypes from 'prop-types';
44
import { useTranslation } from 'react-i18next';
@@ -15,12 +15,13 @@ import {
1515
setGridOutput,
1616
setTextOutput
1717
} from '../../actions/preferences';
18-
import { changeVisibility } from '../../actions/project';
1918
import PlayIcon from '../../../../images/play.svg';
2019
import StopIcon from '../../../../images/stop.svg';
2120
import PreferencesIcon from '../../../../images/preferences.svg';
2221
import ProjectName from './ProjectName';
2322
import VersionIndicator from '../VersionIndicator';
23+
import VisibilityDropdown from '../../../User/components/VisibilityDropdown';
24+
import { changeVisibility } from '../../actions/project';
2425

2526
const Toolbar = (props) => {
2627
const { isPlaying, infiniteLoop, preferencesIsVisible } = useSelector(
@@ -32,25 +33,8 @@ const Toolbar = (props) => {
3233
const dispatch = useDispatch();
3334
const { t } = useTranslation();
3435
const userIsOwner = user?.username === project.owner?.username;
35-
const [isPrivate, setIsPrivate] = useState(project.visibility === 'Private');
36-
useEffect(() => {
37-
setIsPrivate(project.visibility === 'Private');
38-
}, [project]);
39-
40-
const showPrivacyToggle = project?.owner && userIsOwner;
41-
const showOwner = project?.owner && !userIsOwner;
4236

43-
const toggleVisibility = (e) => {
44-
try {
45-
const isChecked = e.target.checked;
46-
const newVisibility = isChecked ? 'Private' : 'Public';
47-
setIsPrivate(isChecked);
48-
dispatch(changeVisibility(project.id, project.name, newVisibility));
49-
} catch (error) {
50-
console.log(error);
51-
setIsPrivate(project.visibility === 'Private');
52-
}
53-
};
37+
const showVisibilityDropdown = project?.owner && userIsOwner;
5438

5539
const playButtonClass = classNames({
5640
'toolbar__play-button': true,
@@ -65,6 +49,13 @@ const Toolbar = (props) => {
6549
'toolbar__preferences-button--selected': preferencesIsVisible
6650
});
6751

52+
const handleVisibilityChange = useCallback(
53+
(sketchId, sketchName, newVisibility) => {
54+
dispatch(changeVisibility(sketchId, sketchName, newVisibility));
55+
},
56+
[changeVisibility]
57+
);
58+
6859
return (
6960
<div className="toolbar">
7061
<button
@@ -118,30 +109,34 @@ const Toolbar = (props) => {
118109
{t('Toolbar.Auto-refresh')}
119110
</label>
120111
</div>
112+
121113
<div className="toolbar__project-name-container">
122114
<ProjectName />
123-
{showPrivacyToggle && (
124-
<main className="toolbar__makeprivate">
125-
<p>Private</p>
126-
<input
127-
type="checkbox"
128-
className="toolbar__togglevisibility"
129-
checked={isPrivate}
130-
onChange={toggleVisibility}
115+
116+
{showVisibilityDropdown && (
117+
<div className="toolbar__visibility">
118+
<VisibilityDropdown
119+
sketch={project}
120+
onVisibilityChange={handleVisibilityChange}
131121
/>
132-
</main>
122+
</div>
133123
)}
134-
{showOwner && (
124+
125+
{/* ✅ Still show owner if not you */}
126+
{project?.owner && !userIsOwner && (
135127
<p className="toolbar__project-owner">
136128
{t('Toolbar.By')}{' '}
137129
<Link to={`/${project.owner.username}/sketches`}>
138130
{project.owner.username}
139131
</Link>
140132
</p>
141133
)}
134+
142135
<VersionIndicator />
143136
</div>
137+
144138
<div style={{ flex: 1 }} />
139+
145140
<button
146141
className={preferencesButtonClass}
147142
onClick={() => dispatch(openPreferences())}

client/modules/IDE/components/SketchList.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ const SketchList = ({
148148
context: mobile ? 'mobile' : ''
149149
})
150150
)}
151-
{userIsOwner && renderFieldHeader('makePrivate', 'Make Private')}
151+
{userIsOwner && renderFieldHeader('visibility', 'Visibility')}
152152
<th scope="col"></th>
153153
</tr>
154154
</thead>

client/modules/IDE/components/SketchListRowBase.jsx

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import TableDropdown from '../../../components/Dropdown/TableDropdown';
1010
import MenuItem from '../../../components/Dropdown/MenuItem';
1111
import dates from '../../../utils/formatDate';
1212
import getConfig from '../../../utils/getConfig';
13-
import LockIcon from '../../../images/lock.svg';
14-
import EarthIcon from '../../../images/earth.svg';
13+
import VisibilityDropdown from '../../User/components/VisibilityDropdown';
1514

1615
const ROOT_URL = getConfig('API_URL');
1716

@@ -90,29 +89,11 @@ const SketchListRowBase = ({
9089
}
9190
};
9291

93-
const handleToggleVisibilityChange = (e) => {
94-
const isChecked = e.target.checked;
95-
const newVisibility = isChecked ? 'Private' : 'Public';
96-
changeVisibility(sketch.id, sketch.name, newVisibility);
97-
};
98-
99-
const renderToggleVisibility = () => (
100-
<div>
101-
<input
102-
checked={sketch.visibility === 'Private'}
103-
type="checkbox"
104-
className="visibility__toggle-checkbox"
105-
id={`toggle-${sketch.id}`}
106-
onChange={handleToggleVisibilityChange}
107-
/>
108-
<label
109-
htmlFor={`toggle-${sketch.id}`}
110-
className="visibility__toggle-label"
111-
>
112-
<LockIcon className="lock" />
113-
<EarthIcon className="earth" />
114-
</label>
115-
</div>
92+
const handleVisibilityChange = useCallback(
93+
(sketchId, sketchName, newVisibility) => {
94+
changeVisibility(sketchId, sketchName, newVisibility);
95+
},
96+
[changeVisibility]
11697
);
11798

11899
const userIsOwner = user.username === username;
@@ -144,7 +125,12 @@ const SketchListRowBase = ({
144125
<th scope="row">{name}</th>
145126
<td>{formatDateCell(sketch.createdAt, mobile)}</td>
146127
<td>{formatDateCell(sketch.updatedAt, mobile)}</td>
147-
<td hidden={!userIsOwner}>{renderToggleVisibility()}</td>
128+
<td hidden={!userIsOwner}>
129+
<VisibilityDropdown
130+
sketch={sketch}
131+
onVisibilityChange={handleVisibilityChange}
132+
/>
133+
</td>{' '}
148134
<td className="sketch-list__dropdown-column">
149135
<TableDropdown aria-label={t('SketchList.ToggleLabelARIA')}>
150136
<MenuItem hideIf={!userIsOwner} onClick={openRename}>
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import React, { useState, useRef, useEffect } from 'react';
2+
import PropTypes from 'prop-types';
3+
import LockIcon from '../../../images/lock.svg';
4+
import EarthIcon from '../../../images/earth.svg';
5+
import DownArrowIcon from '../../../images/down-filled-triangle.svg';
6+
import CheckmarkIcon from '../../../images/checkmark.svg';
7+
8+
const VisibilityDropdown = ({ sketch, onVisibilityChange }) => {
9+
const [isOpen, setIsOpen] = useState(false);
10+
const dropdownRef = useRef(null);
11+
12+
const visibilityOptions = [
13+
{
14+
value: 'Public',
15+
label: 'Public',
16+
icon: <EarthIcon className="visibility-icon" />,
17+
description: 'Anyone can see this sketch'
18+
},
19+
{
20+
value: 'Private',
21+
label: 'Private',
22+
icon: <LockIcon className="visibility-icon" />,
23+
description: 'Only you can see this sketch'
24+
}
25+
];
26+
27+
const currentVisibility =
28+
visibilityOptions.find((option) => option.value === sketch.visibility) ||
29+
visibilityOptions[0];
30+
31+
useEffect(() => {
32+
const handleClickOutside = (event) => {
33+
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
34+
setIsOpen(false);
35+
}
36+
};
37+
38+
document.addEventListener('mousedown', handleClickOutside);
39+
return () => document.removeEventListener('mousedown', handleClickOutside);
40+
}, []);
41+
42+
const handleVisibilitySelect = (newVisibility) => {
43+
if (newVisibility !== sketch.visibility) {
44+
onVisibilityChange(sketch.id, sketch.name, newVisibility);
45+
}
46+
setIsOpen(false);
47+
};
48+
49+
const handleKeyDown = (event, visibility) => {
50+
if (event.key === 'Enter' || event.key === ' ') {
51+
event.preventDefault();
52+
handleVisibilitySelect(visibility);
53+
}
54+
};
55+
56+
return (
57+
<div className="visibility-dropdown" ref={dropdownRef}>
58+
<button
59+
className="visibility-dropdown__trigger"
60+
onClick={() => setIsOpen(!isOpen)}
61+
aria-haspopup="true"
62+
aria-expanded={isOpen}
63+
aria-label={`Change visibility. Currently ${currentVisibility.label}`}
64+
>
65+
{currentVisibility.icon}
66+
<span className="visibility-label">{currentVisibility.label}</span>
67+
<DownArrowIcon focusable="false" aria-hidden="true" />
68+
</button>
69+
70+
{isOpen && (
71+
<div className="visibility-dropdown__menu">
72+
{visibilityOptions.map((option) => (
73+
<div
74+
key={option.value}
75+
className={`visibility-dropdown__option ${
76+
option.value === sketch.visibility ? 'selected' : ''
77+
}`}
78+
onClick={() => handleVisibilitySelect(option.value)}
79+
onKeyDown={(e) => handleKeyDown(e, option.value)}
80+
role="button"
81+
tabIndex={0}
82+
>
83+
<div className="visibility-option__main">
84+
{option.icon}
85+
<span className="visibility-option__label">{option.label}</span>
86+
{option.value === sketch.visibility && (
87+
<CheckmarkIcon focusable="false" aria-hidden="true" />
88+
)}
89+
</div>
90+
<div className="visibility-option__description">
91+
{option.description}
92+
</div>
93+
</div>
94+
))}
95+
</div>
96+
)}
97+
</div>
98+
);
99+
};
100+
101+
VisibilityDropdown.propTypes = {
102+
sketch: PropTypes.shape({
103+
id: PropTypes.string.isRequired,
104+
name: PropTypes.string.isRequired,
105+
visibility: PropTypes.string.isRequired
106+
}).isRequired,
107+
onVisibilityChange: PropTypes.func.isRequired
108+
};
109+
110+
export default VisibilityDropdown;

0 commit comments

Comments
 (0)