Skip to content

Commit d189d75

Browse files
committed
fix: resolve random css issues with mobile layouts
1 parent 8105b8f commit d189d75

File tree

14 files changed

+248
-239
lines changed

14 files changed

+248
-239
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
- Added an `/admin/api/info` route that can expose sensitive information if `server.info_secret` is set in the configuration (closes [#324](https://github.com/tale/headplane/issues/324)).
2323
- Correctly apply Gravatar profile pictures on the user page if applicable (closes [#405](https://github.com/tale/headplane/issues/405)).
2424
- Machine key registration no longer works if the key isn't 24 characters long (closes [#415](https://github.com/tale/headplane/issues/415)).
25+
- Fixed some mobile CSS issues across the application (closes [#401](https://github.com/tale/headplane/issues/401)).
2526
---
2627

2728
# 0.6.1 (October 12, 2025)

app/components/Tabs.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ function Tabs({ label, className, ...props }: TabsProps) {
2323
<div className={cn('flex flex-col', className)}>
2424
<div
2525
{...tabListProps}
26-
ref={ref}
2726
className={cn(
28-
'flex items-center rounded-t-xl w-fit',
27+
'flex items-center rounded-t-xl w-fit max-w-full overflow-x-auto',
2928
'border-headplane-100 dark:border-headplane-800',
3029
'border-t border-x',
3130
)}
31+
ref={ref}
3232
>
3333
{[...state.collection].map((item) => (
34-
<Tab key={item.key} item={item} state={state} />
34+
<Tab item={item} key={item.key} state={state} />
3535
))}
3636
</div>
3737
<TabsPanel key={state.selectedItem?.key} state={state} />
@@ -52,14 +52,14 @@ function Tab({ item, state }: TabsTabProps) {
5252
return (
5353
<div
5454
{...tabProps}
55-
ref={ref}
5655
className={cn(
5756
'pl-2 pr-3 py-2.5',
5857
'aria-selected:bg-headplane-100 dark:aria-selected:bg-headplane-950',
5958
'focus:outline-hidden focus:ring-3 z-10',
6059
'border-r border-headplane-100 dark:border-headplane-800',
6160
'first:rounded-tl-xl last:rounded-tr-xl last:border-r-0',
6261
)}
62+
ref={ref}
6363
>
6464
{rendered}
6565
</div>
@@ -76,11 +76,11 @@ function TabsPanel({ state, ...props }: TabsPanelProps) {
7676
return (
7777
<div
7878
{...tabPanelProps}
79-
ref={ref}
8079
className={cn(
8180
'w-full overflow-clip rounded-b-xl rounded-r-xl',
8281
'border border-headplane-100 dark:border-headplane-800',
8382
)}
83+
ref={ref}
8484
>
8585
{state.selectedItem?.props.children}
8686
</div>

app/root.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export function Layout({ children }: { readonly children: React.ReactNode }) {
4545
<Links />
4646
<link href="favicon.ico" rel="icon" />
4747
</head>
48-
<body className="overscroll-none dark:bg-headplane-900 dark:text-headplane-50">
48+
<body className="overscroll-none overflow-x-hidden dark:bg-headplane-900 dark:text-headplane-50">
4949
{children}
5050
<ToastProvider queue={toastQueue} />
5151
<ScrollRestoration />

app/routes/dns/components/manage-domains.tsx

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
import { DndContext, DragOverlay, closestCorners } from '@dnd-kit/core';
1+
import { closestCorners, DndContext, DragOverlay } from '@dnd-kit/core';
22
import {
33
restrictToParentElement,
44
restrictToVerticalAxis,
55
} from '@dnd-kit/modifiers';
66
import {
7-
SortableContext,
87
arrayMove,
8+
SortableContext,
99
useSortable,
1010
verticalListSortingStrategy,
1111
} from '@dnd-kit/sortable';
1212
import { CSS } from '@dnd-kit/utilities';
1313
import { GripVertical, Lock } from 'lucide-react';
1414
import { useEffect, useState } from 'react';
15-
import { type FetcherWithComponents, Form, useFetcher } from 'react-router';
15+
import { Form } from 'react-router';
1616
import Button from '~/components/Button';
1717
import Input from '~/components/Input';
1818
import TableList from '~/components/TableList';
@@ -37,18 +37,15 @@ export default function ManageDomains({
3737
}, [searchDomains]);
3838

3939
return (
40-
<div className="flex flex-col w-2/3">
40+
<div className="flex flex-col w-full sm:w-2/3">
4141
<h1 className="text-2xl font-medium mb-4">Search Domains</h1>
4242
<p className="mb-4">
4343
Set custom DNS search domains for your Tailnet. When using Magic DNS,
4444
your tailnet domain is used as the first search domain.
4545
</p>
4646
<DndContext
47-
modifiers={[restrictToVerticalAxis, restrictToParentElement]}
4847
collisionDetection={closestCorners}
49-
onDragStart={(event) => {
50-
setActiveId(event.active.id);
51-
}}
48+
modifiers={[restrictToVerticalAxis, restrictToParentElement]}
5249
onDragEnd={(event) => {
5350
setActiveId(null);
5451
const { active, over } = event;
@@ -70,6 +67,9 @@ export default function ManageDomains({
7067
setLocalDomains(arrayMove(localDomains, oldIndex, newIndex));
7168
}
7269
}}
70+
onDragStart={(event) => {
71+
setActiveId(event.active.id);
72+
}}
7373
>
7474
<TableList>
7575
{magic ? (
@@ -91,48 +91,48 @@ export default function ManageDomains({
9191
>
9292
{localDomains.map((sd, index) => (
9393
<Domain
94-
key={sd}
9594
domain={sd}
9695
id={index + 1}
9796
isDisabled={isDisabled}
97+
key={sd}
9898
/>
9999
))}
100100
<DragOverlay adjustScale>
101101
{activeId ? (
102102
<Domain
103-
isDragging
104103
domain={localDomains[(activeId as number) - 1]}
105104
id={(activeId as number) - 1}
106105
isDisabled={isDisabled}
106+
isDragging
107107
/>
108108
) : undefined}
109109
</DragOverlay>
110110
</SortableContext>
111111
{isDisabled ? undefined : (
112112
<TableList.Item key="add-sd">
113113
<Form
114-
method="POST"
115114
className="flex items-center justify-between w-full"
115+
method="POST"
116116
>
117-
<input type="hidden" name="action_id" value="add_domain" />
117+
<input name="action_id" type="hidden" value="add_domain" />
118118
<Input
119-
type="text"
120119
className={cn(
121120
'border-none font-mono p-0 text-sm',
122121
'rounded-none focus:ring-0 w-full ml-1',
123122
)}
124-
placeholder="Search Domain"
123+
isRequired
125124
label="Search Domain"
126-
name="domain"
127125
labelHidden
128-
isRequired
126+
name="domain"
127+
placeholder="Search Domain"
128+
type="text"
129129
/>
130130
<Button
131-
type="submit"
132131
className={cn(
133132
'px-2 py-1 rounded-md',
134133
'text-blue-500 dark:text-blue-400',
135134
)}
135+
type="submit"
136136
>
137137
Add
138138
</Button>
@@ -164,11 +164,11 @@ function Domain({ domain, id, isDragging, isDisabled }: DomainProps) {
164164

165165
return (
166166
<TableList.Item
167-
ref={setNodeRef}
168167
className={cn(
169168
isSortableDragging ? 'opacity-50' : '',
170169
isDragging ? 'ring-3 bg-white dark:bg-headplane-900' : '',
171170
)}
171+
ref={setNodeRef}
172172
style={{
173173
transform: CSS.Transform.toString(transform),
174174
transition,
@@ -186,15 +186,15 @@ function Domain({ domain, id, isDragging, isDisabled }: DomainProps) {
186186
</p>
187187
{isDragging ? undefined : (
188188
<Form method="POST">
189-
<input type="hidden" name="action_id" value="remove_domain" />
190-
<input type="hidden" name="domain" value={domain} />
189+
<input name="action_id" type="hidden" value="remove_domain" />
190+
<input name="domain" type="hidden" value={domain} />
191191
<Button
192-
type="submit"
193-
isDisabled={isDisabled}
194192
className={cn(
195193
'px-2 py-1 rounded-md',
196194
'text-red-500 dark:text-red-400',
197195
)}
196+
isDisabled={isDisabled}
197+
type="submit"
198198
>
199199
Remove
200200
</Button>

app/routes/dns/components/manage-ns.tsx

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,27 @@ export default function ManageNS({
2020
overrideLocalDns,
2121
}: Props) {
2222
return (
23-
<div className="flex flex-col w-2/3">
23+
<div className="flex flex-col w-full sm:w-2/3">
2424
<h1 className="text-2xl font-medium mb-4">Nameservers</h1>
2525
<p>
2626
Set the nameservers used by devices on the Tailnet to resolve DNS
2727
queries.{' '}
2828
<Link
29-
to="https://tailscale.com/kb/1054/dns"
3029
name="Tailscale DNS Documentation"
30+
to="https://tailscale.com/kb/1054/dns"
3131
>
3232
Learn more
3333
</Link>
3434
</p>
3535
<div className="mt-4">
3636
{Object.keys(nameservers).map((key) => (
3737
<NameserverList
38-
key={key}
39-
isGlobal={key === 'global'}
4038
isDisabled={isDisabled}
39+
isGlobal={key === 'global'}
40+
key={key}
41+
name={key}
4142
nameservers={nameservers}
4243
overrideLocalDns={overrideLocalDns}
43-
name={key}
4444
/>
4545
))}
4646

@@ -66,16 +66,17 @@ function NameserverList({
6666
name,
6767
}: ListProps) {
6868
const list = isGlobal ? nameservers.global : nameservers[name];
69+
const submit = useSubmit();
70+
6971
if (list.length === 0) {
7072
return null;
7173
}
7274

73-
const submit = useSubmit();
7475
return (
7576
<div className="mb-8">
7677
<div className="flex items-center justify-between mb-2">
7778
{isGlobal ? (
78-
<div className="flex items-center justify-between w-full">
79+
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between w-full gap-2">
7980
<h2 className="text-md font-medium opacity-80">
8081
Global Nameservers
8182
</h2>
@@ -87,20 +88,19 @@ function NameserverList({
8788
names outside the tailnet. When disabled (default), devices
8889
will prefer their local DNS configuration.
8990
<Link
90-
to="https://tailscale.com/kb/1054/dns#global-nameservers"
9191
name="Tailscale Global Nameservers Documentation"
92+
to="https://tailscale.com/kb/1054/dns#global-nameservers"
9293
>
9394
Learn More
9495
</Link>
9596
</Tooltip.Body>
9697
</Tooltip>
9798
<p>Override DNS servers</p>
9899
<Switch
100+
className="h-[15px] w-[23px] p-0.5"
101+
defaultSelected={overrideLocalDns}
99102
label="Override local DNS settings"
100-
className="h-[15px] w-[23px] p-[2px]"
101-
switchClassName="h-[9px] w-[9px]"
102103
name="override_dns"
103-
defaultSelected={overrideLocalDns}
104104
onChange={(v) => {
105105
submit(
106106
{
@@ -112,6 +112,7 @@ function NameserverList({
112112
},
113113
);
114114
}}
115+
switchClassName="h-[9px] w-[9px]"
115116
/>
116117
</div>
117118
</div>
@@ -125,20 +126,20 @@ function NameserverList({
125126
<TableList.Item key={ns}>
126127
<p className="font-mono text-sm">{ns}</p>
127128
<Form method="POST">
128-
<input type="hidden" name="action_id" value="remove_ns" />
129-
<input type="hidden" name="ns" value={ns} />
129+
<input name="action_id" type="hidden" value="remove_ns" />
130+
<input name="ns" type="hidden" value={ns} />
130131
<input
131-
type="hidden"
132132
name="split_name"
133+
type="hidden"
133134
value={isGlobal ? 'global' : name}
134135
/>
135136
<Button
136-
isDisabled={isDisabled}
137-
type="submit"
138137
className={cn(
139138
'px-2 py-1 rounded-md',
140139
'text-red-500 dark:text-red-400',
141140
)}
141+
isDisabled={isDisabled}
142+
type="submit"
142143
>
143144
Remove
144145
</Button>

app/routes/dns/components/manage-records.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ interface Props {
1313

1414
export default function ManageRecords({ records, isDisabled }: Props) {
1515
return (
16-
<div className="flex flex-col w-2/3">
16+
<div className="flex flex-col w-full sm:w-2/3">
1717
<h1 className="text-2xl font-medium mb-4">DNS Records</h1>
1818
<p>
1919
Headscale supports adding custom DNS records to your Tailnet. As of now,
2020
only <Code>A</Code> and <Code>AAAA</Code> records are supported.{' '}
2121
<Link
22-
to="https://headscale.net/stable/ref/dns"
2322
name="Headscale DNS Records documentation"
23+
to="https://headscale.net/stable/ref/dns"
2424
>
2525
Learn More
2626
</Link>
@@ -32,7 +32,7 @@ export default function ManageRecords({ records, isDisabled }: Props) {
3232
<p className="opacity-50 mx-auto">No DNS records found</p>
3333
</TableList.Item>
3434
) : (
35-
records.map((record, index) => (
35+
records.map((record) => (
3636
<TableList.Item key={`${record.name}-${record.value}`}>
3737
<div className="flex gap-2 items-center w-full">
3838
<p
@@ -43,22 +43,24 @@ export default function ManageRecords({ records, isDisabled }: Props) {
4343
>
4444
{record.type}
4545
</p>
46-
<div className="grid grid-cols-2 gap-2 w-full">
47-
<p className="font-mono text-sm">{record.name}</p>
48-
<p className="font-mono text-sm">{record.value}</p>
46+
<div className="flex flex-col sm:flex-row sm:gap-2 flex-1 min-w-0">
47+
<p className="font-mono text-sm truncate">{record.name}</p>
48+
<p className="font-mono text-sm truncate opacity-70 sm:opacity-100">
49+
{record.value}
50+
</p>
4951
</div>
5052
</div>
5153
<Form method="POST">
52-
<input type="hidden" name="action_id" value="remove_record" />
53-
<input type="hidden" name="record_name" value={record.name} />
54-
<input type="hidden" name="record_type" value={record.type} />
54+
<input name="action_id" type="hidden" value="remove_record" />
55+
<input name="record_name" type="hidden" value={record.name} />
56+
<input name="record_type" type="hidden" value={record.type} />
5557
<Button
56-
type="submit"
57-
isDisabled={isDisabled}
5858
className={cn(
5959
'px-2 py-1 rounded-md',
6060
'text-red-500 dark:text-red-400',
6161
)}
62+
isDisabled={isDisabled}
63+
type="submit"
6264
>
6365
Remove
6466
</Button>

0 commit comments

Comments
 (0)