Skip to content

Commit

Permalink
feat: create Command component
Browse files Browse the repository at this point in the history
Signed-off-by: Arthur Broudoux <[email protected]>
  • Loading branch information
abroudoux committed Nov 17, 2024
1 parent 468217f commit 1380241
Show file tree
Hide file tree
Showing 16 changed files with 282 additions and 7 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@types/uuid": "^10.0.0",
"autoprefixer": "^10.4.20",
"bits-ui": "^0.21.15",
"cmdk-sv": "^0.0.18",
"drizzle-kit": "^0.24.2",
"eslint": "^9.11.1",
"eslint-config-prettier": "^9.1.0",
Expand Down
14 changes: 14 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 53 additions & 0 deletions src/lib/components/global/Command.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<script lang="ts">
import { onMount } from "svelte";
import Mountain from "lucide-svelte/icons/mountain";
import Trash from "lucide-svelte/icons/trash";
import { Dialog, Input, List, Empty, Group, Item, Separator } from "$lib/components/ui/command";
import { manageHabits, createHabit } from "$stores/habit.store";
let open: boolean = false;
onMount(() => {
function handleKeydown(e: KeyboardEvent) {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
open = !open;
}
}
document.addEventListener("keydown", handleKeydown);
return () => {
document.removeEventListener("keydown", handleKeydown);
};
});
function handleManageHabits() {
manageHabits.update((value: boolean) => !value);
open = false;
}
function handleCreateHabit() {
createHabit.set(true);
open = false;
}
</script>

<Dialog bind:open>
<Input placeholder="Type a command or search..." />
<List>
<Empty>No results found.</Empty>
<Group heading="Suggestions">
<Item value="createHabitCmd" onSelect={handleCreateHabit}>
<Mountain class="mr-2 h-4 w-4" />
<span>Create new habit</span>
</Item>
<Item value="manageHabitsCmd" onSelect={handleManageHabits}>
<Trash class="mr-2 h-4 w-4" />
<span>Manage habits</span>
</Item>
</Group>
<Separator />
</List>
</Dialog>
18 changes: 11 additions & 7 deletions src/lib/components/habits/CreateHabitModal.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { goto, invalidateAll } from "$app/navigation";
import { toast } from "svelte-sonner";
import { writable } from "svelte/store";
import {
Root,
Expand All @@ -19,10 +20,10 @@
import type { HabitRequest } from "$utils/types/services";
import { postHabit } from "$services/habits.services";
import { createHabit } from "$stores/habit.store";
let newHabit: HabitRequest = { name: "" };
let isLoading: boolean = false;
let isOpen: boolean = false;
let createMultipleHabits: boolean = false;
let multipleHabits: string = "";
Expand All @@ -38,19 +39,22 @@
await postHabit(fetch, newHabit);
}
isOpen = false;
createHabit.set(false);
newHabit = { name: "" };
multipleHabits = "";
createMultipleHabits = false;
await invalidateAll();
await goto("/");
toast.success("Habit(s) created successfully");
isLoading = false;
}
</script>

<Root bind:open={isOpen}>
<Trigger class={buttonVariants({ variant: "default" })}>Next step</Trigger>
<Root bind:open={$createHabit}>
<Trigger class={buttonVariants({ variant: "default" })} on:click={() => createHabit.set(true)}>
Next step
</Trigger>
<Content class="sm:max-w-[425px]">
<form action="POST" on:submit|preventDefault={handleCreateHabit}>
<Header class="pb-4">
Expand All @@ -77,9 +81,9 @@
<Label for="createMultipleHabits">Create multiple habits</Label>
</div>
<Footer>
<Button type="submit" disabled={isLoading}
>{isLoading ? "Creating..." : "Next step"}</Button
>
<Button type="submit" disabled={isLoading}>
{isLoading ? "Creating..." : "Next step"}
</Button>
</Footer>
</div>
</form>
Expand Down
23 changes: 23 additions & 0 deletions src/lib/components/ui/command/command-dialog.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script lang="ts">
import type { Dialog as DialogPrimitive } from "bits-ui";
import type { Command as CommandPrimitive } from "cmdk-sv";
import Command from "./command.svelte";
import * as Dialog from "$lib/components/ui/dialog/index.js";
type $$Props = DialogPrimitive.Props & CommandPrimitive.CommandProps;
export let open: $$Props["open"] = false;
export let value: $$Props["value"] = undefined;
</script>

<Dialog.Root bind:open {...$$restProps}>
<Dialog.Content class="overflow-hidden p-0 shadow-lg">
<Command
class="[&_[data-cmdk-group-heading]]:text-muted-foreground [&_[data-cmdk-group-heading]]:px-2 [&_[data-cmdk-group-heading]]:font-medium [&_[data-cmdk-group]:not([hidden])_~[data-cmdk-group]]:pt-0 [&_[data-cmdk-group]]:px-2 [&_[data-cmdk-input-wrapper]_svg]:h-5 [&_[data-cmdk-input-wrapper]_svg]:w-5 [&_[data-cmdk-input]]:h-12 [&_[data-cmdk-item]]:px-2 [&_[data-cmdk-item]]:py-3 [&_[data-cmdk-item]_svg]:h-5 [&_[data-cmdk-item]_svg]:w-5"
{...$$restProps}
bind:value
>
<slot />
</Command>
</Dialog.Content>
</Dialog.Root>
12 changes: 12 additions & 0 deletions src/lib/components/ui/command/command-empty.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.EmptyProps;
let className: string | undefined | null = undefined;
export { className as class };
</script>

