Skip to content

Commit d8b00af

Browse files
committed
added reordering
1 parent 7751686 commit d8b00af

File tree

17 files changed

+330
-112
lines changed

17 files changed

+330
-112
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@connectrpc/connect-web": "^2.1.1",
1616
"@dnd-kit/core": "^6.3.1",
1717
"@dnd-kit/sortable": "^10.0.0",
18+
"@dnd-kit/utilities": "^3.2.2",
1819
"@octokit/openapi-types": "^27.0.0",
1920
"@radix-ui/react-dialog": "^1.1.15",
2021
"@radix-ui/react-label": "^2.1.8",

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/auth/repo-wall.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { CollectionProvider } from "@/components/collection/provider"
2+
import { OrderingProvider } from "@/components/ordering/provider"
13
import { PageHeader } from "@/components/page-header"
2-
import { RepoContentView } from "@/components/repo/content-view"
4+
import { RepoContentProvider } from "@/components/repo/content-provider"
35
import { RepoContext } from "@/components/repo/context"
46
import { RepoSelector } from "@/components/repo/selector"
57
import { useSelectedRepo } from "@/lib/git"
@@ -21,7 +23,11 @@ export const RepoWall: React.FC<React.PropsWithChildren> = ({ children }) => {
2123

2224
return (
2325
<RepoContext.Provider value={repo}>
24-
<RepoContentView>{children}</RepoContentView>
26+
<RepoContentProvider>
27+
<CollectionProvider>
28+
<OrderingProvider>{children}</OrderingProvider>
29+
</CollectionProvider>
30+
</RepoContentProvider>
2531
</RepoContext.Provider>
2632
)
2733
}

src/components/collection/context.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
import type { Collection, Habit } from "@/proto/models/v1/models_pb"
1+
import type { Collection } from "@/proto/models/v1/models_pb"
22
import React from "react"
33

44
interface S {
55
collection: Collection
6+
67
tags: string[]
78
tagSet: Set<string>
8-
habits: string[]
9-
habitNameSet: Set<string>
10-
completed: Set<string>
11-
recordCompletion: (h: Habit) => void
9+
10+
habits: Set<string>
1211
}
1312

1413
export const CollectionContext = React.createContext<S | undefined>(undefined)
Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,15 @@ import { useRepoContentContext } from "@/components/repo/content-context"
22
import { ErrorView } from "@/components/util/error-view"
33
import { LoadingPage } from "@/components/util/loading-page"
44
import { iterHabitNames } from "@/lib/git"
5-
import { getProgress } from "@/lib/progress"
65
import { useCollection } from "@/lib/queries"
7-
import type { Habit } from "@/proto/models/v1/models_pb"
86
import React from "react"
97

108
import { CollectionContext } from "./context"
119

12-
export const CollectionView: React.FC<React.PropsWithChildren> = ({ children }) => {
10+
export const CollectionProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
1311
const collection = useCollection()
1412
const repoContent = useRepoContentContext()
1513

16-
const [completed, setCompleted] = React.useState<Set<string>>(new Set())
17-
const recordCompletion = (h: Habit) => {
18-
const { completion } = getProgress(h, new Date())
19-
const isCompleted = completion.count >= completion.target
20-
21-
setCompleted((prev) => {
22-
const next = new Set(prev)
23-
if (isCompleted) {
24-
next.add(h.name)
25-
} else {
26-
next.delete(h.name)
27-
}
28-
return next
29-
})
30-
}
31-
3214
const habits = [...iterHabitNames(repoContent)]
3315

3416
if (collection.isLoading) {
@@ -43,10 +25,7 @@ export const CollectionView: React.FC<React.PropsWithChildren> = ({ children })
4325
collection: collection.data,
4426
tags,
4527
tagSet: new Set(tags),
46-
habits,
47-
habitNameSet: new Set(habits),
48-
completed,
49-
recordCompletion,
28+
habits: new Set(habits),
5029
}}
5130
>
5231
<div data-testid="collection-view" className="flex flex-col items-center grow">

