mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-17 18:13:36 +00:00
feat: enhance the appearance of scrollable setting menu in the mobile view
This commit is contained in:
1 parent
802311a06a
commit
b05fec93bb
2 files changed
+98
-49
No files matched your search
@@ -0,0 +1,96 @@
|
|||||||
|
import React, { useCallback, useRef, useState } from 'react';
|
||||||
|
import ArrowLeft from '../assets/arrow-left.svg';
|
||||||
|
import ArrowRight from '../assets/arrow-right.svg';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
type HiddenGradientType = 'left' | 'right' | undefined;
|
||||||
|
|
||||||
|
const useTabs = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const tabs = [
|
||||||
|
t('settings.general.label'),
|
||||||
|
t('settings.documents.label'),
|
||||||
|
t('settings.apiKeys.label'),
|
||||||
|
t('settings.analytics.label'),
|
||||||
|
t('settings.logs.label'),
|
||||||
|
];
|
||||||
|
return tabs;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface SettingsBarProps {
|
||||||
|
setActiveTab: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
activeTab: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SettingsBar = ({ setActiveTab, activeTab }: SettingsBarProps) => {
|
||||||
|
const [hiddenGradient, setHiddenGradient] =
|
||||||
|
useState<HiddenGradientType>('left');
|
||||||
|
const containerRef = useRef<null | HTMLDivElement>(null);
|
||||||
|
const tabs = useTabs();
|
||||||
|
const scrollTabs = useCallback(
|
||||||
|
(direction: number) => {
|
||||||
|
if (containerRef.current) {
|
||||||
|
const container = containerRef.current;
|
||||||
|
container.scrollLeft += direction * 100; // Adjust the scroll amount as needed
|
||||||
|
if (container.scrollLeft === 0) {
|
||||||
|
setHiddenGradient('left');
|
||||||
|
} else if (
|
||||||
|
container.scrollLeft + container.offsetWidth ===
|
||||||
|
container.scrollWidth
|
||||||
|
) {
|
||||||
|
setHiddenGradient('right');
|
||||||
|
} else {
|
||||||
|
setHiddenGradient(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[containerRef.current],
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div className="relative mt-6 flex flex-row items-center space-x-1 md:space-x-0 overflow-auto">
|
||||||
|
<div
|
||||||
|
className={`${hiddenGradient === 'left' ? 'hidden' : ''} md:hidden absolute inset-y-0 left-6 w-14 bg-gradient-to-r from-white dark:from-raisin-black pointer-events-none`}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className={`${hiddenGradient === 'right' ? 'hidden' : ''} md:hidden absolute inset-y-0 right-6 w-14 bg-gradient-to-l from-white dark:from-raisin-black pointer-events-none`}
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div className="md:hidden z-10">
|
||||||
|
<button
|
||||||
|
onClick={() => scrollTabs(-1)}
|
||||||
|
className="flex h-6 w-6 items-center rounded-full justify-center transition-all hover:bg-gray-100"
|
||||||
|
>
|
||||||
|
<img src={ArrowLeft} alt="left-arrow" className="h-3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
className="flex flex-nowrap overflow-x-auto no-scrollbar md:space-x-4 scroll-smooth snap-x"
|
||||||
|
>
|
||||||
|
{tabs.map((tab, index) => (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
onClick={() => setActiveTab(tab)}
|
||||||
|
className={`snap-start h-9 rounded-3xl px-4 font-bold ${
|
||||||
|
activeTab === tab
|
||||||
|
? 'bg-purple-3000 text-purple-30 dark:bg-dark-charcoal'
|
||||||
|
: 'text-gray-6000'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{tab}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="md:hidden z-10">
|
||||||
|
<button
|
||||||
|
onClick={() => scrollTabs(1)}
|
||||||
|
className="flex h-6 w-6 rounded-full items-center justify-center hover:bg-gray-100"
|
||||||
|
>
|
||||||
|
<img src={ArrowRight} alt="right-arrow" className="h-3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SettingsBar;
|
||||||
@@ -3,8 +3,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import userService from '../api/services/userService';
|
import userService from '../api/services/userService';
|
||||||
import ArrowLeft from '../assets/arrow-left.svg';
|
import SettingsBar from '../components/SettingsBar';
|
||||||
import ArrowRight from '../assets/arrow-right.svg';
|
|
||||||
import i18n from '../locale/i18n';
|
import i18n from '../locale/i18n';
|
||||||
import { Doc } from '../models/misc';
|
import { Doc } from '../models/misc';
|
||||||
import {
|
import {
|
||||||
@@ -21,13 +20,6 @@ import Widgets from './Widgets';
|
|||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const tabs = [
|
|
||||||
t('settings.general.label'),
|
|
||||||
t('settings.documents.label'),
|
|
||||||
t('settings.apiKeys.label'),
|
|
||||||
t('settings.analytics.label'),
|
|
||||||
t('settings.logs.label'),
|
|
||||||
];
|
|
||||||
const [activeTab, setActiveTab] = React.useState(t('settings.general.label'));
|
const [activeTab, setActiveTab] = React.useState(t('settings.general.label'));
|
||||||
const [widgetScreenshot, setWidgetScreenshot] = React.useState<File | null>(
|
const [widgetScreenshot, setWidgetScreenshot] = React.useState<File | null>(
|
||||||
null,
|
null,
|
||||||
@@ -61,39 +53,7 @@ export default function Settings() {
|
|||||||
<p className="text-2xl font-bold text-eerie-black dark:text-bright-gray">
|
<p className="text-2xl font-bold text-eerie-black dark:text-bright-gray">
|
||||||
{t('settings.label')}
|
{t('settings.label')}
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-6 flex flex-row items-center space-x-4 overflow-auto md:space-x-8 ">
|
<SettingsBar activeTab={activeTab} setActiveTab={setActiveTab} />
|
||||||
<div className="md:hidden">
|
|
||||||
<button
|
|
||||||
onClick={() => scrollTabs(-1)}
|
|
||||||
className="flex h-8 w-8 items-center justify-center rounded-full border-2 border-purple-30 transition-all hover:bg-gray-100"
|
|
||||||
>
|
|
||||||
<img src={ArrowLeft} alt="left-arrow" className="h-6 w-6" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-nowrap space-x-4 overflow-x-auto no-scrollbar md:space-x-8 md:!mx-0">
|
|
||||||
{tabs.map((tab, index) => (
|
|
||||||
<button
|
|
||||||
key={index}
|
|
||||||
onClick={() => setActiveTab(tab)}
|
|
||||||
className={`h-9 rounded-3xl px-4 font-bold ${
|
|
||||||
activeTab === tab
|
|
||||||
? 'bg-purple-3000 text-purple-30 dark:bg-dark-charcoal'
|
|
||||||
: 'text-gray-6000'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{tab}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="md:hidden">
|
|
||||||
<button
|
|
||||||
onClick={() => scrollTabs(1)}
|
|
||||||
className="flex h-8 w-8 items-center justify-center rounded-full border-2 border-purple-30 hover:bg-gray-100"
|
|
||||||
>
|
|
||||||
<img src={ArrowRight} alt="right-arrow" className="h-6 w-6" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{renderActiveTab()}
|
{renderActiveTab()}
|
||||||
|
|
||||||
{/* {activeTab === 'Widgets' && (
|
{/* {activeTab === 'Widgets' && (
|
||||||
@@ -105,13 +65,6 @@ export default function Settings() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
function scrollTabs(direction: number) {
|
|
||||||
const container = document.querySelector('.flex-nowrap');
|
|
||||||
if (container) {
|
|
||||||
container.scrollLeft += direction * 100; // Adjust the scroll amount as needed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderActiveTab() {
|
function renderActiveTab() {
|
||||||
switch (activeTab) {
|
switch (activeTab) {
|
||||||
case t('settings.general.label'):
|
case t('settings.general.label'):
|
||||||
|
|||||||
Reference in new issue
Block a user