<CommandPrimitive.Empty class={cn("py-6 text-center text-sm", className)} {...$$restProps}>
<slot />
</CommandPrimitive.Empty>
18 changes: 18 additions & 0 deletions src/lib/components/ui/command/command-group.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.GroupProps;
let className: string | undefined | null = undefined;
export { className as class };
</script>

<CommandPrimitive.Group
class={cn(
"text-foreground [&_[data-cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[data-cmdk-group-heading]]:px-2 [&_[data-cmdk-group-heading]]:py-1.5 [&_[data-cmdk-group-heading]]:text-xs [&_[data-cmdk-group-heading]]:font-medium",
className
)}
{...$$restProps}
>
<slot />
</CommandPrimitive.Group>
23 changes: 23 additions & 0 deletions src/lib/components/ui/command/command-input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import Search from "lucide-svelte/icons/search";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.InputProps;
let className: string | undefined | null = undefined;
export { className as class };
export let value: string = "";
</script>

<div class="flex items-center border-b px-2" data-cmdk-input-wrapper="">
<Search class="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
class={cn(
"placeholder:text-muted-foreground flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...$$restProps}
bind:value
/>
</div>
24 changes: 24 additions & 0 deletions src/lib/components/ui/command/command-item.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.ItemProps;
export let asChild = false;
let className: string | undefined | null = undefined;
export { className as class };
</script>

<CommandPrimitive.Item
{asChild}
class={cn(
"aria-selected:bg-accent aria-selected:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...$$restProps}
let:action
let:attrs
>
<slot {action} {attrs} />
</CommandPrimitive.Item>
15 changes: 15 additions & 0 deletions src/lib/components/ui/command/command-list.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.ListProps;
let className: string | undefined | null = undefined;
export { className as class };
</script>

<CommandPrimitive.List
class={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
{...$$restProps}
>
<slot />
</CommandPrimitive.List>
10 changes: 10 additions & 0 deletions src/lib/components/ui/command/command-separator.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.SeparatorProps;
let className: string | undefined | null = undefined;
export { className as class };
</script>

<CommandPrimitive.Separator class={cn("bg-border -mx-1 h-px", className)} {...$$restProps} />
16 changes: 16 additions & 0 deletions src/lib/components/ui/command/command-shortcut.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLSpanElement>;
let className: string | undefined | null = undefined;
export { className as class };
</script>

<span
class={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
{...$$restProps}
>
<slot />
</span>
22 changes: 22 additions & 0 deletions src/lib/components/ui/command/command.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.CommandProps;
export let value: $$Props["value"] = undefined;
let className: string | undefined | null = undefined;
export { className as class };
</script>

<CommandPrimitive.Root
class={cn(
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
className
)}
bind:value
{...$$restProps}
>
<slot />
</CommandPrimitive.Root>
37 changes: 37 additions & 0 deletions src/lib/components/ui/command/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Command as CommandPrimitive } from "cmdk-sv";

import Root from "./command.svelte";
import Dialog from "./command-dialog.svelte";
import Empty from "./command-empty.svelte";
import Group from "./command-group.svelte";
import Item from "./command-item.svelte";
import Input from "./command-input.svelte";
import List from "./command-list.svelte";
import Separator from "./command-separator.svelte";
import Shortcut from "./command-shortcut.svelte";

const Loading = CommandPrimitive.Loading;

export {
Root,
Dialog,
Empty,
Group,
Item,
Input,
List,
Separator,
Shortcut,
Loading,
//
Root as Command,
Dialog as CommandDialog,
Empty as CommandEmpty,
Group as CommandGroup,
Item as CommandItem,
Input as CommandInput,
List as CommandList,
Separator as CommandSeparator,
Shortcut as CommandShortcut,
Loading as CommandLoading,
};
2 changes: 2 additions & 0 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import Header from "$lib/components/global/Header.svelte";
import Sidebar from "$lib/components/global/Sidebar.svelte";
import Command from "$lib/components/global/Command.svelte";
import { Toaster } from "$lib/components/ui/sonner";
import { manageDay } from "$utils/days";
Expand All @@ -26,3 +27,4 @@
</div>

<Toaster />
<Command />
1 change: 1 addition & 0 deletions src/stores/habit.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import { writable, type Writable } from "svelte/store";
import type { Habit } from "$utils/types/entities";

export const manageHabits: Writable<boolean> = writable(false);
export const createHabit: Writable<boolean> = writable(false);
export const habitsData = writable<Habit[]>([]);

0 comments on commit 1380241

Please sign in to comment.