src/components/habit/card.tsx

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { useHabitContext } from "@/components/habit/context"
2+
import { useOrderingContext } from "@/components/ordering/context"
23
import { TagContext } from "@/components/tag/context"
34
import { useStoredDisplayOptions } from "@/lib/displayOptions"
45
import { cn } from "@/lib/utils"
56
import type { Habit } from "@/proto/models/v1/models_pb"
7+
import { useSortable } from "@dnd-kit/sortable"
8+
import { CSS } from "@dnd-kit/utilities"
69
import { Link } from "@tanstack/react-router"
710
import React from "react"
811

@@ -20,18 +23,54 @@ const matchActiveTags = (h: Habit, active?: Set<string>): boolean => {
2023
return false
2124
}
2225

26+
const LinkWrapper: React.FC<React.PropsWithChildren> = ({ children }) => {
27+
const { isReordering } = useOrderingContext()
28+
const { habit } = useHabitContext()
29+
30+
if (isReordering) {
31+
return children
32+
}
33+
34+
return (
35+
<Link
36+
to="/habits/$name"
37+
params={{ name: habit.name }}
38+
className="flex flex-col justify-center grow h-full min-h-11"
39+
>
40+
{children}
41+
</Link>
42+
)
43+
}
44+
2345
export const HabitCard: React.FC = () => {
2446
const tags = React.useContext(TagContext)
2547
const { habit, color, isCompleted } = useHabitContext()
48+
const { isReordering } = useOrderingContext()
49+
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
50+
id: habit.name,
51+
})
52+
const style: React.CSSProperties = {
53+
transform: CSS.Transform.toString(transform),
54+
transition,
55+
}
2656
const [displayOptions] = useStoredDisplayOptions()
2757

2858
if (!matchActiveTags(habit, tags.active)) return null
2959

60+
const extraProps = isReordering
61+
? { ref: setNodeRef, style, ...attributes, ...listeners }
62+
: undefined
63+
3064
return (
3165
<div
32-
className={cn("habit-card rounded truncate w-full max-w-211 flex flex-col gap-1", {
33-
"min-h-40": !displayOptions.hideChart,
34-
})}
66+
{...extraProps}
67+
className={cn(
68+
"habit-card rounded truncate w-full max-w-211 flex flex-col gap-1",
69+
{
70+
"min-h-40": !displayOptions.hideChart,
71+
},
72+
{ "cursor-move": isReordering },
73+
)}
3574
>
3675
<div
3776
className={cn(
@@ -41,22 +80,18 @@ export const HabitCard: React.FC = () => {
4180
color.lightBackground,
4281
{ "pt-1": !displayOptions.hideProgressbar },
4382
{ "py-1": displayOptions.hideProgressbar },
44-
{ "opacity-50": isCompleted },
83+
{ "opacity-50": isCompleted && !isReordering },
4584
)}
4685
>
4786
<div className={cn("flex items-center gap-1 w-full px-2 pl-3 min-h-11")}>
48-
<Link
49-
to="/habits/$name"
50-
params={{ name: habit.name }}
51-
className="flex flex-col justify-center grow h-full min-h-11"
52-
>
87+
<LinkWrapper>
5388
<div className="flex items-center gap-3 grow">
5489
<HabitIcon size={20} className="w-6" />
5590

5691
<div className="flex flex-col gap-0">
5792
<span
5893
className={cn("max-w-120 overflow-hidden text-ellipsis", {
59-
"line-through": isCompleted,
94+
"line-through": isCompleted && !isReordering,
6095
})}
6196
title={habit.name}
6297
>
@@ -73,11 +108,13 @@ export const HabitCard: React.FC = () => {
73108
)}
74109
</div>
75110
</div>
76-
</Link>
111+
</LinkWrapper>
77112

78-
<div className="ml-auto flex items-center gap-1 overflow-hidden">
79-
<CompletionButtons max={2} />
80-
</div>
113+
{!isReordering && (
114+
<div className="ml-auto flex items-center gap-1 overflow-hidden">
115+
<CompletionButtons max={2} />
116+
</div>
117+
)}
81118
</div>
82119

83120
{!displayOptions.hideProgressbar && <HabitProgress />}

src/components/habit/chart.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import { useHabitContext } from "@/components/habit/context"
2+
import { Progress } from "@/components/ui/progress"
3+
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
24
import { formatDate } from "@/lib/dates"
35
import { cn } from "@/lib/utils"
46
import { type Completion } from "@/proto/models/v1/models_pb"
57
import type React from "react"
68

7-
import { Progress } from "../ui/progress"
8-
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"
9-
109
const rows = Array.from(Array(7).keys())
1110
const columns = Array.from(Array(53).keys())
1211

src/components/habit/fetcher.tsx

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCollectionContext } from "@/components/collection/context"
1+
import { useOrderingContext } from "@/components/ordering/context"
22
import { Button } from "@/components/ui/button"
33
import { ErrorView } from "@/components/util/error-view"
44
import { LoadingScreen } from "@/components/util/loading-screen"
@@ -15,20 +15,15 @@ import { FileButton } from "./file-button"
1515

