mirror of
https://github.com/supabase/supabase.git
synced 2026-05-06 08:56:46 -04:00
chore(studio): better empty states for new users (#41044)
* box-plus icon * design-system updates * new project empty state * org empty state * better /organization empty state * add missing dependency * fix admonition * remove prop * fix badge * use org slug * clarify * org note found cleanup
This commit is contained in:
@@ -55,6 +55,7 @@ Follow these steps to add a new custom icon to the Supabase icon library.
|
||||
|
||||
- Exported at 24x24px with `viewBox="0 0 24 24"`
|
||||
- Uses `stroke="currentColor"` for strokes (no hardcoded colors)
|
||||
- Uses `stroke-width="1.5"` unless there is a good reason to deviate
|
||||
- Uses `fill="none"` for fills (no hardcoded colors)
|
||||
- Icon content is optically centered and around 18x18px within the 24x24 frame
|
||||
- Any unnecessary elements like `<clipPath>`, `<defs>`, and `<g>` wrappers have been removed
|
||||
|
||||
@@ -5,36 +5,12 @@ description: Convey the absence of data and provide clear instruction for what t
|
||||
|
||||
Empty states convey the fact that there is nothing to list, perform, or display on the current page. **Ideally**, they also provide a clear action for the user to take.
|
||||
|
||||
## Missing route
|
||||
|
||||
Users may accidentally navigate to a non-existent dynamic route, such as a non-existent bucket in [Storage](https://supabase.com/dashboard/project/_/storage) or a non-existent table in the [Table Editor](https://supabase.com/dashboard/project/_/editor). In these cases, follow the pattern of a centered [Admonition](../fragments/admonition) as shown below.
|
||||
|
||||
<ComponentPreview name="empty-state-missing-route" peekCode wide />
|
||||
|
||||
## No data
|
||||
|
||||
There are two ways an empty state may be displayed in cases where there is no data:
|
||||
|
||||
- **Zero results**: no data after a search or filter
|
||||
- **Initial state**: no data to begin with
|
||||
|
||||
### Zero results
|
||||
|
||||
Data-heavy presentations without results should have an empty state that broadly matches the state when there is data. This makes the transition between the two states more seamless.
|
||||
|
||||
#### Table
|
||||
|
||||
A [Table](../components/table) instance with zero results should display a single row. Dulling the TableHead text color and removing the TableCell hover state can further reinforce the lack of usable data.
|
||||
|
||||
<ComponentPreview name="empty-state-zero-items-table" peekCode wide />
|
||||
|
||||
#### DataGrid
|
||||
|
||||
[DataGrid](../components/data-table) typically spans the full height and width of a container. A classic example is [Users](https://supabase.com/dashboard/project/_/auth/users), which (as it sounds) displays a list of the project’s registered users. Any instance with zero results should display a more prominent empty with a clear title, description, and supporting illustration.
|
||||
|
||||
<ComponentPreview name="empty-state-zero-items-data-grid" peekCode wide />
|
||||
|
||||
Other DataGrid instances include [Cron Jobs](https://supabase.com/dashboard/project/_/integrations/cron/jobs) and [Queues](https://supabase.com/dashboard/project/_/integrations/queues).
|
||||
- **Zero results**: no data after a search or filter
|
||||
|
||||
### Initial state
|
||||
|
||||
@@ -56,6 +32,30 @@ Or perhaps the list type is data-heavy or does not benefit from additional infor
|
||||
|
||||
Keep in mind that empty states will likely appear after a visual loading state. Consider layout shift and button placement during and after the transition.
|
||||
|
||||
### Zero results
|
||||
|
||||
Data-heavy presentations without results should have an empty state that broadly matches the state when there is data. This makes the transition between the two states more seamless.
|
||||
|
||||
#### Table
|
||||
|
||||
A [Table](../components/table) instance with zero results should display a single row. Dulling the TableHead text color and removing the TableCell hover state can further reinforce the lack of usable data.
|
||||
|
||||
<ComponentPreview name="empty-state-zero-items-table" peekCode wide />
|
||||
|
||||
#### DataGrid
|
||||
|
||||
[DataGrid](../components/data-table) typically spans the full height and width of a container. A classic example is [Users](https://supabase.com/dashboard/project/_/auth/users), which (as it sounds) displays a list of the project’s registered users. Any instance with zero results should display a more prominent empty with a clear title, description, and supporting illustration.
|
||||
|
||||
<ComponentPreview name="empty-state-zero-items-data-grid" peekCode wide />
|
||||
|
||||
Other DataGrid instances include [Cron Jobs](https://supabase.com/dashboard/project/_/integrations/cron/jobs) and [Queues](https://supabase.com/dashboard/project/_/integrations/queues).
|
||||
|
||||
## Missing route
|
||||
|
||||
Users may accidentally navigate to a non-existent dynamic route, such as a non-existent bucket in [Storage](https://supabase.com/dashboard/project/_/storage) or a non-existent table in the [Table Editor](https://supabase.com/dashboard/project/_/editor). In these cases, follow the pattern of a centered [Admonition](../fragments/admonition) as shown below.
|
||||
|
||||
<ComponentPreview name="empty-state-missing-route" peekCode wide />
|
||||
|
||||
## Components
|
||||
|
||||
For presentational empty states (initial states with value propositions and actions), use the [EmptyStatePresentational](../fragments/empty-state-presentational) component from `ui-patterns`. This component provides a consistent structure with support for icons, titles, descriptions, and action buttons.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { BoxPlus } from 'icons'
|
||||
import { Plus } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from 'ui'
|
||||
import { EmptyStatePresentational } from 'ui-patterns'
|
||||
import { ShimmeringCard } from './ShimmeringCard'
|
||||
|
||||
export const Header = () => {
|
||||
@@ -126,35 +128,29 @@ export const NoProjectsState = ({ slug }: { slug: string }) => {
|
||||
const projectCreationEnabled = useIsFeatureEnabled('projects:create')
|
||||
|
||||
return (
|
||||
<div className="col-span-4 space-y-4 rounded-lg border border-dashed p-6 text-center">
|
||||
<div className="space-y-1">
|
||||
<p>No projects</p>
|
||||
<p className="text-sm text-foreground-light">Get started by creating a new project.</p>
|
||||
</div>
|
||||
|
||||
<EmptyStatePresentational
|
||||
icon={BoxPlus}
|
||||
title="Create a project"
|
||||
description="Launch a complete backend built on Postgres."
|
||||
>
|
||||
{projectCreationEnabled && (
|
||||
<Button asChild icon={<Plus />}>
|
||||
<Link href={`/new/${slug}`}>New Project</Link>
|
||||
<Button size="tiny" type="default" asChild icon={<Plus />}>
|
||||
<Link href={`/new/${slug}`}>New project</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</EmptyStatePresentational>
|
||||
)
|
||||
}
|
||||
|
||||
export const NoOrganizationsState = () => {
|
||||
export const NoOrganizationsState = ({}) => {
|
||||
return (
|
||||
<div className="col-span-4 space-y-4 rounded-lg border border-dashed border-muted p-6 text-center">
|
||||
<div className="space-y-1">
|
||||
<p>You are not part of any organizations yet</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Create your first organization to get started with Supabase
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Button asChild icon={<Plus />}>
|
||||
<Link href="/new">New organization</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<EmptyStatePresentational
|
||||
title="Create an organization"
|
||||
description="Manage your team and projects in one place."
|
||||
>
|
||||
<Button size="tiny" type="primary" asChild icon={<Plus />}>
|
||||
<Link href="/new">New organization</Link>
|
||||
</Button>
|
||||
</EmptyStatePresentational>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
|
||||
import { Skeleton } from 'ui'
|
||||
import { Admonition } from 'ui-patterns/admonition'
|
||||
@@ -16,20 +15,26 @@ export const OrgNotFound = ({ slug }: { slug?: string }) => {
|
||||
return (
|
||||
<>
|
||||
{slug !== '_' && (
|
||||
<Admonition type="danger">
|
||||
The selected organization does not exist or you don't have permission to access it.{' '}
|
||||
{slug ? (
|
||||
<Admonition
|
||||
type="destructive"
|
||||
title="Organization not found"
|
||||
description={
|
||||
<>
|
||||
Contact the owner or administrator to create a new project in the <code>{slug}</code>{' '}
|
||||
organization.
|
||||
{slug ? (
|
||||
<>
|
||||
The organization <code className="text-code-inline">{slug}</code>{' '}
|
||||
</>
|
||||
) : (
|
||||
<>This organization </>
|
||||
)}
|
||||
does not exist or you do not have permission to access to it. Contact the the owner if
|
||||
you believe this is a mistake.
|
||||
</>
|
||||
) : (
|
||||
<>Contact the owner or administrator to create a new project.</>
|
||||
)}
|
||||
</Admonition>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<h3 className="text-sm">Select an organization to create your new project in</h3>
|
||||
<h3 className="text-sm">Select a different organization to create your new project in</h3>
|
||||
|
||||
<div className="grid gap-2 grid-cols-2">
|
||||
{isOrganizationsLoading && (
|
||||
@@ -40,7 +45,11 @@ export const OrgNotFound = ({ slug }: { slug?: string }) => {
|
||||
</>
|
||||
)}
|
||||
{isOrganizationsError && (
|
||||
<AlertError error={organizationsError} subject="Failed to load organizations" />
|
||||
<Admonition
|
||||
type="destructive"
|
||||
title="Failed to load organizations"
|
||||
description={organizationsError?.message}
|
||||
/>
|
||||
)}
|
||||
{isOrganizationsSuccess &&
|
||||
organizations?.map((org) => (
|
||||
|
||||
@@ -17,7 +17,7 @@ export function LastSignInWrapper({
|
||||
{lastSignIn === type && (
|
||||
<Badge
|
||||
variant="success"
|
||||
className="absolute -right-4 -top-3 rounded-full px-2 py-0.5 shadow z-10 bg-brand-400 bg-opacity-100 text-foreground pointer-events-none"
|
||||
className="absolute -right-4 -top-3 shadow z-10 bg-brand-400 bg-opacity-100 text-foreground pointer-events-none"
|
||||
>
|
||||
Last used
|
||||
</Badge>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import { NoOrganizationsState } from 'components/interfaces/Home/ProjectList/EmptyStates'
|
||||
import { OrganizationCard } from 'components/interfaces/Organization/OrganizationCard'
|
||||
import AppLayout from 'components/layouts/AppLayout/AppLayout'
|
||||
import DefaultLayout from 'components/layouts/DefaultLayout'
|
||||
@@ -15,14 +16,8 @@ import { useOrganizationsQuery } from 'data/organizations/organizations-query'
|
||||
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
|
||||
import { withAuth } from 'hooks/misc/withAuth'
|
||||
import type { NextPageWithLayout } from 'types'
|
||||
import {
|
||||
AlertDescription_Shadcn_,
|
||||
AlertTitle_Shadcn_,
|
||||
Alert_Shadcn_,
|
||||
Button,
|
||||
CriticalIcon,
|
||||
Skeleton,
|
||||
} from 'ui'
|
||||
import { Button, Skeleton } from 'ui'
|
||||
import { Admonition } from 'ui-patterns/admonition'
|
||||
import { Input } from 'ui-patterns/DataInputs/Input'
|
||||
|
||||
const OrganizationsPage: NextPageWithLayout = () => {
|
||||
@@ -53,25 +48,21 @@ const OrganizationsPage: NextPageWithLayout = () => {
|
||||
<ScaffoldContainer>
|
||||
<ScaffoldSection isFullWidth className="flex flex-col gap-y-4">
|
||||
{orgNotFound && (
|
||||
<Alert_Shadcn_ variant="destructive">
|
||||
<CriticalIcon />
|
||||
<AlertTitle_Shadcn_>Organization not found</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
That organization (<code>{orgSlug}</code>) does not exist or you don't have access to
|
||||
it.
|
||||
</AlertDescription_Shadcn_>
|
||||
<AlertDescription_Shadcn_ className="mt-3">
|
||||
If you think this is an error, please reach out to the org owner to get access.
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
<Admonition
|
||||
type="destructive"
|
||||
title="Organization not found"
|
||||
description={
|
||||
<>
|
||||
The organization <code className="text-code-inline">{orgSlug}</code> does not exist
|
||||
or you do not have permission to access to it. Contact the the owner if you believe
|
||||
this is a mistake.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{organizations.length === 0 && orgNotFound && (
|
||||
<p className="-mt-4">You don't have any organizations yet. Create one to get started.</p>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between gap-x-2 md:gap-x-3">
|
||||
{organizations.length > 0 && (
|
||||
{organizations.length > 0 && (
|
||||
<div className="flex items-center justify-between gap-x-2 md:gap-x-3">
|
||||
<Input
|
||||
size="tiny"
|
||||
placeholder="Search for an organization"
|
||||
@@ -80,14 +71,16 @@ const OrganizationsPage: NextPageWithLayout = () => {
|
||||
value={search}
|
||||
onChange={(event) => setSearch(event.target.value)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{organizationCreationEnabled && (
|
||||
<Button asChild icon={<Plus />} type="primary" className="w-min">
|
||||
<Link href={`/new`}>New organization</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{organizationCreationEnabled && (
|
||||
<Button asChild icon={<Plus />} type="primary" className="w-min">
|
||||
<Link href={`/new`}>New organization</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isSuccess && organizations.length === 0 && !isError && <NoOrganizationsState />}
|
||||
|
||||
{search.length > 0 && filteredOrganizations.length === 0 && (
|
||||
<NoSearchResults searchString={search} />
|
||||
|
||||
@@ -54,6 +54,16 @@ export const Index: Record<string, any> = [
|
||||
svg: "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"1\">\n <path\n d=\"M5.24121 15.0674H12.7412M5.24121 15.0674V18.0674H12.7412V15.0674M5.24121 15.0674V12.0674H12.7412V15.0674M15 7.60547V4.60547C15 2.94861 13.6569 1.60547 12 1.60547C10.3431 1.60547 9 2.94861 9 4.60547V7.60547M5.20898 9.60547L5.20898 19.1055C5.20898 20.21 6.10441 21.1055 7.20898 21.1055H16.709C17.8136 21.1055 18.709 20.21 18.709 19.1055V9.60547C18.709 8.5009 17.8136 7.60547 16.709 7.60547L7.20899 7.60547C6.10442 7.60547 5.20898 8.5009 5.20898 9.60547Z\" />\n</svg>",
|
||||
jsx: "import { Auth } from \"icons\"\n <Auth/>\n "
|
||||
},
|
||||
{
|
||||
name: "box-plus",
|
||||
componentName: "BoxPlus",
|
||||
deprecated: false,
|
||||
raw: "import createSupabaseIcon from '../createSupabaseIcon';\n\n/**\n * @component @name BoxPlus\n * @description Supabase SVG icon component, renders SVG Element with children.\n *\n * @preview \n *\n * @param {Object} props - Supabase icons props and any valid SVG attribute\n * @returns {JSX.Element} JSX Element\n *\n */\nconst BoxPlus = createSupabaseIcon('BoxPlus', [\n [\n 'path',\n {\n d: 'M20.7315 7.00119C20.556 6.69754 20.3037 6.44539 20 6.27002L13 2.27002C12.696 2.09449 12.3511 2.00208 12 2.00208C11.6489 2.00208 11.304 2.09449 11 2.27002L4 6.27002C3.69626 6.44539 3.44398 6.69754 3.26846 7.00119C3.09294 7.30483 3.00036 7.6493 3 8.00002V16C3.00036 16.3508 3.09294 16.6952 3.26846 16.9989C3.44398 17.3025 3.69626 17.5547 4 17.73L11 21.73C11.304 21.9056 11.6489 21.998 12 21.998',\n stroke: 'currentColor',\n 'stroke-width': '1.5',\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n key: '1g22fp',\n },\n ],\n [\n 'path',\n {\n d: 'M3.3 7L12 12L20.7 7',\n stroke: 'currentColor',\n 'stroke-width': '1.5',\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n key: '1v4gtz',\n },\n ],\n [\n 'path',\n {\n d: 'M12 22V12',\n stroke: 'currentColor',\n 'stroke-width': '1.5',\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n key: 'hoijqc',\n },\n ],\n [\n 'path',\n {\n d: 'M19 14V20',\n stroke: 'currentColor',\n 'stroke-width': '1.5',\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n key: '7ts4zv',\n },\n ],\n [\n 'path',\n {\n d: 'M16 17H22',\n stroke: 'currentColor',\n 'stroke-width': '1.5',\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n key: 'ir88vv',\n },\n ],\n]);\n\nexport default BoxPlus;\n",
|
||||
component: React.lazy(() => import('icons/src/icons/box-plus')),
|
||||
import: "import { BoxPlus } from 'icons'",
|
||||
svg: "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M20.7315 7.00119C20.556 6.69754 20.3037 6.44539 20 6.27002L13 2.27002C12.696 2.09449 12.3511 2.00208 12 2.00208C11.6489 2.00208 11.304 2.09449 11 2.27002L4 6.27002C3.69626 6.44539 3.44398 6.69754 3.26846 7.00119C3.09294 7.30483 3.00036 7.6493 3 8.00002V16C3.00036 16.3508 3.09294 16.6952 3.26846 16.9989C3.44398 17.3025 3.69626 17.5547 4 17.73L11 21.73C11.304 21.9056 11.6489 21.998 12 21.998\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M3.3 7L12 12L20.7 7\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M12 22V12\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M19 14V20\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M16 17H22\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
jsx: "import { BoxPlus } from \"icons\"\n <BoxPlus/>\n "
|
||||
},
|
||||
{
|
||||
name: "bucket-plus",
|
||||
componentName: "BucketPlus",
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import createSupabaseIcon from '../createSupabaseIcon';
|
||||
|
||||
/**
|
||||
* @component @name BoxPlus
|
||||
* @description Supabase SVG icon component, renders SVG Element with children.
|
||||
*
|
||||
* @preview 
|
||||
*
|
||||
* @param {Object} props - Supabase icons props and any valid SVG attribute
|
||||
* @returns {JSX.Element} JSX Element
|
||||
*
|
||||
*/
|
||||
const BoxPlus = createSupabaseIcon('BoxPlus', [
|
||||
[
|
||||
'path',
|
||||
{
|
||||
d: 'M20.7315 7.00119C20.556 6.69754 20.3037 6.44539 20 6.27002L13 2.27002C12.696 2.09449 12.3511 2.00208 12 2.00208C11.6489 2.00208 11.304 2.09449 11 2.27002L4 6.27002C3.69626 6.44539 3.44398 6.69754 3.26846 7.00119C3.09294 7.30483 3.00036 7.6493 3 8.00002V16C3.00036 16.3508 3.09294 16.6952 3.26846 16.9989C3.44398 17.3025 3.69626 17.5547 4 17.73L11 21.73C11.304 21.9056 11.6489 21.998 12 21.998',
|
||||
stroke: 'currentColor',
|
||||
'stroke-width': '1.5',
|
||||
'stroke-linecap': 'round',
|
||||
'stroke-linejoin': 'round',
|
||||
key: '1g22fp',
|
||||
},
|
||||
],
|
||||
[
|
||||
'path',
|
||||
{
|
||||
d: 'M3.3 7L12 12L20.7 7',
|
||||
stroke: 'currentColor',
|
||||
'stroke-width': '1.5',
|
||||
'stroke-linecap': 'round',
|
||||
'stroke-linejoin': 'round',
|
||||
key: '1v4gtz',
|
||||
},
|
||||
],
|
||||
[
|
||||
'path',
|
||||
{
|
||||
d: 'M12 22V12',
|
||||
stroke: 'currentColor',
|
||||
'stroke-width': '1.5',
|
||||
'stroke-linecap': 'round',
|
||||
'stroke-linejoin': 'round',
|
||||
key: 'hoijqc',
|
||||
},
|
||||
],
|
||||
[
|
||||
'path',
|
||||
{
|
||||
d: 'M19 14V20',
|
||||
stroke: 'currentColor',
|
||||
'stroke-width': '1.5',
|
||||
'stroke-linecap': 'round',
|
||||
'stroke-linejoin': 'round',
|
||||
key: '7ts4zv',
|
||||
},
|
||||
],
|
||||
[
|
||||
'path',
|
||||
{
|
||||
d: 'M16 17H22',
|
||||
stroke: 'currentColor',
|
||||
'stroke-width': '1.5',
|
||||
'stroke-linecap': 'round',
|
||||
'stroke-linejoin': 'round',
|
||||
key: 'ir88vv',
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
export default BoxPlus;
|
||||
@@ -3,6 +3,7 @@ export { default as ExampleTemplate } from './_example-template';
|
||||
export { default as AnalyticsBucket } from './analytics-bucket';
|
||||
export { default as ApiDocs } from './api-docs';
|
||||
export { default as Auth } from './auth';
|
||||
export { default as BoxPlus } from './box-plus';
|
||||
export { default as BucketPlus } from './bucket-plus';
|
||||
export { default as Database } from './database';
|
||||
export { default as Datadog } from './datadog';
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20.7315 7.00119C20.556 6.69754 20.3037 6.44539 20 6.27002L13 2.27002C12.696 2.09449 12.3511 2.00208 12 2.00208C11.6489 2.00208 11.304 2.09449 11 2.27002L4 6.27002C3.69626 6.44539 3.44398 6.69754 3.26846 7.00119C3.09294 7.30483 3.00036 7.6493 3 8.00002V16C3.00036 16.3508 3.09294 16.6952 3.26846 16.9989C3.44398 17.3025 3.69626 17.5547 4 17.73L11 21.73C11.304 21.9056 11.6489 21.998 12 21.998" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3.3 7L12 12L20.7 7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 22V12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M19 14V20" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M16 17H22" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
Reference in New Issue
Block a user