Skip to content

Commit 0f9df73

Browse files
committed
feat: Optimize UI
1 parent 2bbbfef commit 0f9df73

File tree

17 files changed

+142
-45
lines changed

17 files changed

+142
-45
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"@radix-ui/react-switch": "^1.1.0",
5353
"@radix-ui/react-tabs": "^1.0.4",
5454
"@radix-ui/react-toggle": "^1.0.3",
55+
"@radix-ui/react-visually-hidden": "^1.1.0",
5556
"@reduxjs/toolkit": "^1.9.5",
5657
"@snyk/protect": "^1.1200.0",
5758
"@testing-library/jest-dom": "^5.17.0",

src/AppContainer.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Provider as ReduxProvider } from 'react-redux'
55
import Bootstrap from '@/bootstrap'
66
import { ThemeProvider } from '@/components/ThemeProvider'
77
import { UIProvider } from '@/components/UIProvider'
8+
import { SafeAreaInsetsProvider } from '@/hooks/useSafeAreaInsets'
89
import { store } from '@/store'
910

1011
const AppContainer: React.FC<{ children: ReactNode }> = ({ children }) => {
@@ -13,7 +14,9 @@ const AppContainer: React.FC<{ children: ReactNode }> = ({ children }) => {
1314
<HelmetProvider>
1415
<ThemeProvider>
1516
<UIProvider>
16-
<Bootstrap>{children}</Bootstrap>
17+
<SafeAreaInsetsProvider>
18+
<Bootstrap>{children}</Bootstrap>
19+
</SafeAreaInsetsProvider>
1720
</UIProvider>
1821
</ThemeProvider>
1922
</HelmetProvider>

src/components/ActionsModal.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'
44
import { useResponsiveDialog } from '@/components/ResponsiveDialog'
55
import { Button } from '@/components/ui/button'
66
import type { Dialog } from '@/components/ui/dialog'
7-
import { BottomSafeArea } from '@/components/VerticalSafeArea'
87

98
export type Action = {
109
id: number | string
@@ -52,8 +51,6 @@ const ActionsModal = ({
5251
<Button variant="outline">{t('common.close')}</Button>
5352
</DialogClose>
5453
</DialogFooter>
55-
56-
<BottomSafeArea />
5754
</DialogContent>
5855
</Dialog>
5956
)

src/components/ResponsiveDialog.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import React, { memo, type ReactNode } from 'react'
2+
import { css } from '@emotion/react'
13
import tw from 'twin.macro'
24
import { useMediaQuery } from 'usehooks-ts'
35

@@ -23,8 +25,23 @@ import {
2325
} from '@/components/ui/drawer'
2426

2527
const CustomDrawerContent = tw(DrawerContent)`px-6`
26-
const CustomDrawerFooter = tw(DrawerFooter)`px-0`
2728
const CustomDrawerHeader = tw(DrawerHeader)`px-0`
29+
const CustomDrawerFooter = memo(function CustomDrawerFooter({
30+
children,
31+
}: {
32+
children: ReactNode
33+
}) {
34+
return (
35+
<DrawerFooter
36+
className="px-0"
37+
css={css`
38+
padding-bottom: max(env(safe-area-inset-bottom), 1rem);
39+
`}
40+
>
41+
{children}
42+
</DrawerFooter>
43+
)
44+
})
2845

2946
export const useResponsiveDialog = () => {
3047
const isDesktop = useMediaQuery('(min-width: 768px)')
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { createContext } from 'react'
2+
3+
export type Context = {
4+
top: number
5+
left: number
6+
right: number
7+
bottom: number
8+
}
9+
10+
const context = createContext<Context | null>(null)
11+
12+
export default context

src/hooks/useSafeAreaInsets/hooks.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react'
2+
3+
import context from './context'
4+
5+
export const useSafeAreaInsets = () => {
6+
const safeAreaInsets = React.useContext(context)
7+
8+
if (safeAreaInsets === null) {
9+
throw new Error(
10+
'useSafeAreaInsets must be used within a SafeAreaInsetsProvider',
11+
)
12+
}
13+
14+
return safeAreaInsets
15+
}

src/hooks/useSafeAreaInsets/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './provider'
2+
export * from './hooks'
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React, { useEffect, useState } from 'react'
2+
3+
import context, { type Context } from './context'
4+
5+
type SafeAreaInsetsProviderProps = {
6+
children: React.ReactNode
7+
}
8+
9+
export const SafeAreaInsetsProvider = ({
10+
children,
11+
}: SafeAreaInsetsProviderProps) => {
12+
const [state, setState] = useState<Context | null>(null)
13+
14+
useEffect(() => {
15+
const tempDiv = document.createElement('div')
16+
17+
tempDiv.style.paddingTop = 'env(safe-area-inset-top, 0px)'
18+
tempDiv.style.paddingBottom = 'env(safe-area-inset-bottom, 0px)'
19+
tempDiv.style.paddingLeft = 'env(safe-area-inset-left, 0px)'
20+
tempDiv.style.paddingRight = 'env(safe-area-inset-right, 0px)'
21+
tempDiv.style.position = 'absolute'
22+
tempDiv.style.visibility = 'hidden'
23+
24+
document.body.appendChild(tempDiv)
25+
26+
const safeAreaInsetTop = window.getComputedStyle(tempDiv).paddingTop
27+
const safeAreaInsetBottom = window.getComputedStyle(tempDiv).paddingBottom
28+
const safeAreaInsetLeft = window.getComputedStyle(tempDiv).paddingLeft
29+
const safeAreaInsetRight = window.getComputedStyle(tempDiv).paddingRight
30+
31+
document.body.removeChild(tempDiv)
32+
33+
setState({
34+
top: parseInt(safeAreaInsetTop.replace('px', ''), 10),
35+
bottom: parseInt(safeAreaInsetBottom.replace('px', ''), 10),
36+
left: parseInt(safeAreaInsetLeft.replace('px', ''), 10),
37+
right: parseInt(safeAreaInsetRight.replace('px', ''), 10),
38+
})
39+
}, [])
40+
41+
return <context.Provider value={state}>{children}</context.Provider>
42+
}

src/pages/Home/components/SetHostModal.tsx

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,9 @@ import { Laptop } from 'lucide-react'
66
import store from 'store2'
77

88
import ProfileCell from '@/components/ProfileCell'
9+
import { useResponsiveDialog } from '@/components/ResponsiveDialog'
910
import { Badge } from '@/components/ui/badge'
1011
import { Button } from '@/components/ui/button'
11-
import {
12-
Dialog,
13-
DialogContent,
14-
DialogFooter,
15-
DialogHeader,
16-
DialogTitle,
17-
DialogTrigger,
18-
} from '@/components/ui/dialog'
1912
import { useAppDispatch, useProfile } from '@/store'
2013
import { profileActions } from '@/store/slices/profile'
2114
import { trafficActions } from '@/store/slices/traffic'
@@ -26,6 +19,16 @@ const SetHostModal: React.FC = () => {
2619
const { t } = useTranslation()
2720
const dispatch = useAppDispatch()
2821

22+
const {
23+
Dialog,
24+
DialogContent,
25+
DialogHeader,
26+
DialogTitle,
27+
DialogFooter,
28+
DialogTrigger,
29+
DialogClose,
30+
} = useResponsiveDialog()
31+
2932
const [existingProfiles, setExistingProfiles] = useState<Array<Profile>>([])
3033
const currentProfile = useProfile()
3134
const navigate = useNavigate()
@@ -69,7 +72,7 @@ const SetHostModal: React.FC = () => {
6972
<DialogTitle>{t('landing.history')}</DialogTitle>
7073
</DialogHeader>
7174

72-
<div className="bg-gray-100 dark:bg-muted border divide-y divide-gray-200 dark:divide-black/20 rounded-xl overflow-hidden">
75+
<div className="bg-gray-100 dark:bg-muted border divide-y divide-gray-200 dark:divide-black/20 rounded-xl overflow-hidden my-3">
7376
{existingProfiles.map((profile) => {
7477
return (
7578
<div
@@ -94,7 +97,10 @@ const SetHostModal: React.FC = () => {
9497
</div>
9598

9699
<DialogFooter>
97-
<Button className="mt-3 sm:mt-0" onClick={() => onAddNewProfile()}>
100+
<DialogClose asChild className="md:hidden">
101+
<Button variant="outline">{t('common.close')}</Button>
102+
</DialogClose>
103+
<Button onClick={() => onAddNewProfile()}>
98104
{t('landing.add_new_host')}
99105
</Button>
100106
</DialogFooter>

src/pages/Landing/Regular.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import DarkModeToggle from '@/components/DarkModeToggle'
1212
import ProfileCell from '@/components/ProfileCell'
1313
import RunInSurge from '@/components/RunInSurge'
1414
import { Button } from '@/components/ui/button'
15-
import { Checkbox } from '@/components/ui/checkbox'
1615
import {
1716
Form,
1817
FormControl,
@@ -23,6 +22,7 @@ import {
2322
FormMessage,
2423
} from '@/components/ui/form'
2524
import { Input } from '@/components/ui/input'
25+
import { Switch } from '@/components/ui/switch'
2626
import { TypographyH2, TypographyH4 } from '@/components/ui/typography'
2727
import VersionTag from '@/components/VersionTag'
2828
import InstallCertificateModal from '@/pages/Landing/components/InstallCertificateModal'
@@ -294,15 +294,15 @@ export const Component: React.FC = () => {
294294
)}
295295
/>
296296

297-
<div className="pt-2 space-y-2">
297+
<div className="pt-2 space-y-3">
298298
<RunInSurge not>
299299
<FormField
300300
control={form.control}
301301
name="useTls"
302302
render={({ field }) => (
303303
<FormItem className="flex flex-row items-center space-x-3 space-y-0">
304304
<FormControl>
305-
<Checkbox
305+
<Switch
306306
disabled={protocol === 'https:'}
307307
checked={field.value}
308308
onCheckedChange={field.onChange}
@@ -320,7 +320,7 @@ export const Component: React.FC = () => {
320320
render={({ field }) => (
321321
<FormItem className="flex flex-row items-center space-x-3 space-y-0">
322322
<FormControl>
323-
<Checkbox
323+
<Switch
324324
checked={field.value}
325325
onCheckedChange={field.onChange}
326326
/>

src/pages/Policies/components/PolicyGroup.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import useIsInViewport from 'use-is-in-viewport'
77
import { StatusChip } from '@/components/StatusChip'
88
import { Button } from '@/components/ui/button'
99
import { Card, CardContent, CardHeader } from '@/components/ui/card'
10-
import { TypographyH3 } from '@/components/ui/typography'
1110
import {
1211
Policy,
1312
SelectPolicyTestResult,
@@ -221,10 +220,12 @@ const PolicyGroup: React.FC<PolicyGroupProps> = ({
221220
}, [refreshSelection, isInViewport, selection])
222221

223222
const cardInner = (
224-
<>
225-
<CardHeader className="py-4 px-4">
223+
<div className="p-3 sm:p-4 grid gap-4 select-none">
224+
<CardHeader className="p-0">
226225
<div className="flex flex-row justify-between items-center">
227-
<TypographyH3>{policyGroupName}</TypographyH3>
226+
<div className="scroll-m-20 text-md sm:text-xl font-bold">
227+
{policyGroupName}
228+
</div>
228229
<Button
229230
size="icon"
230231
variant="outline"
@@ -236,7 +237,7 @@ const PolicyGroup: React.FC<PolicyGroupProps> = ({
236237
</div>
237238
</CardHeader>
238239

239-
<CardContent className="p-4 pt-0">
240+
<CardContent className="p-0">
240241
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
241242
{policyGroup.map((policy) => {
242243
const typeDescription = policy.typeDescription.toUpperCase()
@@ -254,7 +255,7 @@ const PolicyGroup: React.FC<PolicyGroupProps> = ({
254255
<div>
255256
<div className="text-xs mb-1 truncate">{typeDescription}</div>
256257

257-
<div className="text-sm font-bold md:text-base leading-snug whitespace-break-spaces break-words">
258+
<div className="text-xs sm:text-sm font-bold leading-snug whitespace-break-spaces break-all">
258259
{policy.name}
259260
</div>
260261
</div>
@@ -285,7 +286,7 @@ const PolicyGroup: React.FC<PolicyGroupProps> = ({
285286
})}
286287
</div>
287288
</CardContent>
288-
</>
289+
</div>
289290
)
290291

291292
return (
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import tw from 'twin.macro'
22

33
export const PolicyNameItem = tw.div`
4-
flex-shrink-0 bg-muted dark:bg-background rounded-xl border px-3 py-2 overflow-hidden cursor-pointer shadow-sm font-bold
4+
flex-shrink-0 bg-muted dark:bg-background rounded-xl border px-2 sm:px-3 py-2 overflow-hidden cursor-pointer shadow-sm font-bold
55
hover:bg-gray-100 dark:hover:bg-black/90 transition-colors ease-in-out duration-200
6-
text-sm sm:text-base
6+
text-xs sm:text-sm select-none
77
`

src/pages/Policies/index.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { css } from '@emotion/react'
55
import useSWR from 'swr'
66

77
import BackButton from '@/components/BackButton'
8-
import PageContainer from '@/components/PageContainer'
98
import { TypographyH3 } from '@/components/ui/typography'
109
import { BottomSafeArea } from '@/components/VerticalSafeArea'
1110
import { useProfile } from '@/store'
@@ -50,7 +49,7 @@ export const Component: React.FC = () => {
5049
{({ scroll }) => (
5150
<>
5251
<div
53-
className="sticky top-0 left-0 right-0 shadow bg-white dark:bg-muted z-10 pt-5 mb-5"
52+
className="sticky top-0 left-0 right-0 shadow bg-white dark:bg-muted z-10 pt-3 sm:pt-5 mb-5"
5453
ref={headerRef}
5554
>
5655
<div
@@ -67,7 +66,7 @@ export const Component: React.FC = () => {
6766
</div>
6867

6968
<div
70-
className="flex justify-start overflow-x-scroll py-4 space-x-3"
69+
className="flex justify-start overflow-x-scroll pb-3 pt-4 sm:pb-4 space-x-3"
7170
css={css`
7271
padding-left: calc(env(safe-area-inset-left) + 1rem);
7372
padding-right: calc(env(safe-area-inset-right) + 1rem);

src/pages/Requests/components/RequestModal.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { toast } from 'react-hot-toast'
33
import { useTranslation } from 'react-i18next'
44
import { css } from '@emotion/react'
55
import styled from '@emotion/styled'
6+
import * as VisuallyHidden from '@radix-ui/react-visually-hidden'
67
import bytes from 'bytes'
78
import dayjs from 'dayjs'
89
import { Search } from 'lucide-react'
@@ -13,14 +14,8 @@ import { useMediaQuery } from 'usehooks-ts'
1314

1415
import CodeContent from '@/components/CodeContent'
1516
import { DataGroup, DataRowMain } from '@/components/Data'
16-
import { Button } from '@/components/ui/button'
17-
import { Dialog, DialogContent } from '@/components/ui/dialog'
18-
import {
19-
Drawer,
20-
DrawerContent,
21-
DrawerFooter,
22-
DrawerClose,
23-
} from '@/components/ui/drawer'
17+
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog'
18+
import { Drawer, DrawerContent, DrawerTitle } from '@/components/ui/drawer'
2419
import {
2520
Tabs,
2621
TabsContent as TabsContentOriginal,
@@ -245,12 +240,18 @@ const RequestModal: React.FC<RequestModalProps> = ({ req, ...props }) => {
245240
return isDesktop ? (
246241
<Dialog {...props}>
247242
<DialogContent className="h-[90%] max-w-4xl flex flex-col pt-10">
243+
<VisuallyHidden.Root>
244+
<DialogTitle>Request {req?.URL}</DialogTitle>
245+
</VisuallyHidden.Root>
248246
{content}
249247
</DialogContent>
250248
</Dialog>
251249
) : (
252250
<Drawer {...props}>
253251
<DrawerContent className="flex flex-col px-4 gap-4 h-[90%]">
252+
<VisuallyHidden.Root>
253+
<DrawerTitle>Request {req?.URL}</DrawerTitle>
254+
</VisuallyHidden.Root>
254255
{content}
255256
<BottomSafeArea />
256257
</DrawerContent>

src/pages/Scripting/Evaluate/index.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { useResponsiveDialog } from '@/components/ResponsiveDialog'
1111
import { Button } from '@/components/ui/button'
1212
import { Input } from '@/components/ui/input'
1313
import { Label } from '@/components/ui/label'
14-
import { BottomSafeArea } from '@/components/VerticalSafeArea'
1514
import { EvaluateResult } from '@/types'
1615
import fetcher from '@/utils/fetcher'
1716

@@ -142,8 +141,6 @@ export const Component: React.FC = () => {
142141
<Button variant="default">{t('common.close')}</Button>
143142
</DialogClose>
144143
</DialogFooter>
145-
146-
<BottomSafeArea />
147144
</DialogContent>
148145
</Dialog>
149146
</FixedFullscreenContainer>

0 commit comments

Comments
 (0)