mirror of
https://github.com/aykhans/slash-e.git
synced 2026-08-03 14:12:42 +00:00
chore: refactor sheet components
This commit is contained in:
@@ -7,7 +7,7 @@ import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from "@/components/ui/sheet";
|
||||
import { Sheet, SheetBody, SheetContent, SheetFooter, SheetHeader, SheetTitle } from "@/components/ui/sheet";
|
||||
import useLoading from "@/hooks/useLoading";
|
||||
import { useCollectionStore, useShortcutStore, useWorkspaceStore } from "@/stores";
|
||||
import { Collection } from "@/types/proto/api/v1/collection_service";
|
||||
@@ -163,107 +163,108 @@ const CreateCollectionDrawer: React.FC<Props> = (props: Props) => {
|
||||
|
||||
return (
|
||||
<Sheet open={true} onOpenChange={onClose}>
|
||||
<SheetContent className="w-full sm:max-w-md overflow-y-auto">
|
||||
<SheetContent className="w-full sm:max-w-md">
|
||||
<SheetHeader>
|
||||
<SheetTitle>{isCreating ? "Create Collection" : "Edit Collection"}</SheetTitle>
|
||||
<SheetDescription>{isCreating ? "Create a new collection of shortcuts" : "Edit your collection details"}</SheetDescription>
|
||||
</SheetHeader>
|
||||
<div className="mt-6 space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">
|
||||
Name <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">c/</span>
|
||||
<SheetBody>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">
|
||||
Name <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">c/</span>
|
||||
<Input
|
||||
id="name"
|
||||
type="text"
|
||||
placeholder="An easy name to remember"
|
||||
value={state.collectionCreate.name}
|
||||
onChange={handleNameInputChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title">
|
||||
Title <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="name"
|
||||
id="title"
|
||||
type="text"
|
||||
placeholder="An easy name to remember"
|
||||
value={state.collectionCreate.name}
|
||||
onChange={handleNameInputChange}
|
||||
placeholder="A short title of your collection"
|
||||
value={state.collectionCreate.title}
|
||||
onChange={handleTitleInputChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title">
|
||||
Title <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="title"
|
||||
type="text"
|
||||
placeholder="A short title of your collection"
|
||||
value={state.collectionCreate.title}
|
||||
onChange={handleTitleInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="description">Description</Label>
|
||||
<Input
|
||||
id="description"
|
||||
type="text"
|
||||
placeholder="A slightly longer description"
|
||||
value={state.collectionCreate.description}
|
||||
onChange={handleDescriptionInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="public"
|
||||
checked={state.collectionCreate.visibility === Visibility.PUBLIC}
|
||||
onCheckedChange={(checked) =>
|
||||
setPartialState({
|
||||
collectionCreate: Object.assign(state.collectionCreate, {
|
||||
visibility: checked ? Visibility.PUBLIC : Visibility.WORKSPACE,
|
||||
}),
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="public" className="text-sm font-normal cursor-pointer">
|
||||
{t(`shortcut.visibility.public.description`)}
|
||||
</Label>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-baseline gap-2">
|
||||
<Label>Shortcuts</Label>
|
||||
<span className="text-sm text-muted-foreground">({selectedShortcuts.length})</span>
|
||||
{selectedShortcuts.length === 0 && <span className="text-sm italic text-muted-foreground">(Select a shortcut first)</span>}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="description">Description</Label>
|
||||
<Input
|
||||
id="description"
|
||||
type="text"
|
||||
placeholder="A slightly longer description"
|
||||
value={state.collectionCreate.description}
|
||||
onChange={handleDescriptionInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full py-1 px-px flex flex-row justify-start items-start flex-wrap gap-2">
|
||||
{selectedShortcuts.map((shortcut) => {
|
||||
return (
|
||||
<ShortcutView
|
||||
key={shortcut.id}
|
||||
className="!w-auto select-none max-w-[40%] cursor-pointer bg-muted shadow"
|
||||
shortcut={shortcut}
|
||||
onClick={() => {
|
||||
setSelectedShortcuts([...selectedShortcuts.filter((selectedShortcut) => selectedShortcut.id !== shortcut.id)]);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{unselectedShortcuts.map((shortcut) => {
|
||||
return (
|
||||
<ShortcutView
|
||||
key={shortcut.id}
|
||||
className="!w-auto select-none max-w-[40%] border-dashed cursor-pointer"
|
||||
shortcut={shortcut}
|
||||
onClick={() => {
|
||||
setSelectedShortcuts([...selectedShortcuts, shortcut]);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{selectedShortcuts.length + unselectedShortcuts.length === 0 && (
|
||||
<div className="w-full flex flex-row justify-center items-center text-muted-foreground">
|
||||
<Icon.PackageOpen className="w-6 h-auto" />
|
||||
<p className="ml-2">No shortcuts found.</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="public"
|
||||
checked={state.collectionCreate.visibility === Visibility.PUBLIC}
|
||||
onCheckedChange={(checked) =>
|
||||
setPartialState({
|
||||
collectionCreate: Object.assign(state.collectionCreate, {
|
||||
visibility: checked ? Visibility.PUBLIC : Visibility.WORKSPACE,
|
||||
}),
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="public" className="text-sm font-normal cursor-pointer">
|
||||
{t(`shortcut.visibility.public.description`)}
|
||||
</Label>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-baseline gap-2">
|
||||
<Label>Shortcuts</Label>
|
||||
<span className="text-sm text-muted-foreground">({selectedShortcuts.length})</span>
|
||||
{selectedShortcuts.length === 0 && <span className="text-sm italic text-muted-foreground">(Select a shortcut first)</span>}
|
||||
</div>
|
||||
<div className="w-full py-1 px-px flex flex-row justify-start items-start flex-wrap gap-2">
|
||||
{selectedShortcuts.map((shortcut) => {
|
||||
return (
|
||||
<ShortcutView
|
||||
key={shortcut.id}
|
||||
className="!w-auto select-none max-w-[40%] cursor-pointer bg-muted shadow"
|
||||
shortcut={shortcut}
|
||||
onClick={() => {
|
||||
setSelectedShortcuts([...selectedShortcuts.filter((selectedShortcut) => selectedShortcut.id !== shortcut.id)]);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{unselectedShortcuts.map((shortcut) => {
|
||||
return (
|
||||
<ShortcutView
|
||||
key={shortcut.id}
|
||||
className="!w-auto select-none max-w-[40%] border-dashed cursor-pointer"
|
||||
shortcut={shortcut}
|
||||
onClick={() => {
|
||||
setSelectedShortcuts([...selectedShortcuts, shortcut]);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{selectedShortcuts.length + unselectedShortcuts.length === 0 && (
|
||||
<div className="w-full flex flex-row justify-center items-center text-muted-foreground">
|
||||
<Icon.PackageOpen className="w-6 h-auto" />
|
||||
<p className="ml-2">No shortcuts found.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SheetFooter className="mt-6">
|
||||
</SheetBody>
|
||||
<SheetFooter>
|
||||
<Button variant="outline" disabled={requestState.isLoading} onClick={onClose}>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from "@/components/ui/sheet";
|
||||
import { Sheet, SheetBody, SheetContent, SheetFooter, SheetHeader, SheetTitle } from "@/components/ui/sheet";
|
||||
import { workspaceServiceClient } from "@/grpcweb";
|
||||
import { absolutifyLink } from "@/helpers/utils";
|
||||
import useLoading from "@/hooks/useLoading";
|
||||
@@ -137,150 +137,136 @@ const CreateIdentityProviderDrawer: React.FC<Props> = (props: Props) => {
|
||||
|
||||
return (
|
||||
<Sheet open={true} onOpenChange={onClose}>
|
||||
<SheetContent className="w-full sm:max-w-md overflow-y-auto">
|
||||
<SheetContent className="w-full sm:max-w-md">
|
||||
<SheetHeader>
|
||||
<SheetTitle>{isCreating ? "Create Identity Provider" : "Edit Identity Provider"}</SheetTitle>
|
||||
<SheetDescription>
|
||||
{isCreating ? "Configure a new OAuth2 identity provider" : "Edit your identity provider settings"}
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
<div className="mt-6 space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title">
|
||||
Title <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="title"
|
||||
type="text"
|
||||
placeholder="A short title will be displayed in the UI"
|
||||
value={state.identityProviderCreate.title}
|
||||
onChange={handleTitleInputChange}
|
||||
/>
|
||||
<SheetBody>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title">
|
||||
Title <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="title"
|
||||
type="text"
|
||||
placeholder="A short title will be displayed in the UI"
|
||||
value={state.identityProviderCreate.title}
|
||||
onChange={handleTitleInputChange}
|
||||
/>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium">Identity provider information</p>
|
||||
{isCreating && (
|
||||
<div className="rounded-md py-2 px-3 bg-secondary text-sm w-full break-all">
|
||||
<span className="text-muted-foreground">Redirect URL</span>
|
||||
<br />
|
||||
<code className="text-xs">{absolutifyLink("/auth/callback")}</code>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="client-id">
|
||||
Client ID <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="client-id"
|
||||
type="text"
|
||||
placeholder="Client ID of the OAuth2 provider"
|
||||
value={state.identityProviderCreate.config?.oauth2?.clientId}
|
||||
onChange={(e) => handleOAuth2ConfigChange(e, "clientId")}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="client-secret">
|
||||
Client Secret <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="client-secret"
|
||||
type="text"
|
||||
placeholder="Client Secret of the OAuth2 provider"
|
||||
value={state.identityProviderCreate.config?.oauth2?.clientSecret}
|
||||
onChange={(e) => handleOAuth2ConfigChange(e, "clientSecret")}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="auth-url">
|
||||
Authorization endpoint <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="auth-url"
|
||||
type="text"
|
||||
placeholder="Authorization endpoint of the OAuth2 provider"
|
||||
value={state.identityProviderCreate.config?.oauth2?.authUrl}
|
||||
onChange={(e) => handleOAuth2ConfigChange(e, "authUrl")}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="token-url">
|
||||
Token endpoint <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="token-url"
|
||||
type="text"
|
||||
placeholder="Token endpoint of the OAuth2 provider"
|
||||
value={state.identityProviderCreate.config?.oauth2?.tokenUrl}
|
||||
onChange={(e) => handleOAuth2ConfigChange(e, "tokenUrl")}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="user-info-url">
|
||||
User endpoint <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="user-info-url"
|
||||
type="text"
|
||||
placeholder="User endpoint of the OAuth2 provider"
|
||||
value={state.identityProviderCreate.config?.oauth2?.userInfoUrl}
|
||||
onChange={(e) => handleOAuth2ConfigChange(e, "userInfoUrl")}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="scopes">
|
||||
Scopes <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="scopes"
|
||||
type="text"
|
||||
placeholder="Scopes of the OAuth2 provider, separated by space"
|
||||
value={state.identityProviderCreate.config?.oauth2?.scopes.join(" ")}
|
||||
onChange={(e) => handleOAuth2ConfigChange(e, "scopes")}
|
||||
/>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium">Field mapping</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="identifier">
|
||||
Identifier <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="identifier"
|
||||
type="text"
|
||||
placeholder="The field in the user info response to identify the user"
|
||||
value={state.identityProviderCreate.config?.oauth2?.fieldMapping?.identifier}
|
||||
onChange={(e) => handleFieldMappingChange(e, "identifier")}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="display-name">Display name</Label>
|
||||
<Input
|
||||
id="display-name"
|
||||
type="text"
|
||||
placeholder="The field in the user info response to display the user"
|
||||
value={state.identityProviderCreate.config?.oauth2?.fieldMapping?.displayName}
|
||||
onChange={(e) => handleFieldMappingChange(e, "displayName")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium">Identity provider information</p>
|
||||
{isCreating && (
|
||||
<div className="rounded-md py-2 px-3 bg-secondary text-sm w-full break-all">
|
||||
<span className="text-muted-foreground">Redirect URL</span>
|
||||
<br />
|
||||
<code className="text-xs">{absolutifyLink("/auth/callback")}</code>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="client-id">
|
||||
Client ID <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="client-id"
|
||||
type="text"
|
||||
placeholder="Client ID of the OAuth2 provider"
|
||||
value={state.identityProviderCreate.config?.oauth2?.clientId}
|
||||
onChange={(e) => handleOAuth2ConfigChange(e, "clientId")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="client-secret">
|
||||
Client Secret <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="client-secret"
|
||||
type="text"
|
||||
placeholder="Client Secret of the OAuth2 provider"
|
||||
value={state.identityProviderCreate.config?.oauth2?.clientSecret}
|
||||
onChange={(e) => handleOAuth2ConfigChange(e, "clientSecret")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="auth-url">
|
||||
Authorization endpoint <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="auth-url"
|
||||
type="text"
|
||||
placeholder="Authorization endpoint of the OAuth2 provider"
|
||||
value={state.identityProviderCreate.config?.oauth2?.authUrl}
|
||||
onChange={(e) => handleOAuth2ConfigChange(e, "authUrl")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="token-url">
|
||||
Token endpoint <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="token-url"
|
||||
type="text"
|
||||
placeholder="Token endpoint of the OAuth2 provider"
|
||||
value={state.identityProviderCreate.config?.oauth2?.tokenUrl}
|
||||
onChange={(e) => handleOAuth2ConfigChange(e, "tokenUrl")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="user-info-url">
|
||||
User endpoint <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="user-info-url"
|
||||
type="text"
|
||||
placeholder="User endpoint of the OAuth2 provider"
|
||||
value={state.identityProviderCreate.config?.oauth2?.userInfoUrl}
|
||||
onChange={(e) => handleOAuth2ConfigChange(e, "userInfoUrl")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="scopes">
|
||||
Scopes <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="scopes"
|
||||
type="text"
|
||||
placeholder="Scopes of the OAuth2 provider, separated by space"
|
||||
value={state.identityProviderCreate.config?.oauth2?.scopes.join(" ")}
|
||||
onChange={(e) => handleOAuth2ConfigChange(e, "scopes")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium">Field mapping</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="identifier">
|
||||
Identifier <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="identifier"
|
||||
type="text"
|
||||
placeholder="The field in the user info response to identify the user"
|
||||
value={state.identityProviderCreate.config?.oauth2?.fieldMapping?.identifier}
|
||||
onChange={(e) => handleFieldMappingChange(e, "identifier")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="display-name">Display name</Label>
|
||||
<Input
|
||||
id="display-name"
|
||||
type="text"
|
||||
placeholder="The field in the user info response to display the user"
|
||||
value={state.identityProviderCreate.config?.oauth2?.fieldMapping?.displayName}
|
||||
onChange={(e) => handleFieldMappingChange(e, "displayName")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SheetFooter className="mt-6">
|
||||
</SheetBody>
|
||||
<SheetFooter>
|
||||
<Button variant="outline" disabled={requestState.isLoading} onClick={onClose}>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from "@/components/ui/sheet";
|
||||
import { Sheet, SheetBody, SheetContent, SheetFooter, SheetHeader, SheetTitle } from "@/components/ui/sheet";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import useLoading from "@/hooks/useLoading";
|
||||
import { useShortcutStore, useWorkspaceStore } from "@/stores";
|
||||
@@ -211,163 +211,156 @@ const CreateShortcutDrawer: React.FC<Props> = (props: Props) => {
|
||||
|
||||
return (
|
||||
<Sheet open={true} onOpenChange={onClose}>
|
||||
<SheetContent className="w-full sm:max-w-md overflow-y-auto">
|
||||
<SheetContent className="w-full sm:max-w-md">
|
||||
<SheetHeader>
|
||||
<SheetTitle>{isCreating ? "Create Shortcut" : "Edit Shortcut"}</SheetTitle>
|
||||
<SheetDescription>{isCreating ? "Create a new shortcut" : "Edit your shortcut details"}</SheetDescription>
|
||||
</SheetHeader>
|
||||
<div className="mt-6 space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">
|
||||
Name <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">s/</span>
|
||||
<SheetBody>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">
|
||||
Name <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">s/</span>
|
||||
<Input
|
||||
id="name"
|
||||
type="text"
|
||||
placeholder="An easy name to remember"
|
||||
value={state.shortcutCreate.name}
|
||||
onChange={handleNameInputChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="link">
|
||||
Link <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="name"
|
||||
id="link"
|
||||
type="text"
|
||||
placeholder="An easy name to remember"
|
||||
value={state.shortcutCreate.name}
|
||||
onChange={handleNameInputChange}
|
||||
placeholder="The destination link of the shortcut"
|
||||
value={state.shortcutCreate.link}
|
||||
onChange={handleLinkInputChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="link">
|
||||
Link <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="link"
|
||||
type="text"
|
||||
placeholder="The destination link of the shortcut"
|
||||
value={state.shortcutCreate.link}
|
||||
onChange={handleLinkInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title">Title</Label>
|
||||
<Input
|
||||
id="title"
|
||||
type="text"
|
||||
placeholder="The title of the shortcut"
|
||||
value={state.shortcutCreate.title}
|
||||
onChange={handleTitleInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="description">Description</Label>
|
||||
<Input
|
||||
id="description"
|
||||
type="text"
|
||||
placeholder="A short description of the shortcut"
|
||||
value={state.shortcutCreate.description}
|
||||
onChange={handleDescriptionInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="tags">Tags</Label>
|
||||
<Input id="tags" type="text" placeholder="The tags of shortcut" value={tag} onChange={handleTagsInputChange} />
|
||||
{tagSuggestions.length > 0 && (
|
||||
<div className="flex flex-row items-start gap-2 mt-2">
|
||||
<Icon.Asterisk className="w-4 h-auto shrink-0 mt-0.5 text-muted-foreground" />
|
||||
<div className="flex flex-row flex-wrap gap-2">
|
||||
{tagSuggestions.map((tag) => (
|
||||
<span
|
||||
className="text-muted-foreground cursor-pointer text-sm hover:text-foreground transition-colors"
|
||||
key={tag}
|
||||
onClick={() => handleTagSuggestionsClick(tag)}
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title">Title</Label>
|
||||
<Input
|
||||
id="title"
|
||||
type="text"
|
||||
placeholder="The title of the shortcut"
|
||||
value={state.shortcutCreate.title}
|
||||
onChange={handleTitleInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="description">Description</Label>
|
||||
<Input
|
||||
id="description"
|
||||
type="text"
|
||||
placeholder="A short description of the shortcut"
|
||||
value={state.shortcutCreate.description}
|
||||
onChange={handleDescriptionInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="tags">Tags</Label>
|
||||
<Input id="tags" type="text" placeholder="The tags of shortcut" value={tag} onChange={handleTagsInputChange} />
|
||||
{tagSuggestions.length > 0 && (
|
||||
<div className="flex flex-row items-start gap-2 mt-2">
|
||||
<Icon.Asterisk className="w-4 h-auto shrink-0 mt-0.5 text-muted-foreground" />
|
||||
<div className="flex flex-row flex-wrap gap-2">
|
||||
{tagSuggestions.map((tag) => (
|
||||
<span
|
||||
className="text-muted-foreground cursor-pointer text-sm hover:text-foreground transition-colors"
|
||||
key={tag}
|
||||
onClick={() => handleTagSuggestionsClick(tag)}
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="public"
|
||||
checked={state.shortcutCreate.visibility === Visibility.PUBLIC}
|
||||
onCheckedChange={(checked) =>
|
||||
setPartialState({
|
||||
shortcutCreate: Object.assign(state.shortcutCreate, {
|
||||
visibility: checked ? Visibility.PUBLIC : Visibility.WORKSPACE,
|
||||
}),
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="public" className="text-sm font-normal cursor-pointer">
|
||||
{t(`shortcut.visibility.public.description`)}
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<Separator className="my-4" />
|
||||
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<div
|
||||
className={classnames(
|
||||
"flex flex-row justify-between items-center px-3 py-2 cursor-pointer hover:bg-accent transition-colors",
|
||||
showOpenGraphMetadata && "bg-accent border-b",
|
||||
)}
|
||||
onClick={() => setShowOpenGraphMetadata(!showOpenGraphMetadata)}
|
||||
>
|
||||
<span className="text-sm flex items-center gap-1">
|
||||
Social media metadata
|
||||
<Icon.Sparkles className="w-4 h-auto text-primary" />
|
||||
</span>
|
||||
<Icon.ChevronDown
|
||||
className={classnames("w-4 h-auto text-muted-foreground transition-transform", showOpenGraphMetadata && "rotate-180")}
|
||||
/>
|
||||
</div>
|
||||
{showOpenGraphMetadata && (
|
||||
<div className="p-3 space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="og-image" className="text-sm">
|
||||
Image URL
|
||||
</Label>
|
||||
<Input
|
||||
id="og-image"
|
||||
type="text"
|
||||
placeholder="https://the.link.to/the/image.png"
|
||||
value={state.shortcutCreate.ogMetadata?.image}
|
||||
onChange={handleOpenGraphMetadataImageChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="og-title" className="text-sm">
|
||||
Title
|
||||
</Label>
|
||||
<Input
|
||||
id="og-title"
|
||||
type="text"
|
||||
placeholder="Slash - An open source, self-hosted platform"
|
||||
value={state.shortcutCreate.ogMetadata?.title}
|
||||
onChange={handleOpenGraphMetadataTitleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="og-description" className="text-sm">
|
||||
Description
|
||||
</Label>
|
||||
<Textarea
|
||||
id="og-description"
|
||||
placeholder="An open source, self-hosted platform for sharing and managing your most frequently used links."
|
||||
rows={3}
|
||||
value={state.shortcutCreate.ogMetadata?.description}
|
||||
onChange={handleOpenGraphMetadataDescriptionChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="public"
|
||||
checked={state.shortcutCreate.visibility === Visibility.PUBLIC}
|
||||
onCheckedChange={(checked) =>
|
||||
setPartialState({
|
||||
shortcutCreate: Object.assign(state.shortcutCreate, {
|
||||
visibility: checked ? Visibility.PUBLIC : Visibility.WORKSPACE,
|
||||
}),
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="public" className="text-sm font-normal cursor-pointer">
|
||||
{t(`shortcut.visibility.public.description`)}
|
||||
</Label>
|
||||
</div>
|
||||
<Separator className="my-4" />
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<div
|
||||
className={classnames(
|
||||
"flex flex-row justify-between items-center px-3 py-2 cursor-pointer hover:bg-accent transition-colors",
|
||||
showOpenGraphMetadata && "bg-accent border-b",
|
||||
)}
|
||||
onClick={() => setShowOpenGraphMetadata(!showOpenGraphMetadata)}
|
||||
>
|
||||
<span className="text-sm flex items-center gap-1">
|
||||
Social media metadata
|
||||
<Icon.Sparkles className="w-4 h-auto text-primary" />
|
||||
</span>
|
||||
<Icon.ChevronDown
|
||||
className={classnames("w-4 h-auto text-muted-foreground transition-transform", showOpenGraphMetadata && "rotate-180")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{showOpenGraphMetadata && (
|
||||
<div className="p-3 space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="og-image" className="text-sm">
|
||||
Image URL
|
||||
</Label>
|
||||
<Input
|
||||
id="og-image"
|
||||
type="text"
|
||||
placeholder="https://the.link.to/the/image.png"
|
||||
value={state.shortcutCreate.ogMetadata?.image}
|
||||
onChange={handleOpenGraphMetadataImageChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="og-title" className="text-sm">
|
||||
Title
|
||||
</Label>
|
||||
<Input
|
||||
id="og-title"
|
||||
type="text"
|
||||
placeholder="Slash - An open source, self-hosted platform"
|
||||
value={state.shortcutCreate.ogMetadata?.title}
|
||||
onChange={handleOpenGraphMetadataTitleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="og-description" className="text-sm">
|
||||
Description
|
||||
</Label>
|
||||
<Textarea
|
||||
id="og-description"
|
||||
placeholder="An open source, self-hosted platform for sharing and managing your most frequently used links."
|
||||
rows={3}
|
||||
value={state.shortcutCreate.ogMetadata?.description}
|
||||
onChange={handleOpenGraphMetadataDescriptionChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SheetFooter className="mt-6">
|
||||
</SheetBody>
|
||||
<SheetFooter>
|
||||
<Button variant="outline" onClick={onClose} disabled={requestState.isLoading}>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
|
||||
@@ -28,7 +28,7 @@ const SheetOverlay = React.forwardRef<
|
||||
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
|
||||
|
||||
const sheetVariants = cva(
|
||||
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
||||
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 flex flex-col overflow-hidden",
|
||||
{
|
||||
variants: {
|
||||
side: {
|
||||
@@ -64,12 +64,17 @@ const SheetContent = React.forwardRef<React.ElementRef<typeof SheetPrimitive.Con
|
||||
SheetContent.displayName = SheetPrimitive.Content.displayName;
|
||||
|
||||
const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
|
||||
<div className={cn("flex flex-col space-y-2 text-center sm:text-left flex-shrink-0", className)} {...props} />
|
||||
);
|
||||
SheetHeader.displayName = "SheetHeader";
|
||||
|
||||
const SheetBody = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn("flex-1 overflow-y-auto min-h-0", className)} {...props} />
|
||||
);
|
||||
SheetBody.displayName = "SheetBody";
|
||||
|
||||
const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
|
||||
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 pt-4 flex-shrink-0", className)} {...props} />
|
||||
);
|
||||
SheetFooter.displayName = "SheetFooter";
|
||||
|
||||
@@ -89,4 +94,16 @@ const SheetDescription = React.forwardRef<
|
||||
));
|
||||
SheetDescription.displayName = SheetPrimitive.Description.displayName;
|
||||
|
||||
export { Sheet, SheetPortal, SheetOverlay, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription };
|
||||
export {
|
||||
Sheet,
|
||||
SheetPortal,
|
||||
SheetOverlay,
|
||||
SheetTrigger,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetBody,
|
||||
SheetFooter,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user