mirror of
https://github.com/mue/mue.git
synced 2026-07-16 05:23:49 +02:00
feat: replace mui with new style
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import variables from 'config/variables';
|
||||
|
||||
import { useState, memo } from 'react';
|
||||
import { TextareaAutosize } from '@mui/material';
|
||||
import { MdAddLink, MdClose } from 'react-icons/md';
|
||||
import { Tooltip } from 'components/Elements';
|
||||
import { Button } from 'components/Elements';
|
||||
@@ -26,22 +25,24 @@ function AddModal({ urlError, iconError, addLink, closeModal, edit, editData, ed
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="quicklinkModalTextbox">
|
||||
<TextareaAutosize
|
||||
maxRows={1}
|
||||
<input
|
||||
type="text"
|
||||
className="text-field-input"
|
||||
placeholder={variables.getMessage('widgets.quicklinks.name')}
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value.replace(/(\r\n|\n|\r)/gm, ''))}
|
||||
style={{ gridColumn: 'span 2' }}
|
||||
/>
|
||||
<TextareaAutosize
|
||||
maxRows={10}
|
||||
<input
|
||||
type="text"
|
||||
className="text-field-input"
|
||||
placeholder={variables.getMessage('widgets.quicklinks.url')}
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value.replace(/(\r\n|\n|\r)/gm, ''))}
|
||||
/>
|
||||
<TextareaAutosize
|
||||
maxRows={10}
|
||||
maxLines={1}
|
||||
<input
|
||||
type="text"
|
||||
className="text-field-input"
|
||||
placeholder={variables.getMessage('widgets.quicklinks.icon')}
|
||||
value={icon}
|
||||
onChange={(e) => setIcon(e.target.value.replace(/(\r\n|\n|\r)/gm, ''))}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import variables from 'config/variables';
|
||||
import { memo, useState, useCallback } from 'react';
|
||||
import { Checkbox as CheckboxUI, FormControlLabel } from '@mui/material';
|
||||
import { MdCheck } from 'react-icons/md';
|
||||
|
||||
import EventBus from 'utils/eventbus';
|
||||
|
||||
import './Checkbox.scss';
|
||||
|
||||
const Checkbox = memo((props) => {
|
||||
const [checked, setChecked] = useState(localStorage.getItem(props.name) === 'true');
|
||||
|
||||
@@ -32,19 +34,23 @@ const Checkbox = memo((props) => {
|
||||
}, [checked, props]);
|
||||
|
||||
return (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<CheckboxUI
|
||||
name={props.name}
|
||||
color="primary"
|
||||
className="checkbox"
|
||||
checked={checked}
|
||||
onChange={handleChange}
|
||||
disabled={props.disabled || false}
|
||||
/>
|
||||
}
|
||||
label={props.text}
|
||||
/>
|
||||
<label className={`checkbox-wrapper ${props.disabled ? 'disabled' : ''}`}>
|
||||
<span className="checkbox-label">{props.text}</span>
|
||||
<div
|
||||
className={`checkbox-box ${checked ? 'checked' : ''}`}
|
||||
onClick={props.disabled ? undefined : handleChange}
|
||||
>
|
||||
{checked && <MdCheck />}
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
name={props.name}
|
||||
checked={checked}
|
||||
onChange={handleChange}
|
||||
disabled={props.disabled || false}
|
||||
className="checkbox-input"
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
63
src/components/Form/Settings/Checkbox/Checkbox.scss
Normal file
63
src/components/Form/Settings/Checkbox/Checkbox.scss
Normal file
@@ -0,0 +1,63 @@
|
||||
@use 'scss/variables' as *;
|
||||
|
||||
.checkbox-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
padding: 8px 0;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
flex: 1;
|
||||
|
||||
@include themed {
|
||||
color: t($color);
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 6px;
|
||||
transition: 0.2s ease;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
|
||||
@include themed {
|
||||
border: 2px solid t($modal-sidebarActive);
|
||||
background: t($modal-sidebar);
|
||||
color: t($color);
|
||||
|
||||
&:hover {
|
||||
border-color: t($color);
|
||||
}
|
||||
}
|
||||
|
||||
&.checked {
|
||||
@include themed {
|
||||
background: t($link);
|
||||
border-color: t($link);
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 18px;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,7 @@
|
||||
import { useState, memo } from 'react';
|
||||
import { useState, memo, useRef, useEffect } from 'react';
|
||||
import { MdExpandMore, MdClose } from 'react-icons/md';
|
||||
|
||||
import Box from '@mui/material/Box';
|
||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import Select from '@mui/material/Select';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import './ChipSelect.scss';
|
||||
|
||||
function ChipSelect({ label, options, onChange }) {
|
||||
let start = (localStorage.getItem('apiCategories') || '').split(',');
|
||||
@@ -14,47 +9,88 @@ function ChipSelect({ label, options, onChange }) {
|
||||
start = [];
|
||||
}
|
||||
|
||||
const [optionsSelected, setoptionsSelected] = useState(start);
|
||||
const [optionsSelected, setOptionsSelected] = useState(start);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const containerRef = useRef(null);
|
||||
|
||||
const handleChange = (event) => {
|
||||
const {
|
||||
target: { value },
|
||||
} = event;
|
||||
setoptionsSelected(typeof value === 'string' ? value.split(',') : value);
|
||||
localStorage.setItem('apiCategories', value);
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (containerRef.current && !containerRef.current.contains(event.target)) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
const handleToggle = (optionName) => {
|
||||
let newSelected;
|
||||
if (optionsSelected.includes(optionName)) {
|
||||
newSelected = optionsSelected.filter((item) => item !== optionName);
|
||||
} else {
|
||||
newSelected = [...optionsSelected, optionName];
|
||||
}
|
||||
|
||||
setOptionsSelected(newSelected);
|
||||
localStorage.setItem('apiCategories', newSelected.join(','));
|
||||
|
||||
// Call parent onChange if provided
|
||||
if (onChange) {
|
||||
onChange(value);
|
||||
onChange(newSelected);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveChip = (e, optionName) => {
|
||||
e.stopPropagation();
|
||||
handleToggle(optionName);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InputLabel id="chipSelect-label">{label}</InputLabel>
|
||||
<Select
|
||||
labelId="chipSelect-label"
|
||||
id="chipSelect"
|
||||
multiple
|
||||
value={optionsSelected}
|
||||
onChange={handleChange}
|
||||
input={<OutlinedInput id="select-multiple-chip" label={label} />}
|
||||
renderValue={(optionsSelected) => (
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
||||
{optionsSelected.map((value) => (
|
||||
<Chip key={value} label={value} />
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<MenuItem key={option.name} value={option.name}>
|
||||
{option.name.charAt(0).toUpperCase() + option.name.slice(1)}{' '}
|
||||
{option.count && `(${option.count})`}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<div className="chipSelect" ref={containerRef}>
|
||||
{label && <label className="chipSelect-label">{label}</label>}
|
||||
<div className="chipSelect-control" onClick={() => setIsOpen(!isOpen)}>
|
||||
<div className="chipSelect-value">
|
||||
{optionsSelected.length === 0 ? (
|
||||
<span className="chipSelect-placeholder">Select options...</span>
|
||||
) : (
|
||||
<div className="chipSelect-chips">
|
||||
{optionsSelected.map((value) => (
|
||||
<span key={value} className="chipSelect-chip">
|
||||
{value.charAt(0).toUpperCase() + value.slice(1)}
|
||||
<button
|
||||
type="button"
|
||||
className="chipSelect-chip-remove"
|
||||
onClick={(e) => handleRemoveChip(e, value)}
|
||||
>
|
||||
<MdClose />
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<MdExpandMore className={`chipSelect-arrow ${isOpen ? 'open' : ''}`} />
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div className="chipSelect-dropdown">
|
||||
{options.map((option) => (
|
||||
<div
|
||||
key={option.name}
|
||||
className={`chipSelect-option ${optionsSelected.includes(option.name) ? 'selected' : ''}`}
|
||||
onClick={() => handleToggle(option.name)}
|
||||
>
|
||||
<span className="chipSelect-option-checkbox">
|
||||
{optionsSelected.includes(option.name) && '✓'}
|
||||
</span>
|
||||
<span className="chipSelect-option-label">
|
||||
{option.name.charAt(0).toUpperCase() + option.name.slice(1)}
|
||||
{option.count && ` (${option.count})`}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
178
src/components/Form/Settings/ChipSelect/ChipSelect.scss
Normal file
178
src/components/Form/Settings/ChipSelect/ChipSelect.scss
Normal file
@@ -0,0 +1,178 @@
|
||||
@use 'scss/variables' as *;
|
||||
|
||||
.chipSelect {
|
||||
position: relative;
|
||||
width: 300px;
|
||||
margin-top: 10px;
|
||||
|
||||
.chipSelect-label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
|
||||
@include themed {
|
||||
color: t($subColor);
|
||||
}
|
||||
}
|
||||
|
||||
.chipSelect-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 56px;
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
transition: 0.2s ease;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
border: 1px solid t($modal-sidebarActive);
|
||||
border-radius: t($borderRadius);
|
||||
color: t($color);
|
||||
|
||||
&:hover {
|
||||
border-color: t($color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chipSelect-value {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.chipSelect-placeholder {
|
||||
@include themed {
|
||||
color: t($subColor);
|
||||
}
|
||||
}
|
||||
|
||||
.chipSelect-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.chipSelect-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
font-size: 13px;
|
||||
text-transform: capitalize;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-sidebarActive);
|
||||
border-radius: calc(t($borderRadius) / 2);
|
||||
color: t($color);
|
||||
}
|
||||
|
||||
.chipSelect-chip-remove {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2px;
|
||||
margin-left: 2px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
transition: 0.2s ease;
|
||||
|
||||
@include themed {
|
||||
color: t($subColor);
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: t($color);
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chipSelect-arrow {
|
||||
flex-shrink: 0;
|
||||
font-size: 24px;
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
@include themed {
|
||||
color: t($subColor);
|
||||
}
|
||||
|
||||
&.open {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.chipSelect-dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
max-height: 250px;
|
||||
overflow-y: auto;
|
||||
z-index: 100;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-background);
|
||||
border: 1px solid t($modal-sidebarActive);
|
||||
border-radius: t($borderRadius);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.chipSelect-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
transition: 0.2s ease;
|
||||
|
||||
@include themed {
|
||||
color: t($color);
|
||||
|
||||
&:hover {
|
||||
background: t($modal-sidebarActive);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: t($modal-sidebar);
|
||||
}
|
||||
}
|
||||
|
||||
.chipSelect-option-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
|
||||
@include themed {
|
||||
border: 2px solid t($modal-sidebarActive);
|
||||
border-radius: 4px;
|
||||
color: t($color);
|
||||
}
|
||||
}
|
||||
|
||||
&.selected .chipSelect-option-checkbox {
|
||||
@include themed {
|
||||
background: t($modal-sidebarActive);
|
||||
border-color: t($color);
|
||||
}
|
||||
}
|
||||
|
||||
.chipSelect-option-label {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +1,88 @@
|
||||
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 } from 'react-icons/md';
|
||||
|
||||
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 containerRef = useRef(null);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
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 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}
|
||||
>
|
||||
{props.items.map((item) =>
|
||||
item !== null ? (
|
||||
<MenuItem key={id + item.value} value={item.value}>
|
||||
{item.text}
|
||||
</MenuItem>
|
||||
) : null,
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<div className={`dropdown ${id}`} ref={containerRef}>
|
||||
{label && <label className="dropdown-label">{label}</label>}
|
||||
<div className="dropdown-control" onClick={() => setIsOpen(!isOpen)}>
|
||||
<span className="dropdown-value">{selectedItem?.text || value}</span>
|
||||
<MdExpandMore className={`dropdown-arrow ${isOpen ? 'open' : ''}`} />
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div className="dropdown-menu">
|
||||
{props.items.map((item) =>
|
||||
item !== null ? (
|
||||
<div
|
||||
key={id + item.value}
|
||||
className={`dropdown-option ${value === item.value ? 'selected' : ''}`}
|
||||
onClick={() => onChange(item.value)}
|
||||
>
|
||||
{item.text}
|
||||
</div>
|
||||
) : null,
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
98
src/components/Form/Settings/Dropdown/Dropdown.scss
Normal file
98
src/components/Form/Settings/Dropdown/Dropdown.scss
Normal file
@@ -0,0 +1,98 @@
|
||||
@use 'scss/variables' as *;
|
||||
|
||||
.dropdown {
|
||||
position: relative;
|
||||
width: 300px;
|
||||
margin-top: 10px;
|
||||
|
||||
.dropdown-label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
|
||||
@include themed {
|
||||
color: t($subColor);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 56px;
|
||||
padding: 0 16px;
|
||||
cursor: pointer;
|
||||
transition: 0.2s ease;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
border: 1px solid t($modal-sidebarActive);
|
||||
border-radius: t($borderRadius);
|
||||
color: t($color);
|
||||
|
||||
&:hover {
|
||||
border-color: t($color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-value {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dropdown-arrow {
|
||||
flex-shrink: 0;
|
||||
font-size: 24px;
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
@include themed {
|
||||
color: t($subColor);
|
||||
}
|
||||
|
||||
&.open {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
max-height: 250px;
|
||||
overflow-y: auto;
|
||||
z-index: 100;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-background);
|
||||
border: 1px solid t($modal-sidebarActive);
|
||||
border-radius: t($borderRadius);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-option {
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
transition: 0.2s ease;
|
||||
|
||||
@include themed {
|
||||
color: t($color);
|
||||
|
||||
&:hover {
|
||||
background: t($modal-sidebarActive);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: t($modal-sidebar);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,85 +1,86 @@
|
||||
import variables from 'config/variables';
|
||||
import { memo, useState, useCallback } from 'react';
|
||||
import { useTranslation } from 'contexts/TranslationContext';
|
||||
import {
|
||||
Radio as RadioUI,
|
||||
RadioGroup,
|
||||
FormControlLabel,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
} from '@mui/material';
|
||||
|
||||
import EventBus from 'utils/eventbus';
|
||||
|
||||
import './Radio.scss';
|
||||
|
||||
const Radio = memo((props) => {
|
||||
const { changeLanguage } = useTranslation();
|
||||
const [value, setValue] = useState(localStorage.getItem(props.name));
|
||||
|
||||
const handleChange = useCallback(async (e) => {
|
||||
const newValue = e.target.value;
|
||||
const handleChange = useCallback(
|
||||
async (newValue) => {
|
||||
if (newValue === 'loading') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newValue === 'loading') {
|
||||
return;
|
||||
}
|
||||
if (props.name === 'language') {
|
||||
changeLanguage(newValue);
|
||||
setValue(newValue);
|
||||
|
||||
if (props.name === 'language') {
|
||||
// Use context to change language directly - no EventBus needed
|
||||
changeLanguage(newValue);
|
||||
variables.stats.postEvent('setting', `${props.name} from ${value} to ${newValue}`);
|
||||
|
||||
if (props.onChange) {
|
||||
props.onChange(newValue);
|
||||
}
|
||||
|
||||
EventBus.emit('refresh', props.category);
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem(props.name, newValue);
|
||||
setValue(newValue);
|
||||
|
||||
variables.stats.postEvent('setting', `${props.name} from ${value} to ${newValue}`);
|
||||
|
||||
if (props.onChange) {
|
||||
props.onChange(newValue);
|
||||
}
|
||||
|
||||
EventBus.emit('refresh', props.category);
|
||||
return;
|
||||
}
|
||||
variables.stats.postEvent('setting', `${props.name} from ${value} to ${newValue}`);
|
||||
|
||||
localStorage.setItem(props.name, newValue);
|
||||
setValue(newValue);
|
||||
|
||||
if (props.onChange) {
|
||||
props.onChange(newValue);
|
||||
}
|
||||
|
||||
variables.stats.postEvent('setting', `${props.name} from ${value} to ${newValue}`);
|
||||
|
||||
if (props.element) {
|
||||
if (!document.querySelector(props.element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
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, changeLanguage]);
|
||||
EventBus.emit('refresh', props.category);
|
||||
},
|
||||
[value, props, changeLanguage],
|
||||
);
|
||||
|
||||
return (
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel
|
||||
className={props.smallTitle ? 'radio-title-small' : 'radio-title'}
|
||||
component="legend"
|
||||
>
|
||||
{props.title}
|
||||
</FormLabel>
|
||||
<RadioGroup
|
||||
aria-label={props.name}
|
||||
name={props.name}
|
||||
onChange={handleChange}
|
||||
value={value}
|
||||
>
|
||||
<div className="radio-group">
|
||||
{props.title && (
|
||||
<legend className={props.smallTitle ? 'radio-title-small' : 'radio-title'}>
|
||||
{props.title}
|
||||
</legend>
|
||||
)}
|
||||
<div className="radio-options" role="radiogroup" aria-label={props.name}>
|
||||
{props.options.map((option) => (
|
||||
<FormControlLabel
|
||||
value={option.value}
|
||||
control={<RadioUI />}
|
||||
label={option.name}
|
||||
<label
|
||||
key={option.value}
|
||||
/>
|
||||
className={`radio-option ${value === option.value ? 'selected' : ''}`}
|
||||
onClick={() => handleChange(option.value)}
|
||||
>
|
||||
<span className="radio-label">{option.name}</span>
|
||||
<div className="radio-circle">
|
||||
{value === option.value && <div className="radio-dot" />}
|
||||
</div>
|
||||
<input
|
||||
type="radio"
|
||||
name={props.name}
|
||||
value={option.value}
|
||||
checked={value === option.value}
|
||||
onChange={() => handleChange(option.value)}
|
||||
className="radio-input"
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
102
src/components/Form/Settings/Radio/Radio.scss
Normal file
102
src/components/Form/Settings/Radio/Radio.scss
Normal file
@@ -0,0 +1,102 @@
|
||||
@use 'scss/variables' as *;
|
||||
|
||||
.radio-group {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
|
||||
.radio-title {
|
||||
font-weight: bold;
|
||||
font-size: 1.17rem;
|
||||
margin-bottom: 12px;
|
||||
display: block;
|
||||
|
||||
@include themed {
|
||||
color: t($color);
|
||||
}
|
||||
}
|
||||
|
||||
.radio-title-small {
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
|
||||
@include themed {
|
||||
color: t($color);
|
||||
}
|
||||
}
|
||||
|
||||
.radio-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.radio-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
padding: 16px 20px;
|
||||
transition: 0.2s ease;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
border-radius: t($borderRadius);
|
||||
box-shadow: 0 0 0 1px t($modal-sidebarActive);
|
||||
|
||||
&:hover {
|
||||
background: t($modal-secondaryColour);
|
||||
}
|
||||
}
|
||||
|
||||
&.selected .radio-circle {
|
||||
@include themed {
|
||||
border-color: t($link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.radio-label {
|
||||
flex: 1;
|
||||
font-size: 15px;
|
||||
|
||||
@include themed {
|
||||
color: t($color);
|
||||
}
|
||||
}
|
||||
|
||||
.radio-circle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
transition: 0.2s ease;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
margin-left: 20px;
|
||||
|
||||
@include themed {
|
||||
border: 2px solid t($modal-sidebarActive);
|
||||
background: t($modal-secondaryColour);
|
||||
}
|
||||
}
|
||||
|
||||
.radio-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
|
||||
@include themed {
|
||||
background: t($link);
|
||||
}
|
||||
}
|
||||
|
||||
.radio-input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
23
src/components/Form/Settings/SearchInput/SearchInput.jsx
Normal file
23
src/components/Form/Settings/SearchInput/SearchInput.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { memo } from 'react';
|
||||
import { MdSearch } from 'react-icons/md';
|
||||
|
||||
import './SearchInput.scss';
|
||||
|
||||
const SearchInput = memo(({ value, onChange, placeholder, fullWidth }) => {
|
||||
return (
|
||||
<div className={`search-input-container${fullWidth ? ' full-width' : ''}`}>
|
||||
<MdSearch className="search-input-icon" />
|
||||
<input
|
||||
type="text"
|
||||
className="search-input-field"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
SearchInput.displayName = 'SearchInput';
|
||||
|
||||
export { SearchInput as default, SearchInput };
|
||||
48
src/components/Form/Settings/SearchInput/SearchInput.scss
Normal file
48
src/components/Form/Settings/SearchInput/SearchInput.scss
Normal file
@@ -0,0 +1,48 @@
|
||||
@use 'scss/variables' as *;
|
||||
|
||||
.search-input-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 250px;
|
||||
|
||||
&.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-input-icon {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
font-size: 20px;
|
||||
pointer-events: none;
|
||||
|
||||
@include themed {
|
||||
color: t($subColor);
|
||||
}
|
||||
}
|
||||
|
||||
.search-input-field {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
padding: 0 16px 0 44px;
|
||||
font-size: 15px;
|
||||
outline: none;
|
||||
transition: 0.2s ease;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
border: 1px solid t($modal-sidebarActive);
|
||||
border-radius: 24px;
|
||||
color: t($color);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-color: t($color);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: t($subColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/components/Form/Settings/SearchInput/index.jsx
Normal file
1
src/components/Form/Settings/SearchInput/index.jsx
Normal file
@@ -0,0 +1 @@
|
||||
export * from './SearchInput';
|
||||
@@ -1,23 +1,19 @@
|
||||
import variables from 'config/variables';
|
||||
import { memo, useState, useCallback } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { Slider } from '@mui/material';
|
||||
import { MdRefresh } from 'react-icons/md';
|
||||
|
||||
import EventBus from 'utils/eventbus';
|
||||
|
||||
import './Slider.scss';
|
||||
|
||||
const SliderComponent = memo((props) => {
|
||||
const [value, setValue] = useState(localStorage.getItem(props.name) || props.default);
|
||||
|
||||
const handleChange = useCallback((e, text) => {
|
||||
let newValue = e.target.value;
|
||||
newValue = Number(newValue);
|
||||
|
||||
if (text) {
|
||||
if (newValue === '') {
|
||||
setValue(0);
|
||||
return;
|
||||
}
|
||||
const handleChange = useCallback(
|
||||
(e) => {
|
||||
let newValue = e.target.value;
|
||||
newValue = Number(newValue);
|
||||
|
||||
if (newValue > props.max) {
|
||||
newValue = props.max;
|
||||
@@ -26,20 +22,21 @@ const SliderComponent = memo((props) => {
|
||||
if (newValue < props.min) {
|
||||
newValue = props.min;
|
||||
}
|
||||
}
|
||||
|
||||
localStorage.setItem(props.name, newValue);
|
||||
setValue(newValue);
|
||||
localStorage.setItem(props.name, newValue);
|
||||
setValue(newValue);
|
||||
|
||||
if (props.element) {
|
||||
if (!document.querySelector(props.element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
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);
|
||||
}, [props]);
|
||||
EventBus.emit('refresh', props.category);
|
||||
},
|
||||
[props],
|
||||
);
|
||||
|
||||
const resetItem = useCallback(() => {
|
||||
handleChange({
|
||||
@@ -50,9 +47,12 @@ const SliderComponent = memo((props) => {
|
||||
toast(variables.getMessage('toasts.reset'));
|
||||
}, [handleChange, props.default]);
|
||||
|
||||
const percentage =
|
||||
((Number(value) - Number(props.min)) / (Number(props.max) - Number(props.min))) * 100;
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className={'sliderTitle'}>
|
||||
<div className="slider-container">
|
||||
<span className="sliderTitle">
|
||||
{props.title}
|
||||
<span>{Number(value)}</span>
|
||||
<span className="link" onClick={resetItem}>
|
||||
@@ -60,18 +60,34 @@ const SliderComponent = memo((props) => {
|
||||
{variables.getMessage('modals.main.settings.buttons.reset')}
|
||||
</span>
|
||||
</span>
|
||||
<Slider
|
||||
value={Number(value)}
|
||||
onChange={handleChange}
|
||||
valueLabelDisplay="auto"
|
||||
default={Number(props.default)}
|
||||
min={Number(props.min)}
|
||||
max={Number(props.max)}
|
||||
step={Number(props.step) || 1}
|
||||
getAriaValueText={(value) => `${value}`}
|
||||
marks={props.marks || []}
|
||||
/>
|
||||
</>
|
||||
<div className="slider-wrapper">
|
||||
<input
|
||||
type="range"
|
||||
className="slider-input"
|
||||
value={Number(value)}
|
||||
onChange={handleChange}
|
||||
min={Number(props.min)}
|
||||
max={Number(props.max)}
|
||||
step={Number(props.step) || 1}
|
||||
style={{ '--slider-percentage': `${percentage}%` }}
|
||||
/>
|
||||
{props.marks && props.marks.length > 0 && (
|
||||
<div className="slider-marks">
|
||||
{props.marks.map((mark) => (
|
||||
<span
|
||||
key={mark.value}
|
||||
className="slider-mark"
|
||||
style={{
|
||||
left: `${((mark.value - Number(props.min)) / (Number(props.max) - Number(props.min))) * 100}%`,
|
||||
}}
|
||||
>
|
||||
{mark.label}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
123
src/components/Form/Settings/Slider/Slider.scss
Normal file
123
src/components/Form/Settings/Slider/Slider.scss
Normal file
@@ -0,0 +1,123 @@
|
||||
@use 'scss/variables' as *;
|
||||
|
||||
.slider-container {
|
||||
width: 300px;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.sliderTitle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
|
||||
@include themed {
|
||||
color: t($color);
|
||||
}
|
||||
|
||||
> span:first-of-type {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
margin-left: auto;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
|
||||
@include themed {
|
||||
color: t($link);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.slider-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.slider-input {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
|
||||
@include themed {
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
t($link) 0%,
|
||||
t($link) var(--slider-percentage),
|
||||
t($modal-sidebarActive) var(--slider-percentage),
|
||||
t($modal-sidebarActive) 100%
|
||||
);
|
||||
}
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: 0.2s ease;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||
|
||||
@include themed {
|
||||
background: t($color);
|
||||
border: 2px solid t($link);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: 0.2s ease;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||
border: none;
|
||||
|
||||
@include themed {
|
||||
background: t($color);
|
||||
border: 2px solid t($link);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.slider-marks {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
margin-top: 8px;
|
||||
|
||||
.slider-mark {
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
font-size: 12px;
|
||||
|
||||
@include themed {
|
||||
color: t($subColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import variables from 'config/variables';
|
||||
import { memo, useState, useCallback } from 'react';
|
||||
import { Switch as SwitchUI, FormControlLabel } from '@mui/material';
|
||||
|
||||
import EventBus from 'utils/eventbus';
|
||||
|
||||
import './Switch.scss';
|
||||
|
||||
const Switch = memo((props) => {
|
||||
const [checked, setChecked] = useState(localStorage.getItem(props.name) === 'true');
|
||||
|
||||
@@ -32,18 +33,19 @@ const Switch = memo((props) => {
|
||||
}, [checked, props]);
|
||||
|
||||
return (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<SwitchUI
|
||||
name={props.name}
|
||||
color="primary"
|
||||
checked={checked}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
}
|
||||
label={props.header ? '' : props.text}
|
||||
labelPlacement="start"
|
||||
/>
|
||||
<label className="switch-wrapper">
|
||||
{!props.header && <span className="switch-label">{props.text}</span>}
|
||||
<div className={`switch-track ${checked ? 'checked' : ''}`} onClick={handleChange}>
|
||||
<div className="switch-thumb" />
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
name={props.name}
|
||||
checked={checked}
|
||||
onChange={handleChange}
|
||||
className="switch-input"
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
63
src/components/Form/Settings/Switch/Switch.scss
Normal file
63
src/components/Form/Settings/Switch/Switch.scss
Normal file
@@ -0,0 +1,63 @@
|
||||
@use 'scss/variables' as *;
|
||||
|
||||
.switch-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
padding: 8px 0;
|
||||
|
||||
.switch-label {
|
||||
flex: 1;
|
||||
|
||||
@include themed {
|
||||
color: t($color);
|
||||
}
|
||||
}
|
||||
|
||||
.switch-track {
|
||||
position: relative;
|
||||
width: 52px;
|
||||
height: 32px;
|
||||
border-radius: 16px;
|
||||
cursor: pointer;
|
||||
transition: 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-sidebarActive);
|
||||
}
|
||||
|
||||
&.checked {
|
||||
@include themed {
|
||||
background: t($link);
|
||||
}
|
||||
|
||||
.switch-thumb {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.switch-thumb {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
transition: 0.2s ease;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
|
||||
@include themed {
|
||||
background: t($color);
|
||||
}
|
||||
}
|
||||
|
||||
.switch-input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
@@ -1,79 +1,83 @@
|
||||
import variables from 'config/variables';
|
||||
import { memo, useState, useCallback } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { TextField } from '@mui/material';
|
||||
import { MdRefresh } from 'react-icons/md';
|
||||
|
||||
import EventBus from 'utils/eventbus';
|
||||
|
||||
import './Text.scss';
|
||||
|
||||
const Text = memo((props) => {
|
||||
const [value, setValue] = useState(localStorage.getItem(props.name) || '');
|
||||
const { name, upperCaseFirst, element, category, onChange, title, textarea, customcss, placeholder } = props;
|
||||
const defaultValue = props.default;
|
||||
const [value, setValue] = useState(localStorage.getItem(name) || '');
|
||||
|
||||
const handleChange = useCallback((e) => {
|
||||
let { value } = e.target;
|
||||
const handleChange = useCallback(
|
||||
(e) => {
|
||||
let newValue = e.target.value;
|
||||
|
||||
// Alex wanted font to work with montserrat and Montserrat, so I made it work
|
||||
if (props.upperCaseFirst === true) {
|
||||
value = value.charAt(0).toUpperCase() + value.slice(1);
|
||||
}
|
||||
|
||||
localStorage.setItem(props.name, value);
|
||||
setValue(value);
|
||||
|
||||
// Call parent onChange if provided
|
||||
if (props.onChange) {
|
||||
props.onChange(value);
|
||||
}
|
||||
|
||||
if (props.element) {
|
||||
if (!document.querySelector(props.element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
if (upperCaseFirst === true) {
|
||||
newValue = newValue.charAt(0).toUpperCase() + newValue.slice(1);
|
||||
}
|
||||
}
|
||||
|
||||
EventBus.emit('refresh', props.category);
|
||||
}, [props.name, props.upperCaseFirst, props.element, props.category, props.onChange]);
|
||||
localStorage.setItem(name, newValue);
|
||||
setValue(newValue);
|
||||
|
||||
if (onChange) {
|
||||
onChange(newValue);
|
||||
}
|
||||
|
||||
if (element) {
|
||||
if (!document.querySelector(element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
}
|
||||
}
|
||||
|
||||
EventBus.emit('refresh', category);
|
||||
},
|
||||
[name, upperCaseFirst, element, category, onChange],
|
||||
);
|
||||
|
||||
const resetItem = useCallback(() => {
|
||||
handleChange({
|
||||
target: {
|
||||
value: props.default || '',
|
||||
value: defaultValue || '',
|
||||
},
|
||||
});
|
||||
toast(variables.getMessage('toasts.reset'));
|
||||
}, [handleChange, props.default]);
|
||||
}, [handleChange, defaultValue]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.textarea === true ? (
|
||||
<TextField
|
||||
label={props.title}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
varient="outlined"
|
||||
className={props.customcss ? 'customcss' : ''}
|
||||
multiline
|
||||
spellCheck={false}
|
||||
minRows={4}
|
||||
maxRows={10}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
<div className="text-field-container">
|
||||
{textarea === true ? (
|
||||
<div className={`text-field ${customcss ? 'customcss' : ''}`}>
|
||||
{title && <label className="text-field-label">{title}</label>}
|
||||
<textarea
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
spellCheck={false}
|
||||
rows={4}
|
||||
className="text-field-textarea"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<TextField
|
||||
label={props.title}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
varient="outlined"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
placeholder={props.placeholder || ''}
|
||||
/>
|
||||
<div className="text-field">
|
||||
{title && <label className="text-field-label">{title}</label>}
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
placeholder={placeholder || ''}
|
||||
className="text-field-input"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<span className="link" onClick={resetItem}>
|
||||
<MdRefresh />
|
||||
{variables.getMessage('modals.main.settings.buttons.reset')}
|
||||
</span>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
102
src/components/Form/Settings/Text/Text.scss
Normal file
102
src/components/Form/Settings/Text/Text.scss
Normal file
@@ -0,0 +1,102 @@
|
||||
@use 'scss/variables' as *;
|
||||
|
||||
.text-field-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
width: 300px;
|
||||
margin-top: 10px;
|
||||
|
||||
.link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
width: fit-content;
|
||||
|
||||
@include themed {
|
||||
color: t($link);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.text-field-label {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
|
||||
@include themed {
|
||||
color: t($subColor);
|
||||
}
|
||||
}
|
||||
|
||||
.text-field-input {
|
||||
height: 56px;
|
||||
padding: 0 16px;
|
||||
font-size: 16px;
|
||||
outline: none;
|
||||
transition: 0.2s ease;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
border: 1px solid t($modal-sidebarActive);
|
||||
border-radius: t($borderRadius);
|
||||
color: t($color);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-color: t($color);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: t($subColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text-field-textarea {
|
||||
padding: 16px;
|
||||
font-size: 16px;
|
||||
outline: none;
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
transition: 0.2s ease;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
border: 1px solid t($modal-sidebarActive);
|
||||
border-radius: t($borderRadius);
|
||||
color: t($color);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-color: t($color);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: t($subColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.customcss .text-field-textarea {
|
||||
font-family: Consolas, 'Andale Mono WT', 'Andale Mono', 'Lucida Console',
|
||||
'Lucida Sans Typewriter', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Liberation Mono',
|
||||
'Nimbus Mono L', Monaco, 'Courier New', Courier, monospace !important;
|
||||
}
|
||||
}
|
||||
57
src/components/Form/Settings/Textarea/Textarea.jsx
Normal file
57
src/components/Form/Settings/Textarea/Textarea.jsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { memo, useRef, useEffect, useCallback } from 'react';
|
||||
|
||||
import './Textarea.scss';
|
||||
|
||||
const Textarea = memo(({ value, onChange, placeholder, minRows = 1, maxRows, className, style, readOnly }) => {
|
||||
const textareaRef = useRef(null);
|
||||
|
||||
const adjustHeight = useCallback(() => {
|
||||
const textarea = textareaRef.current;
|
||||
if (!textarea) return;
|
||||
|
||||
// Reset height to auto to get the correct scrollHeight
|
||||
textarea.style.height = 'auto';
|
||||
|
||||
// Calculate line height
|
||||
const computedStyle = window.getComputedStyle(textarea);
|
||||
const lineHeight = parseInt(computedStyle.lineHeight) || 24;
|
||||
const paddingTop = parseInt(computedStyle.paddingTop) || 0;
|
||||
const paddingBottom = parseInt(computedStyle.paddingBottom) || 0;
|
||||
|
||||
// Calculate min and max heights
|
||||
const minHeight = (minRows * lineHeight) + paddingTop + paddingBottom;
|
||||
const maxHeight = maxRows ? (maxRows * lineHeight) + paddingTop + paddingBottom : Infinity;
|
||||
|
||||
// Set the height based on content, clamped between min and max
|
||||
const newHeight = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);
|
||||
textarea.style.height = `${newHeight}px`;
|
||||
}, [minRows, maxRows]);
|
||||
|
||||
useEffect(() => {
|
||||
adjustHeight();
|
||||
}, [value, adjustHeight]);
|
||||
|
||||
// Adjust on mount and window resize
|
||||
useEffect(() => {
|
||||
adjustHeight();
|
||||
window.addEventListener('resize', adjustHeight);
|
||||
return () => window.removeEventListener('resize', adjustHeight);
|
||||
}, [adjustHeight]);
|
||||
|
||||
return (
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
className={`textarea-autosize${className ? ` ${className}` : ''}`}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
style={style}
|
||||
readOnly={readOnly}
|
||||
rows={minRows}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
Textarea.displayName = 'Textarea';
|
||||
|
||||
export { Textarea as default, Textarea };
|
||||
34
src/components/Form/Settings/Textarea/Textarea.scss
Normal file
34
src/components/Form/Settings/Textarea/Textarea.scss
Normal file
@@ -0,0 +1,34 @@
|
||||
@use 'scss/variables' as *;
|
||||
|
||||
.textarea-autosize {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
outline: none;
|
||||
resize: none;
|
||||
overflow: hidden;
|
||||
transition: 0.2s ease;
|
||||
font-family: inherit;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
border: 1px solid t($modal-sidebarActive);
|
||||
border-radius: t($borderRadius);
|
||||
color: t($color);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-color: t($color);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: t($subColor);
|
||||
}
|
||||
}
|
||||
|
||||
&[readonly] {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
1
src/components/Form/Settings/Textarea/index.jsx
Normal file
1
src/components/Form/Settings/Textarea/index.jsx
Normal file
@@ -0,0 +1 @@
|
||||
export * from './Textarea';
|
||||
@@ -3,6 +3,8 @@ export * from './ChipSelect';
|
||||
export * from './Dropdown';
|
||||
export * from './FileUpload';
|
||||
export * from './Radio';
|
||||
export * from './SearchInput';
|
||||
export * from './Slider';
|
||||
export * from './Switch';
|
||||
export * from './Text';
|
||||
export * from './Textarea';
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
Section,
|
||||
} from 'components/Layout/Settings';
|
||||
import { Checkbox, Switch, Text } from 'components/Form/Settings';
|
||||
import { TextareaAutosize } from '@mui/material';
|
||||
import { Button } from 'components/Elements';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
@@ -192,15 +191,15 @@ const GreetingOptions = ({ currentSubSection, onSubSectionChange, sectionName })
|
||||
<span className="subtitle">
|
||||
{variables.getMessage(`${GREETING_SECTION}.event_name`)}
|
||||
</span>
|
||||
<TextareaAutosize
|
||||
<input
|
||||
type="text"
|
||||
className="text-field-input"
|
||||
value={event.name}
|
||||
placeholder={variables.getMessage(`${GREETING_SECTION}.event_name`)}
|
||||
onChange={(e) => {
|
||||
const updatedEvent = { ...event, name: e.target.value };
|
||||
updateEvent(index, updatedEvent);
|
||||
}}
|
||||
varient="outlined"
|
||||
style={{ padding: '0' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,9 +2,9 @@ import variables from 'config/variables';
|
||||
import { useState } from 'react';
|
||||
import { MdCancel, MdAdd, MdOutlineTextsms } from 'react-icons/md';
|
||||
import { toast } from 'react-toastify';
|
||||
import { TextareaAutosize } from '@mui/material';
|
||||
|
||||
import { Header, Row, Content, Action, PreferencesWrapper } from 'components/Layout/Settings';
|
||||
import { Textarea } from 'components/Form/Settings';
|
||||
import { Button } from 'components/Elements';
|
||||
import EventBus from 'utils/eventbus';
|
||||
|
||||
@@ -82,14 +82,13 @@ const MessageOptions = () => {
|
||||
<span className="subtitle">
|
||||
{variables.getMessage(`${MESSAGE_SECTION}.title`)}
|
||||
</span>
|
||||
<TextareaAutosize
|
||||
<Textarea
|
||||
value={messages[index]}
|
||||
placeholder={variables.getMessage(
|
||||
'modals.main.settings.sections.message.content',
|
||||
)}
|
||||
onChange={(e) => message(e, true, index)}
|
||||
varient="outlined"
|
||||
style={{ padding: '0' }}
|
||||
minRows={2}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,6 @@ import variables from 'config/variables';
|
||||
import { useState, memo } from 'react';
|
||||
import { Checkbox, Slider } from 'components/Form/Settings';
|
||||
import { Button } from 'components/Elements';
|
||||
import { TextField } from '@mui/material';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import EventBus from 'utils/eventbus';
|
||||
@@ -39,22 +38,26 @@ function ExperimentalOptions() {
|
||||
element=".other"
|
||||
/>
|
||||
<p style={{ textAlign: 'left', width: '100%' }}>Send Event</p>
|
||||
<TextField
|
||||
label={'Type'}
|
||||
value={eventType}
|
||||
onChange={(e) => setEventType(e.target.value)}
|
||||
spellCheck={false}
|
||||
varient="outlined"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
<TextField
|
||||
label={'Name'}
|
||||
value={eventName}
|
||||
onChange={(e) => setEventName(e.target.value)}
|
||||
spellCheck={false}
|
||||
varient="outlined"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
<div className="text-field">
|
||||
<label className="text-field-label">Type</label>
|
||||
<input
|
||||
type="text"
|
||||
className="text-field-input"
|
||||
value={eventType || ''}
|
||||
onChange={(e) => setEventType(e.target.value)}
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-field">
|
||||
<label className="text-field-label">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
className="text-field-input"
|
||||
value={eventName || ''}
|
||||
onChange={(e) => setEventName(e.target.value)}
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="settings"
|
||||
onClick={() => EventBus.emit(eventType, eventName)}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useT, useTranslation } from 'contexts/TranslationContext';
|
||||
|
||||
import { MdOutlineOpenInNew, MdSearch, MdComputer } from 'react-icons/md';
|
||||
import { TextField, InputAdornment } from '@mui/material';
|
||||
import { MdOutlineOpenInNew, MdComputer } from 'react-icons/md';
|
||||
|
||||
import { Radio, Checkbox } from 'components/Form/Settings';
|
||||
import { Radio, Checkbox, SearchInput } from 'components/Form/Settings';
|
||||
|
||||
import languages from '@/i18n/languages.json';
|
||||
import translationPercentages from '@/i18n/translationPercentages.json';
|
||||
@@ -123,35 +122,10 @@ const LanguageOptions = () => {
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
<SearchInput
|
||||
placeholder={t('modals.main.settings.sections.language.search')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<MdSearch style={{ color: '#888' }} />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
sx={{
|
||||
width: '250px',
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '24px',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.08)',
|
||||
'& fieldset': {
|
||||
border: 'none',
|
||||
},
|
||||
'&:hover fieldset': {
|
||||
border: 'none',
|
||||
},
|
||||
'&.Mui-focused fieldset': {
|
||||
border: 'none',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{currentLangOption && (
|
||||
<div style={{ color: '#888', whiteSpace: 'nowrap' }}>
|
||||
|
||||
@@ -4,9 +4,9 @@ import { useT } from 'contexts';
|
||||
|
||||
import { MdContentCopy, MdAssignment, MdPushPin, MdDownload } from 'react-icons/md';
|
||||
import { useFloating, shift } from '@floating-ui/react-dom';
|
||||
import TextareaAutosize from '@mui/material/TextareaAutosize';
|
||||
import { toast } from 'react-toastify';
|
||||
import { Tooltip } from 'components/Elements';
|
||||
import { Textarea } from 'components/Form/Settings';
|
||||
|
||||
import { saveFile } from 'utils/saveFile';
|
||||
import EventBus from 'utils/eventbus';
|
||||
@@ -112,12 +112,11 @@ const Notes = ({ notesRef, floatRef, position, xPosition, yPosition }) => {
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<TextareaAutosize
|
||||
<Textarea
|
||||
placeholder={t('widgets.navbar.notes.placeholder')}
|
||||
value={notes}
|
||||
onChange={handleSetNotes}
|
||||
minRows={5}
|
||||
maxLength={10000}
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
@@ -9,11 +9,10 @@ import {
|
||||
MdPlaylistAdd,
|
||||
MdOutlineDragIndicator,
|
||||
MdPlaylistRemove,
|
||||
MdCheck,
|
||||
} from 'react-icons/md';
|
||||
import TextareaAutosize from '@mui/material/TextareaAutosize';
|
||||
import { Tooltip } from 'components/Elements';
|
||||
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import { Textarea } from 'components/Form/Settings';
|
||||
import { shift, useFloating } from '@floating-ui/react-dom';
|
||||
import {
|
||||
DndContext,
|
||||
@@ -210,15 +209,18 @@ function Todo({ todoRef, floatRef, position, xPosition, yPosition }) {
|
||||
<SortableItem key={index} id={index}>
|
||||
{({ attributes, listeners }) => (
|
||||
<div className={'todoRow' + (todoItem.done ? ' done' : '')}>
|
||||
<Checkbox
|
||||
checked={todoItem.done}
|
||||
<div
|
||||
className={'todo-checkbox' + (todoItem.done ? ' checked' : '')}
|
||||
onClick={() => updateTodo('done', index)}
|
||||
/>
|
||||
<TextareaAutosize
|
||||
>
|
||||
{todoItem.done && <MdCheck />}
|
||||
</div>
|
||||
<Textarea
|
||||
placeholder={t('widgets.navbar.notes.placeholder')}
|
||||
value={todoItem.value}
|
||||
onChange={(data) => updateTodo('set', index, data)}
|
||||
readOnly={todoItem.done}
|
||||
minRows={1}
|
||||
/>
|
||||
<Tooltip
|
||||
title={t(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import variables from 'config/variables';
|
||||
import React, { useState } from 'react';
|
||||
import { MdCancel, MdAdd, MdSource, MdOutlineFormatQuote } from 'react-icons/md';
|
||||
import TextareaAutosize from '@mui/material/TextareaAutosize';
|
||||
|
||||
import {
|
||||
Header,
|
||||
@@ -11,7 +10,7 @@ import {
|
||||
Section,
|
||||
PreferencesWrapper,
|
||||
} from 'components/Layout/Settings';
|
||||
import { Checkbox, Dropdown } from 'components/Form/Settings';
|
||||
import { Checkbox, Dropdown, Textarea } from 'components/Form/Settings';
|
||||
import { Button } from 'components/Elements';
|
||||
|
||||
const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) => {
|
||||
@@ -166,23 +165,23 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) =>
|
||||
<MdOutlineFormatQuote />
|
||||
</div>
|
||||
<div className="messageText">
|
||||
<TextareaAutosize
|
||||
<Textarea
|
||||
value={customQuote[index].quote}
|
||||
placeholder={variables.getMessage(
|
||||
'modals.main.settings.sections.quote.title',
|
||||
)}
|
||||
onChange={(e) => handleCustomQuote(e, true, index, 'quote')}
|
||||
varient="outlined"
|
||||
style={{ fontSize: '22px', fontWeight: 'bold' }}
|
||||
minRows={1}
|
||||
/>
|
||||
<TextareaAutosize
|
||||
<Textarea
|
||||
value={customQuote[index].author}
|
||||
placeholder={variables.getMessage(
|
||||
'modals.main.settings.sections.quote.author',
|
||||
)}
|
||||
className="subtitle"
|
||||
onChange={(e) => handleCustomQuote(e, true, index, 'author')}
|
||||
varient="outlined"
|
||||
minRows={1}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -3,7 +3,6 @@ import { MdAutoAwesome } from 'react-icons/md';
|
||||
import { Header, Row, Content, Action, PreferencesWrapper } from 'components/Layout/Settings';
|
||||
import { useLocalStorageState } from 'utils/useLocalStorageState';
|
||||
import { Radio, Dropdown, Checkbox } from 'components/Form/Settings';
|
||||
import { TextField } from '@mui/material';
|
||||
import variables from 'config/variables';
|
||||
|
||||
const useWeatherSettings = () => {
|
||||
@@ -82,18 +81,24 @@ const WeatherOptions = () => {
|
||||
<Row>
|
||||
<Content title={variables.getMessage(`${WEATHER_SECTION}.location`)} />
|
||||
<Action>
|
||||
<TextField
|
||||
label={variables.getMessage(`${WEATHER_SECTION}.location`)}
|
||||
value={location}
|
||||
onChange={changeLocation}
|
||||
placeholder="London"
|
||||
variant="outlined"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
<span className="link" onClick={getAutoLocation}>
|
||||
<MdAutoAwesome />
|
||||
{variables.getMessage(`${WEATHER_SECTION}.auto`)}
|
||||
</span>
|
||||
<div className="text-field-container">
|
||||
<div className="text-field">
|
||||
<label className="text-field-label">
|
||||
{variables.getMessage(`${WEATHER_SECTION}.location`)}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="text-field-input"
|
||||
value={location}
|
||||
onChange={changeLocation}
|
||||
placeholder="London"
|
||||
/>
|
||||
</div>
|
||||
<span className="link" onClick={getAutoLocation}>
|
||||
<MdAutoAwesome />
|
||||
{variables.getMessage(`${WEATHER_SECTION}.auto`)}
|
||||
</span>
|
||||
</div>
|
||||
</Action>
|
||||
</Row>
|
||||
);
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { MdOutlineOpenInNew, MdSearch } from 'react-icons/md';
|
||||
import { TextField, InputAdornment } from '@mui/material';
|
||||
import { MdOutlineOpenInNew } from 'react-icons/md';
|
||||
import languages from '@/i18n/languages.json';
|
||||
import translationPercentages from '@/i18n/translationPercentages.json';
|
||||
import { useT, useTranslation } from 'contexts/TranslationContext';
|
||||
import variables from 'config/variables';
|
||||
|
||||
import { Radio } from 'components/Form/Settings';
|
||||
import { Radio, SearchInput } from 'components/Form/Settings';
|
||||
import { Header, Content } from '../Layout';
|
||||
|
||||
function ChooseLanguage() {
|
||||
@@ -107,37 +106,14 @@ function ChooseLanguage() {
|
||||
{t('modals.main.settings.sections.language.use_system')} ({systemLanguage.name})
|
||||
</button>
|
||||
)}
|
||||
<TextField
|
||||
placeholder={t('modals.main.settings.sections.language.search')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
fullWidth
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<MdSearch style={{ color: '#888' }} />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
sx={{
|
||||
marginBottom: 2,
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '24px',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.08)',
|
||||
'& fieldset': {
|
||||
border: 'none',
|
||||
},
|
||||
'&:hover fieldset': {
|
||||
border: 'none',
|
||||
},
|
||||
'&.Mui-focused fieldset': {
|
||||
border: 'none',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<SearchInput
|
||||
placeholder={t('modals.main.settings.sections.language.search')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
<div className="languageSettings">
|
||||
<Radio name="language" options={filteredLanguages} category="welcomeLanguage" />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user