Skip to content

Commit 6d18c99

Browse files
committed
zero-value completion buttons
1 parent e15dd65 commit 6d18c99

File tree

4 files changed

+209
-108
lines changed

4 files changed

+209
-108
lines changed

src/components/habit/button.tsx

Lines changed: 77 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import {
1010
HabitSchema,
1111
} from "@/proto/models/v1/models_pb"
1212
import { clone, create } from "@bufbuild/protobuf"
13-
import { ArrowRight, ArrowUp, Check, Undo2 } from "lucide-react"
14-
import type React from "react"
13+
import { ArrowRight, ArrowUp, Check, Plus, Undo2 } from "lucide-react"
14+
import React from "react"
1515
import { toast } from "sonner"
1616

17+
import { ManualCompletionDialog } from "./manual-completion-dialog"
18+
1719
interface P extends React.ComponentProps<typeof Button> {
1820
options?: Completion_ButtonOptions
1921
preview?: boolean
@@ -23,32 +25,33 @@ const defaultOptions = create(Completion_ButtonOptionsSchema, {
2325
kind: { case: "complete", value: true },
2426
})
2527

26-
const applyButton = (completion: Completion, options: Completion_ButtonOptions) => {
28+
const applyButton = (
29+
completion: Completion,
30+
options: Completion_ButtonOptions,
31+
manualValue?: number,
32+
) => {
2733
switch (options.kind.case) {
2834
case "delta":
29-
completion.count += options.kind.value
35+
completion.count = manualValue ?? options.kind.value
3036
return
3137
case "percentage":
32-
completion.count += Math.ceil((options.kind.value / 100) * completion.target)
38+
completion.count += Math.ceil(
39+
((manualValue ?? options.kind.value) / 100) * completion.target,
40+
)
3341
return
3442
case "complete":
3543
completion.count = completion.target
3644
return
3745
case "set":
38-
completion.count = options.kind.value
46+
completion.count = manualValue ?? options.kind.value
3947
return
4048
}
4149
}
4250

43-
export const CompletionButton: React.FC<P> = ({
44-
options = defaultOptions,
45-
children,
46-
preview,
47-
...rest
48-
}) => {
51+
export const CompletionButton: React.FC<P> = ({ options = defaultOptions, preview, ...rest }) => {
4952
const { habit, color, update } = useHabitContext()
5053

51-
const handleClick = () => {
54+
const handleClick = (manualValue?: number) => {
5255
if (preview) return
5356

5457
const next = clone(HabitSchema, habit)
@@ -60,7 +63,7 @@ export const CompletionButton: React.FC<P> = ({
6063
const completion =
6164
next.completions[date] ?? create(CompletionSchema, { target: next.dailyTarget })
6265

63-
applyButton(completion, options)
66+
applyButton(completion, options, manualValue)
6467
next.completions[date] = completion
6568
}
6669

@@ -85,30 +88,78 @@ export const CompletionButton: React.FC<P> = ({
8588
<span>Reset</span>
8689
</>
8790
)
88-
} else if (kind.case === "delta") {
89-
className = cn(className, "gap-1")
91+
} else if (kind.case === "complete") {
9092
content = (
9193
<>
92-
<ArrowUp />
93-
<span>+{kind.value}</span>
94+
<Check />
95+
Done
9496
</>
9597
)
96-
} else if (kind.case === "percentage") {
98+
} else if (kind.case === "delta") {
99+
if (kind.value === 0) {
100+
return (
101+
<ManualCompletionDialog onSubmit={handleClick}>
102+
<Button
103+
variant="ghost"
104+
{...rest}
105+
className={className}
106+
disabled={rest.disabled || update.isPending}
107+
>
108+
<Plus />
109+
Add
110+
</Button>
111+
</ManualCompletionDialog>
112+
)
113+
}
114+
97115
className = cn(className, "gap-1")
98116
content = (
99117
<>
100-
<ArrowUp />
101-
<span>+{kind.value}%</span>
118+
<Plus />
119+
<span>{kind.value}</span>
102120
</>
103121
)
104-
} else if (kind.case === "complete") {
122+
} else if (kind.case === "percentage") {
123+
className = cn(className, "gap-1")
105124
content = (
106125
<>
107-
<Check />
108-
Done
126+
<Plus />
127+
<span>{kind.value}%</span>
109128
</>
110129
)
130+
131+
if (kind.value === 0) {
132+
return (
133+
<ManualCompletionDialog onSubmit={handleClick}>
134+
<Button
135+
variant="ghost"
136+
{...rest}
137+
className={className}
138+
disabled={rest.disabled || update.isPending}
139+
>
140+
<Plus />
141+
Add %
142+
</Button>
143+
</ManualCompletionDialog>
144+
)
145+
}
111146
} else if (kind.case === "set") {
147+
if (kind.value === 0) {
148+
return (
149+
<ManualCompletionDialog onSubmit={handleClick}>
150+
<Button
151+
variant="ghost"
152+
{...rest}
153+
className={className}
154+
disabled={rest.disabled || update.isPending}
155+
>
156+
<ArrowRight />
157+
Set
158+
</Button>
159+
</ManualCompletionDialog>
160+
)
161+
}
162+
112163
content = (
113164
<>
114165
<ArrowRight />
@@ -123,9 +174,9 @@ export const CompletionButton: React.FC<P> = ({
123174
{...rest}
124175
className={className}
125176
disabled={rest.disabled || update.isPending}
126-
onClick={handleClick}
177+
onClick={() => handleClick()}
127178
>
128-
{children ?? content}
179+
{content}
129180
</Button>
130181
)
131182
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Button } from "@/components/ui/button"
2+
import {
3+
Dialog,
4+
DialogContent,
5+
DialogDescription,
6+
DialogFooter,
7+
DialogTitle,
8+
DialogTrigger,
9+
} from "@/components/ui/dialog"
10+
import { Input } from "@/components/ui/input"
11+
import { Check, Undo2 } from "lucide-react"
12+
import React from "react"
13+
14+
interface P extends React.PropsWithChildren {
15+
onSubmit: (v: number) => void
16+
}
17+
18+
export const ManualCompletionDialog: React.FC<P> = ({ children, onSubmit }) => {
19+
const [open, setOpen] = React.useState(false)
20+
const [value, setValue] = React.useState(0)
21+
22+
const handleSubmit = () => {
23+
onSubmit(value)
24+
setOpen(false)
25+
setValue(0)
26+
}
27+
28+
return (
29+
<Dialog open={open} onOpenChange={setOpen}>
30+
<DialogTrigger asChild>{children}</DialogTrigger>
31+
<DialogContent>
32+
<DialogTitle>Manual update</DialogTitle>
33+
<DialogDescription>Set a value for manual update</DialogDescription>
34+
35+
<Input
36+
type="number"
37+
inputMode="numeric"
38+
value={value}
39+
onFocus={(e) => e.target.select()}
40+
onChange={(e) => setValue(e.target.valueAsNumber || 0)}
41+
/>
42+
43+
<DialogFooter>
44+
<Button variant="outline" onClick={() => setOpen(false)}>
45+
<Undo2 />
46+
Cancel
47+
</Button>
48+
<Button variant="outline" onClick={handleSubmit}>
49+
<Check className="text-emerald-600" />
50+
Cancel
51+
</Button>
52+
</DialogFooter>
53+
</DialogContent>
54+
</Dialog>
55+
)
56+
}

0 commit comments

Comments
 (0)