Skip to content

Commit 97d81b4

Browse files
committed
feat: animated sliding indicator for ModeToggle, update next-env types
1 parent 47b9b31 commit 97d81b4

3 files changed

Lines changed: 35 additions & 8 deletions

File tree

next-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3-
import "./.next/dev/types/routes.d.ts";
3+
import "./.next/types/routes.d.ts";
44

55
// NOTE: This file should not be edited
66
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

src/components/shared/ModeToggle.module.css

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
}
2222

2323
.toggle {
24+
position: relative;
2425
display: flex;
2526
gap: 0;
2627
border: 1px solid var(--border);
@@ -32,29 +33,38 @@
3233
flex: 1;
3334
padding: 8px 12px;
3435
min-height: 44px;
35-
background: var(--bg-primary);
36+
background: transparent;
3637
color: var(--text-secondary);
3738
border: none;
3839
font-size: 13px;
3940
font-weight: 500;
4041
cursor: pointer;
4142
font-family: inherit;
42-
transition: background 0.15s, color 0.15s;
43+
position: relative;
44+
z-index: 1;
45+
transition: color var(--duration-fast) var(--ease-out);
4346
}
4447

4548
.btn:not(:last-child) {
4649
border-right: 1px solid var(--border);
4750
}
4851

49-
.btn:hover {
50-
background: var(--bg-surface);
52+
.btn:hover:not(.btnActive) {
53+
color: var(--text-primary);
54+
background: var(--surface-1);
5155
}
5256

5357
.btnActive {
54-
background: var(--accent);
5558
color: var(--text-on-accent);
5659
}
5760

58-
.btnActive:hover {
61+
.indicator {
62+
position: absolute;
63+
top: 0;
64+
bottom: 0;
5965
background: var(--accent);
66+
border-radius: 7px;
67+
transition: left var(--duration-normal) var(--ease-out),
68+
width var(--duration-normal) var(--ease-out);
69+
z-index: 0;
6070
}

src/components/shared/ModeToggle.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useRef, useEffect, useState } from 'react'
12
import styles from './ModeToggle.module.css'
23

34
interface ModeOption<T extends string> {
@@ -14,10 +15,26 @@ interface ModeToggleProps<T extends string> {
1415
}
1516

1617
export function ModeToggle<T extends string>({ options, value, onChange, sticky, title }: ModeToggleProps<T>) {
18+
const toggleRef = useRef<HTMLDivElement>(null)
19+
const [indicator, setIndicator] = useState({ left: 0, width: 0 })
20+
21+
useEffect(() => {
22+
const container = toggleRef.current
23+
if (!container) return
24+
const idx = options.findIndex((o) => o.value === value)
25+
const btn = container.children[idx + 1] as HTMLElement | undefined // +1 for the indicator div
26+
if (!btn) return
27+
setIndicator({ left: btn.offsetLeft, width: btn.offsetWidth })
28+
}, [value, options])
29+
1730
return (
1831
<div className={`${styles.wrapper} ${sticky ? styles.sticky : ''}`}>
1932
{title && <div className={styles.title}>{title}</div>}
20-
<div className={styles.toggle}>
33+
<div className={styles.toggle} ref={toggleRef}>
34+
<div
35+
className={styles.indicator}
36+
style={{ left: indicator.left, width: indicator.width }}
37+
/>
2138
{options.map((opt) => (
2239
<button
2340
key={opt.value}

0 commit comments

Comments
 (0)