1616
interface P extends React.PropsWithChildren {
1717
name: string
18-
mode: "completed" | "active" | "page"
1918
}
2019

21-
export const HabitFetcher: React.FC<P> = ({ name, children, mode }) => {
20+
export const HabitFetcher: React.FC<P> = ({ name, children }) => {
2221
const habit = useHabit(name)
2322
const update = useUpdateHabit(name)
2423
const [displayOptions] = useStoredDisplayOptions()
25-
const { completed } = useCollectionContext()
24+
const { completed } = useOrderingContext()
2625

2726
if (habit.isLoading) {
28-
if (mode === "completed") {
29-
// Loading will be shown in `active` fetcher, avoid duplication.
30-
return null
31-
}
3227
return (
3328
<LoadingScreen
3429
label={name}
@@ -44,14 +39,6 @@ export const HabitFetcher: React.FC<P> = ({ name, children, mode }) => {
4439
const { completion, progress } = getProgress(habit.data, new Date())
4540
const isCompleted = completed.has(habit.data.name)
4641

47-
if (mode === "completed" && !isCompleted) {
48-
return null
49-
}
50-
if (mode === "active" && isCompleted) {
51-
// Habit will be shown in `completed` fetcher, avoid duplication.
52-
return null
53-
}
54-
5542
return (
5643
<HabitContext
5744
value={{
@@ -70,10 +57,6 @@ export const HabitFetcher: React.FC<P> = ({ name, children, mode }) => {
7057
)
7158
}
7259

73-
if (mode === "completed") {
74-
// Error will be shown in `active` fetcher, avoid duplication.
75-
return null
76-
}
7760
return (
7861
<div className="w-full max-w-211">
7962
<ErrorView title={name} error={habit.error}>

src/components/habit/form.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const empty = (): Habit =>
3030
})
3131

3232
export const HabitForm: React.FC<P> = ({ value, onChange, onCancel }) => {
33-
const { habitNameSet } = useCollectionContext()
33+
const { habits: habitNameSet } = useCollectionContext()
3434

3535
const [loading, setLoading] = React.useState(false)
3636
const [habit, setHabit] = React.useState<Habit>(value ?? empty())

src/components/habit/icon.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
import { useHabitContext } from "@/components/habit/context"
2+
import { useOrderingContext } from "@/components/ordering/context"
23
import * as colors from "@/lib/colors"
34
import * as icons from "@/lib/icons"
45
import { cn } from "@/lib/utils"
5-
import { Loader2, type LucideProps } from "lucide-react"
6-
import type React from "react"
6+
import { GripVertical, Loader2, type LucideProps } from "lucide-react"
7+
import React from "react"
78

89
export const HabitIcon: React.FC<LucideProps> = (props) => {
10+
const { isReordering } = useOrderingContext()
911
const { habit, isFetching } = useHabitContext()
1012
const color = colors.index[colors.fromString(habit.color)]
1113

1214
if (isFetching) {
1315
return <Loader2 {...props} className={cn("animate-spin", color.text, props.className)} />
1416
}
1517

16-
return icons.render(habit.icon, {
17-
...props,
18-
className: cn(color.text, props.className),
19-
})
18+
const className = cn(color.text, props.className)
19+
20+
if (isReordering) return React.createElement(GripVertical, { ...props, className })
21+
return icons.render(habit.icon, { ...props, className })
2022
}

0 commit comments

Comments
 (0)