mirror of
https://github.com/mue/mue.git
synced 2026-07-16 21:44:22 +02:00
Dev (#1134)
* feat: add professional three-branch release workflow automation (#1129) - Add version-bump workflow for semantic versioning across all files - Add beta-release workflow for automated pre-release testing - Add production-release workflow with manual approval gates - Add hotfix-release workflow for emergency patches - Create comprehensive CONTRIBUTING.md with workflow guide - Create detailed RELEASE_PROCESS.md for maintainers - Add PR template with release checklists - Update CODEOWNERS to protect workflow files - Update README with contribution links - Remove /docs from .gitignore to allow documentation This implements a dev beta main branching strategy with: - Automated version management across 6 files - Changelog generation from conventional commits - GitHub Releases with build artifacts - Environment-based approvals for production - Back-merge support for hotfixes * feat: new default quotes experience, improve added page * Fix/beta workflow version check (#1131) * fix(workflows): prevent beta release for non-beta versions * fix(workflows): address copilot PR review feedback - Support iterative beta versions (7.6.0-beta.1 -> 7.6.0-beta.2) - Remove tag trigger from beta workflow to prevent premature releases - Fix tag format in docs/summaries to include 'v' prefix - Clarify deployment approval wording * feat: replace mui with new style * feat: improve time formatting in Clock component with padded digits * fix: change Checkbox component from label to div for better semantics * fix: change Switch component from label to div for better semantics * feat: add smooth animation to reset functionality in Slider component * feat: enhance accessibility and styling for form components including Checkbox, Dropdown, Radio, Slider, and Text * feat: enhance WeatherOptions component with improved layout and auto location reset functionality * feat: update Slider and Dropdown components with improved layout and z-index adjustments * feat: add reset functionality to Dropdown component with toast notification * feat: update Dropdown component styles for improved layout and structure * feat: update languageSettings component with increased padding for better spacing * feat: bump version to 7.6.0 across all manifests and documentation --------- Signed-off-by: Alex Sparkes <alexsparkes@gmail.com> Co-authored-by: David Ralph <me@davidcralph.co.uk>
This commit is contained in:
@@ -1,69 +1,165 @@
|
||||
import variables from 'config/variables';
|
||||
import { memo, useState, useCallback, useRef } from 'react';
|
||||
import { InputLabel, MenuItem, FormControl, Select } from '@mui/material';
|
||||
import { memo, useState, useCallback, useRef, useEffect } from 'react';
|
||||
import { MdExpandMore, MdCheck, MdRefresh } from 'react-icons/md';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import EventBus from 'utils/eventbus';
|
||||
|
||||
import './Dropdown.scss';
|
||||
|
||||
const Dropdown = memo((props) => {
|
||||
const [value, setValue] = useState(
|
||||
localStorage.getItem(props.name) || props.items[0].value,
|
||||
localStorage.getItem(props.name) || props.items[0]?.value,
|
||||
);
|
||||
const dropdown = useRef();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [focusedIndex, setFocusedIndex] = useState(-1);
|
||||
const containerRef = useRef(null);
|
||||
const optionsRef = useRef([]);
|
||||
|
||||
const onChange = useCallback((e) => {
|
||||
const newValue = e.target.value;
|
||||
|
||||
if (newValue === variables.getMessage('modals.main.loading')) {
|
||||
return;
|
||||
}
|
||||
|
||||
variables.stats.postEvent('setting', `${props.name} from ${value} to ${newValue}`);
|
||||
|
||||
setValue(newValue);
|
||||
|
||||
if (!props.noSetting) {
|
||||
localStorage.setItem(props.name, newValue);
|
||||
localStorage.setItem(props.name2, props.value2);
|
||||
}
|
||||
|
||||
if (props.onChange) {
|
||||
props.onChange(newValue);
|
||||
}
|
||||
|
||||
if (props.element) {
|
||||
if (!document.querySelector(props.element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (containerRef.current && !containerRef.current.contains(event.target)) {
|
||||
setIsOpen(false);
|
||||
setFocusedIndex(-1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
EventBus.emit('refresh', props.category);
|
||||
}, [value, props]);
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
const onChange = useCallback(
|
||||
(newValue) => {
|
||||
if (newValue === variables.getMessage('modals.main.loading')) {
|
||||
return;
|
||||
}
|
||||
|
||||
variables.stats.postEvent('setting', `${props.name} from ${value} to ${newValue}`);
|
||||
|
||||
setValue(newValue);
|
||||
setIsOpen(false);
|
||||
setFocusedIndex(-1);
|
||||
|
||||
if (!props.noSetting) {
|
||||
localStorage.setItem(props.name, newValue);
|
||||
localStorage.setItem(props.name2, props.value2);
|
||||
}
|
||||
|
||||
if (props.onChange) {
|
||||
props.onChange(newValue);
|
||||
}
|
||||
|
||||
if (props.element) {
|
||||
if (!document.querySelector(props.element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
}
|
||||
}
|
||||
|
||||
EventBus.emit('refresh', props.category);
|
||||
},
|
||||
[value, props],
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e) => {
|
||||
if (props.disabled) return;
|
||||
|
||||
switch (e.key) {
|
||||
case 'Enter':
|
||||
case ' ':
|
||||
e.preventDefault();
|
||||
setIsOpen(!isOpen);
|
||||
break;
|
||||
case 'Escape':
|
||||
setIsOpen(false);
|
||||
setFocusedIndex(-1);
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
if (!isOpen) {
|
||||
setIsOpen(true);
|
||||
} else {
|
||||
setFocusedIndex((prev) => (prev < props.items.filter((i) => i !== null).length - 1 ? prev + 1 : prev));
|
||||
}
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
if (isOpen) {
|
||||
setFocusedIndex((prev) => (prev > 0 ? prev - 1 : prev));
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
[isOpen, props.items, props.disabled],
|
||||
);
|
||||
|
||||
const handleOptionKeyDown = useCallback(
|
||||
(e, item) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
onChange(item.value);
|
||||
}
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
const resetItem = useCallback(() => {
|
||||
const defaultValue = props.default || props.items[0]?.value;
|
||||
onChange(defaultValue);
|
||||
toast(variables.getMessage('toasts.reset'));
|
||||
}, [onChange, props.default, props.items]);
|
||||
|
||||
const id = 'dropdown' + props.name;
|
||||
const label = props.label || '';
|
||||
const selectedItem = props.items.find((item) => item?.value === value);
|
||||
|
||||
return (
|
||||
<FormControl fullWidth className={id}>
|
||||
<InputLabel id={id}>{label}</InputLabel>
|
||||
<Select
|
||||
labelId={id}
|
||||
id={props.name}
|
||||
value={value}
|
||||
label={label}
|
||||
onChange={onChange}
|
||||
ref={dropdown}
|
||||
key={id}
|
||||
<div className={`dropdown ${id} ${props.disabled ? 'disabled' : ''}`} ref={containerRef}>
|
||||
{label && (
|
||||
<div className="dropdown-header">
|
||||
<label className="dropdown-label">{label}</label>
|
||||
<span className="dropdown-reset" onClick={resetItem}>
|
||||
<MdRefresh />
|
||||
{variables.getMessage('modals.main.settings.buttons.reset')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="dropdown-control"
|
||||
onClick={() => !props.disabled && setIsOpen(!isOpen)}
|
||||
onKeyDown={handleKeyDown}
|
||||
role="button"
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={isOpen}
|
||||
aria-label={label || props.name}
|
||||
tabIndex={props.disabled ? -1 : 0}
|
||||
>
|
||||
{props.items.map((item) =>
|
||||
item !== null ? (
|
||||
<MenuItem key={id + item.value} value={item.value}>
|
||||
{item.text}
|
||||
</MenuItem>
|
||||
) : null,
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<span className="dropdown-value">{selectedItem?.text || value}</span>
|
||||
<MdExpandMore className={`dropdown-arrow ${isOpen ? 'open' : ''}`} />
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div className="dropdown-menu" role="listbox">
|
||||
{props.items.map((item, index) =>
|
||||
item !== null ? (
|
||||
<div
|
||||
key={id + item.value}
|
||||
ref={(el) => (optionsRef.current[index] = el)}
|
||||
className={`dropdown-option ${value === item.value ? 'selected' : ''} ${index === focusedIndex ? 'focused' : ''}`}
|
||||
onClick={() => onChange(item.value)}
|
||||
onKeyDown={(e) => handleOptionKeyDown(e, item)}
|
||||
role="option"
|
||||
aria-selected={value === item.value}
|
||||
tabIndex={0}
|
||||
>
|
||||
<span className="dropdown-option-text">{item.text}</span>
|
||||
{value === item.value && <MdCheck className="dropdown-option-check" />}
|
||||
</div>
|
||||
) : null,
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user