feat: custom bucket icons (#39280)

* chore: separate interfaces per bucket type

* chore: reuse existing bucket creation modal for files

* chore: allow external trigger for new bucket modal

* chore: minor fixes

* chore: full-size empty state

* chore: files other tabs

* fix: show tab bar when on file subroute

* chore: use existing table component to list buckets

* fix: tests

* fix: build error

* chore: remove future bucket type components from PR

* fix: dependency

* Couple of refactors to simplify implementation

* Fix TS

* chore: move s3 settings out to sidebar

* fix: minor nits

* fix: reuse existing loading spinners from schema visualiser

* fix: loading for files

* fix: prevent loading of table for zero buckets

* chore: improved loading state

* chore: shimmer on table

* chore: copywriting on S3

* chore: improve empty states

* fix: deprecated input instructions

* fix: filter/search bucket table

* chore: tidy

* fix: tests

* first commit

* improve documentation and fix icon build error

* change svg kebab case to camel case for React

* copy nits

* fix svg codeblock color

* add bucket (normal) icon

* add bucket icon to policies

* add bucket icon to other storage bucket surfaces

* fix spacing on empty state

* fix inconsistent icons in home sections

Make 1:1 with what we have in the sidebar with optically-correct strokeWidth.

* fix icons

Like the previous commit, this makes sure we use the same product icons as in the sidebar.

* clearer bucket add icon naming

* remove unused layouts

* fix naming collision

* Tinnnnyyy nit

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
This commit is contained in:
Danny White
2025-10-08 12:14:03 +10:00
committed by GitHub
parent f0436716f9
commit 5ff03b2971
17 changed files with 330 additions and 148 deletions

View File

@@ -2,7 +2,7 @@
Design resources for building consistent user experiences at Supabase.
## Getting Started
## Getting started
First, make a copy of _.env.local.example_ and name it _env.local_. Then install any required packages and start the development server:

View File

@@ -11,14 +11,108 @@ description: Icons make actions and navigation across Supabase easier.
## Tints
Destructive actions, such as deleting an API key, dont need to be [tinted](color-usage#text) with `text-destructive` because there should be a confirmation dialog as a failsafe right after.
Use classes just like you would for [text](color-usage#text) to tint icons. For example:
## UI Icons
```jsx
<BucketAdd className="text-foreground-muted" />
```
We rely on Lucide icons for most of our UI icons.
Just like text, dont tint icons with `text-destructive` for destructive actions. There should be a confirmation dialog right after which can handle the destructive styling.
## Custom Icons
## UI icons
Tap on an icon below to copy the JSX, SVG, or import path.
We rely on [Lucide](https://lucide.dev/icons/) for any standard UI icon needs.
## Custom icons
Create and use custom icons when Lucide doesnt have the icon you need. Tap on an icon below to copy the JSX, SVG, or import path.
<Icons />
### Usage
```jsx
import { ReplaceCode, InsertCode, BucketAdd } from 'icons'
function app() {
return (
<>
<ReplaceCode className="text-light" strokeWidth={1} size={16} />
<InsertCode className="text-light" strokeWidth={1} size={16} />
<BucketAdd size={24} className="text-foreground-muted" />
</>
)
}
```
**Default props**: All icons have `strokeWidth={2}` and `size={24}` by default. Override these props as needed for your use case.
### Adding new custom icons
To add a new custom icon to the Supabase icon library:
1. **Create SVG file**: Add your SVG file to `packages/icons/src/raw-icons/` with a kebab-case name (e.g., `my-new-icon.svg`). Make sure it has follows these exact requirements:
- Exported at 24x24px with `viewBox="0 0 24 24"`
- Uses `stroke="currentColor"` for strokes (no hardcoded colors)
- 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
- SVG structure is as simple as possible with just `<path>` elements
Just leave attributes like `stroke-width` as they are. The conversion to camel-case (for React compatibility) is handled by the below build process.
2. **Build the component**: Run `npm run build:icons` from inside the `packages/icons` directory
3. **Use the icon**: Import and use like any other icon:
```jsx
import { MyNewIcon } from 'icons'
;<MyNewIcon size={16} strokeWidth={1} />
```
### SVG design guidelines
Icons should:
- Always be exported 24x24px
- Have an icon inside that frame thats around 18x18px(ish)
- Use clean, simple paths without unnecessary wrapper elements
#### Bad example ❌
Notice the hardcoded colors, unnecessary backgrounds, and complex structure:
```svg
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="24" height="24" fill="#1E1E1E" /> <!-- ❌ Hardcoded color -->
<path d="M..." fill="#404040" /> <!-- ❌ Hardcoded color -->
<path d="M..." stroke="#EDEDED" stroke-linecap="round" /> <!-- ❌ Hardcoded color -->
</svg>
```
#### Good example ✅
Clean structure with `currentColor` and proper attributes:
```svg
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M6 7C6 4.2 8.2 2 11 2H13C15.8 2 18 4.2 18 7" />
<path d="M4.5 11H19.5" />
<path d="M6 11L6.8 20C6.9 21.1 7.9 22 9 22H12" />
</svg>
```
{/* This is still wrong: */}
```svg
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M6 7C6 4.2 8.2 2 11 2H13C15.8 2 18 4.2 18 7" />
<path d="M4.5 11H19.5" />
<path d="M6 11L6.8 20C6.9 21.1 7.9 22 9 22H12" />
</svg>
```
### Troubleshooting
If your SVG specifies `stroke-width` attributes, they will override the component's `strokeWidth` prop. Remove stroke attributes from individual paths to let the component control them.

View File

@@ -15,7 +15,7 @@
}
[data-rehype-pretty-code-fragment] {
@apply relative text-white;
@apply relative;
}
[data-rehype-pretty-code-fragment] code {

View File

@@ -1,6 +1,7 @@
import dayjs from 'dayjs'
import sumBy from 'lodash/sumBy'
import { Archive, ChevronDown, Database, Key, Zap } from 'lucide-react'
import { ChevronDown } from 'lucide-react'
import { Auth, Database, Realtime, Storage } from 'icons'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useState } from 'react'
@@ -200,7 +201,7 @@ const ProjectUsage = () => {
<PanelHeader
icon={
<div className="rounded bg-surface-300 p-1.5 text-foreground-light shadow-sm">
<Database strokeWidth={2} size={16} />
<Database strokeWidth={1.5} size={16} />
</div>
}
title="Database"
@@ -226,7 +227,7 @@ const ProjectUsage = () => {
<PanelHeader
icon={
<div className="rounded bg-surface-300 p-1.5 text-foreground-light shadow-sm">
<Key strokeWidth={2} size={16} />
<Auth strokeWidth={1.5} size={16} />
</div>
}
title="Auth"
@@ -252,7 +253,7 @@ const ProjectUsage = () => {
<PanelHeader
icon={
<div className="rounded bg-surface-300 p-1.5 text-foreground-light shadow-sm">
<Archive strokeWidth={2} size={16} />
<Storage strokeWidth={1.5} size={16} />
</div>
}
title="Storage"
@@ -278,7 +279,7 @@ const ProjectUsage = () => {
<PanelHeader
icon={
<div className="rounded bg-surface-300 p-1.5 text-foreground-light shadow-sm">
<Zap strokeWidth={2} size={16} />
<Realtime strokeWidth={1.5} size={16} />
</div>
}
title="Realtime"

View File

@@ -1,5 +1,5 @@
import dayjs from 'dayjs'
import { Archive, ChevronDown, Code, Database, Key, Zap } from 'lucide-react'
import { ChevronDown } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useMemo, useState } from 'react'
@@ -77,7 +77,6 @@ type ServiceKey = 'db' | 'functions' | 'auth' | 'storage' | 'realtime'
type ServiceEntry = {
key: ServiceKey
title: string
icon: React.ReactNode
href?: string
route: string
enabled: boolean
@@ -159,7 +158,7 @@ export const ProjectUsageSection = () => {
{
key: 'db',
title: 'Database requests',
icon: <Database strokeWidth={1.5} size={16} className="text-foreground-lighter" />,
href: `/project/${projectRef}/editor`,
route: '/logs/postgres-logs',
enabled: true,
@@ -167,14 +166,12 @@ export const ProjectUsageSection = () => {
{
key: 'functions',
title: 'Functions requests',
icon: <Code strokeWidth={1.5} size={16} className="text-foreground-lighter" />,
route: '/logs/edge-functions-logs',
enabled: true,
},
{
key: 'auth',
title: 'Auth requests',
icon: <Key strokeWidth={1.5} size={16} className="text-foreground-lighter" />,
href: `/project/${projectRef}/auth/users`,
route: '/logs/auth-logs',
enabled: authEnabled,
@@ -182,7 +179,6 @@ export const ProjectUsageSection = () => {
{
key: 'storage',
title: 'Storage requests',
icon: <Archive strokeWidth={1.5} size={16} className="text-foreground-lighter" />,
href: `/project/${projectRef}/storage/buckets`,
route: '/logs/storage-logs',
enabled: storageEnabled,
@@ -190,7 +186,6 @@ export const ProjectUsageSection = () => {
{
key: 'realtime',
title: 'Realtime requests',
icon: <Zap strokeWidth={1.5} size={16} className="text-foreground-lighter" />,
route: '/logs/realtime-logs',
enabled: true,
},

View File

@@ -1,3 +1,4 @@
import { BucketAdd } from 'icons'
import { CreateBucketModal } from './CreateBucketModal'
import { BUCKET_TYPES } from './Storage.constants'
@@ -9,10 +10,13 @@ export const EmptyBucketState = ({ bucketType }: EmptyBucketStateProps) => {
const config = BUCKET_TYPES[bucketType]
return (
<aside className="mt-12 border border-dashed w-full bg-surface-100 rounded-lg px-4 py-10 flex flex-col gap-6 items-center text-center gap-1 text-balance">
<div className="flex flex-col gap-1">
<h3>Create {config.label}</h3>
<p className="text-foreground-light text-sm">{config.valueProp}</p>
<aside className="mt-12 border border-dashed w-full bg-surface-100 rounded-lg px-4 py-10 flex flex-col gap-y-4 items-center text-center gap-1 text-balance">
<div className="flex flex-col gap-3 items-center text-center">
<BucketAdd size={24} strokeWidth={1.5} className="text-foreground-muted" />
<div className="flex flex-col gap-1">
<h3>Create {config.label}</h3>
<p className="text-foreground-light text-sm">{config.valueProp}</p>
</div>
</div>
{/* [Joshen] We can render the individual bucket modals here instead - where each modal has its own trigger */}

View File

@@ -1,9 +1,10 @@
import { PostgresPolicy } from '@supabase/postgres-meta'
import { noop } from 'lodash'
import { Archive } from 'lucide-react'
import { PolicyRow } from 'components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow'
import { Bucket } from 'data/storage/buckets-query'
import { PolicyRow } from 'components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow'
import { Bucket as BucketIcon } from 'icons'
import {
Badge,
Button,
@@ -41,7 +42,7 @@ export const StoragePoliciesBucketRow = ({
<Card>
<CardHeader className="flex flex-row w-full items-center justify-between gap-0 space-y-0">
<div className="flex items-center gap-3">
<Archive className="text-foreground-light" size={16} strokeWidth={1.5} />
<BucketIcon className="text-foreground-muted" size={16} strokeWidth={1.5} />
<CardTitle>{label}</CardTitle>
{bucket?.public && <Badge variant="warning">Public</Badge>}
</div>

View File

@@ -52,18 +52,19 @@ export const Index: Record<string, any> = [`
let { children } = iconNodes[iconName]
children = children.map(({ name, attributes }) => [name, attributes])
const getSvg = () => readSvg(`${iconName}.svg`, iconsDir)
const svgContent = readSvg(`${iconName}.svg`, iconsDir)
const getSvg = () => svgContent
// const { deprecated = false } = iconMetaData[iconName]
const deprecated = false
const elementTemplate = template({ componentName, iconName, children, getSvg, deprecated })
const output = pretty
? prettier.format(elementTemplate, {
singleQuote: true,
trailingComma: 'all',
printWidth: 100,
parser: 'babel',
})
? await prettier.format(elementTemplate, {
singleQuote: true,
trailingComma: 'all',
printWidth: 100,
parser: 'babel',
})
: elementTemplate
const rawSvg = JSON.stringify(readSvg(`${iconName}.svg`, iconsDir))
@@ -111,7 +112,7 @@ export const Index: Record<string, any> = [`
}
// TO DO -- END
Promise.all([writeIconFiles])
Promise.all(writeIconFiles)
// TO DO -- START
//

View File

@@ -1,122 +1,31 @@
# ./packages/icons
This package is for custom Supabase icons
They can be used alongside any other icon packages
This package contains custom Supabase icons that can be used alongside other icon libraries.
## example use
## Documentation
**For complete documentation, usage examples, and guidelines, see the [Design System](../../apps/design-system/content/docs/icons.mdx)**
## Quick start
```jsx
import { ReplaceCode, InsertCode } from 'icons'
import { BucketAdd, Database, Auth } from 'icons'
function app() {
function MyComponent() {
return (
<>
<ReplaceCode className="text-light" strokeWidth={1} size={16} />
<InsertCode className="text-light" strokeWidth={1} size={16} />
<BucketAdd size={24} className="text-foreground-muted" />
<Database size={16} strokeWidth={1} />
<Auth size={20} />
</>
)
}
```
## adding new icons
### Adding new custom icons
Add new icons into ./src/raw-icons
1. Add your SVG file to `src/raw-icons/` (kebab-case name)
2. Run `npm run build:icons` in this directory
3. Import and use your new icon
Make sure there are no inline stroke/border/fill colors (see below)
run this in ./packages/build-icons
```bash
npm run build
```
This will output icons into ./src/icons and update import names/paths
### Design spec
Icons should:
- always be exported 24x24px,
- and have an icon inside that frame that's around 18x18px(ish)
### ❌ bad example
Notice the stroke, stroke-linecap, fills, etc.
These need to be in the parent <svg> so the react component can easily control it.
The SVG child elements will then respect their parent's attributes.
```svg
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect <-- silly backgrounds figma adds in
width="24"
height="24"
fill="#1E1E1E"
/>
<path <-- silly backgrounds figma adds in
d="M-20439
-11141C-20439..."
fill="#404040"
/>
<path
d="M-20437 -11142H12131V-11144H-20437V-11142ZM12132 ...."
fill="white"
fill-opacity="0.1"
/>
<path
d="M22.8437 8.69499L19.5369 12.0018L22.8438 15.3086..."
stroke="#EDEDED"
stroke-linecap="round"
stroke-linejoin="round"
/>
<rect
x="0.5"
y="14.0625"
width="16"
height="8"
rx="1"
stroke="#EDEDED"
stroke-linejoin="round"
/>
<rect
x="0.5"
y="1.9375"
width="16"
height="8"
rx="1"
stroke="#EDEDED"
stroke-linejoin="round"
/>
</svg>
```
✅ Good example
We've now cleaned it up, and the parent SVG element now has all the attributes for color and stroke width styling.
We have also removed the redundant elements that figma adds in like background / artboard backgrounds.
```svg
<svg
width="24"
height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M-20439 -11141C-20439..." />
<path d="M-20437 -11142H12131V-11144H-20437V-11142ZM12132 ...."/>
<path d="M22.8437 8.69499L19.5369 12.0018L22.8438 15.3086..." />
<rect x="0.5" y="14.0625" width="16" height="8" rx="1" />
<rect x="0.5" y="1.9375" width="16" height="8" rx="1" />
</svg>
```
For detailed instructions, examples, and troubleshooting, see the [Design System](../../apps/design-system/content/docs/icons.mdx).

View File

@@ -44,6 +44,26 @@ 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: "bucket-add",
componentName: "BucketAdd",
deprecated: false,
raw: "import createSupabaseIcon from '../createSupabaseIcon';\n\n/**\n * @component @name BucketAdd\n * @description Supabase SVG icon component, renders SVG Element with children.\n *\n * @preview ![img](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNiA3QzYgNC4yIDguMiAyIDExIDJIMTNDMTUuOCAyIDE4IDQuMiAxOCA3IiBzdHJva2U9IiMwMDAiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiAjZmZmOyBib3JkZXItcmFkaXVzOiAycHgiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTQuNSAxMUgxOS41IiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNNiAxMUw2LjggMjBDNi45IDIxLjEgNy45IDIyIDkgMjJIMTIiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0xOCAxNS4wMjQ5VjIyLjAyNDkiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0xNC41IDE4LjUyNDlIMjEuNSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9zdmc+Cg==)\n *\n * @param {Object} props - Supabase icons props and any valid SVG attribute\n * @returns {JSX.Element} JSX Element\n *\n */\nconst BucketAdd = createSupabaseIcon('BucketAdd', [\n [\n 'path',\n {\n d: 'M6 7C6 4.2 8.2 2 11 2H13C15.8 2 18 4.2 18 7',\n stroke: 'currentColor',\n 'stroke-width': '1.5',\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n key: '9nyc2k',\n },\n ],\n [\n 'path',\n {\n d: 'M4.5 11H19.5',\n stroke: 'currentColor',\n 'stroke-width': '1.5',\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n key: 'fq7r7q',\n },\n ],\n [\n 'path',\n {\n d: 'M6 11L6.8 20C6.9 21.1 7.9 22 9 22H12',\n stroke: 'currentColor',\n 'stroke-width': '1.5',\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n key: 'f9mz1i',\n },\n ],\n [\n 'path',\n {\n d: 'M18 15.0249V22.0249',\n stroke: 'currentColor',\n 'stroke-width': '1.5',\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n key: '7kgnjd',\n },\n ],\n [\n 'path',\n {\n d: 'M14.5 18.5249H21.5',\n stroke: 'currentColor',\n 'stroke-width': '1.5',\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n key: '4i9ntd',\n },\n ],\n]);\n\nexport default BucketAdd;\n",
component: React.lazy(() => import('icons/src/icons/bucket-add')),
import: "import { BucketAdd } 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=\"M6 7C6 4.2 8.2 2 11 2H13C15.8 2 18 4.2 18 7\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M4.5 11H19.5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M6 11L6.8 20C6.9 21.1 7.9 22 9 22H12\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M18 15.0249V22.0249\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M14.5 18.5249H21.5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
jsx: "import { BucketAdd } from \"icons\"\n <BucketAdd/>\n "
},
{
name: "bucket",
componentName: "Bucket",
deprecated: false,
raw: "import createSupabaseIcon from '../createSupabaseIcon';\n\n/**\n * @component @name Bucket\n * @description Supabase SVG icon component, renders SVG Element with children.\n *\n * @preview ![img](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNiA3QzYgNC4yIDguMiAyIDExIDJIMTNDMTUuOCAyIDE4IDQuMiAxOCA3IiBzdHJva2U9IiMwMDAiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiAjZmZmOyBib3JkZXItcmFkaXVzOiAycHgiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTQuNSAxMUgxOS41IiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMTggMTFMMTcuMiAyMEMxNy4xIDIxLjEgMTYuMSAyMiAxNSAyMkg5QzcuOSAyMiA2LjkgMjEuMSA2LjggMjBMNiAxMSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9zdmc+Cg==)\n *\n * @param {Object} props - Supabase icons props and any valid SVG attribute\n * @returns {JSX.Element} JSX Element\n *\n */\nconst Bucket = createSupabaseIcon('Bucket', [\n [\n 'path',\n {\n d: 'M6 7C6 4.2 8.2 2 11 2H13C15.8 2 18 4.2 18 7',\n stroke: 'currentColor',\n 'stroke-width': '1.5',\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n key: '9nyc2k',\n },\n ],\n [\n 'path',\n {\n d: 'M4.5 11H19.5',\n stroke: 'currentColor',\n 'stroke-width': '1.5',\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n key: 'fq7r7q',\n },\n ],\n [\n 'path',\n {\n d: 'M18 11L17.2 20C17.1 21.1 16.1 22 15 22H9C7.9 22 6.9 21.1 6.8 20L6 11',\n stroke: 'currentColor',\n 'stroke-width': '1.5',\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n key: 'nfdgw2',\n },\n ],\n]);\n\nexport default Bucket;\n",
component: React.lazy(() => import('icons/src/icons/bucket')),
import: "import { Bucket } 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=\"M6 7C6 4.2 8.2 2 11 2H13C15.8 2 18 4.2 18 7\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M4.5 11H19.5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M18 11L17.2 20C17.1 21.1 16.1 22 15 22H9C7.9 22 6.9 21.1 6.8 20L6 11\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
jsx: "import { Bucket } from \"icons\"\n <Bucket/>\n "
},
{
name: "database",
componentName: "Database",

View File

@@ -33,6 +33,33 @@ export const toKebabCase = (string: string) =>
.toLowerCase()
.trim()
/**
* Converts kebab-case string to camelCase
* @param {string} string
* @returns {string} A camelCased string
*/
export const toCamelCase = (string: string) =>
string.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()).trim()
/**
* Converts kebab-case attributes to camelCase for React compatibility
* @param {Record<string, string>} attrs
* @returns {Record<string, string>} Attributes with camelCase keys
*/
export const convertAttributesToCamelCase = (
attrs: Record<string, string>
): Record<string, string> => {
const converted: Record<string, string> = {}
for (const [key, value] of Object.entries(attrs)) {
// Convert kebab-case to camelCase, but keep some special cases
const camelKey = key.includes('-') ? toCamelCase(key) : key
converted[camelKey] = value
}
return converted
}
const createLucideIcon = (iconName: string, iconNode: IconNode): LucideIcon => {
const Component = forwardRef<SVGSVGElement, LucideProps>(
(
@@ -62,7 +89,9 @@ const createLucideIcon = (iconName: string, iconNode: IconNode): LucideIcon => {
...rest,
},
[
...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),
...iconNode.map(([tag, attrs]) =>
createElement(tag, convertAttributesToCamelCase(attrs))
),
...(Array.isArray(children) ? children : [children]),
]
)

View File

@@ -0,0 +1,71 @@
import createSupabaseIcon from '../createSupabaseIcon';
/**
* @component @name BucketAdd
* @description Supabase SVG icon component, renders SVG Element with children.
*
* @preview ![img](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNiA3QzYgNC4yIDguMiAyIDExIDJIMTNDMTUuOCAyIDE4IDQuMiAxOCA3IiBzdHJva2U9IiMwMDAiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiAjZmZmOyBib3JkZXItcmFkaXVzOiAycHgiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTQuNSAxMUgxOS41IiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNNiAxMUw2LjggMjBDNi45IDIxLjEgNy45IDIyIDkgMjJIMTIiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0xOCAxNS4wMjQ5VjIyLjAyNDkiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0xNC41IDE4LjUyNDlIMjEuNSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9zdmc+Cg==)
*
* @param {Object} props - Supabase icons props and any valid SVG attribute
* @returns {JSX.Element} JSX Element
*
*/
const BucketAdd = createSupabaseIcon('BucketAdd', [
[
'path',
{
d: 'M6 7C6 4.2 8.2 2 11 2H13C15.8 2 18 4.2 18 7',
stroke: 'currentColor',
'stroke-width': '1.5',
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
key: '9nyc2k',
},
],
[
'path',
{
d: 'M4.5 11H19.5',
stroke: 'currentColor',
'stroke-width': '1.5',
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
key: 'fq7r7q',
},
],
[
'path',
{
d: 'M6 11L6.8 20C6.9 21.1 7.9 22 9 22H12',
stroke: 'currentColor',
'stroke-width': '1.5',
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
key: 'f9mz1i',
},
],
[
'path',
{
d: 'M18 15.0249V22.0249',
stroke: 'currentColor',
'stroke-width': '1.5',
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
key: '7kgnjd',
},
],
[
'path',
{
d: 'M14.5 18.5249H21.5',
stroke: 'currentColor',
'stroke-width': '1.5',
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
key: '4i9ntd',
},
],
]);
export default BucketAdd;

View File

@@ -0,0 +1,49 @@
import createSupabaseIcon from '../createSupabaseIcon';
/**
* @component @name Bucket
* @description Supabase SVG icon component, renders SVG Element with children.
*
* @preview ![img](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNiA3QzYgNC4yIDguMiAyIDExIDJIMTNDMTUuOCAyIDE4IDQuMiAxOCA3IiBzdHJva2U9IiMwMDAiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiAjZmZmOyBib3JkZXItcmFkaXVzOiAycHgiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTQuNSAxMUgxOS41IiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMTggMTFMMTcuMiAyMEMxNy4xIDIxLjEgMTYuMSAyMiAxNSAyMkg5QzcuOSAyMiA2LjkgMjEuMSA2LjggMjBMNiAxMSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9zdmc+Cg==)
*
* @param {Object} props - Supabase icons props and any valid SVG attribute
* @returns {JSX.Element} JSX Element
*
*/
const Bucket = createSupabaseIcon('Bucket', [
[
'path',
{
d: 'M6 7C6 4.2 8.2 2 11 2H13C15.8 2 18 4.2 18 7',
stroke: 'currentColor',
'stroke-width': '1.5',
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
key: '9nyc2k',
},
],
[
'path',
{
d: 'M4.5 11H19.5',
stroke: 'currentColor',
'stroke-width': '1.5',
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
key: 'fq7r7q',
},
],
[
'path',
{
d: 'M18 11L17.2 20C17.1 21.1 16.1 22 15 22H9C7.9 22 6.9 21.1 6.8 20L6 11',
stroke: 'currentColor',
'stroke-width': '1.5',
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
key: 'nfdgw2',
},
],
]);
export default Bucket;

View File

@@ -2,6 +2,8 @@ export { default as RESTApi } from './REST-api';
export { default as ExampleTemplate } from './_example-template';
export { default as ApiDocs } from './api-docs';
export { default as Auth } from './auth';
export { default as BucketAdd } from './bucket-add';
export { default as Bucket } from './bucket';
export { default as Database } from './database';
export { default as Datadog } from './datadog';
export { default as EdgeFunctions } from './edge-functions';

View File

@@ -1,6 +0,0 @@
{
"$schema": "../icon.schema.json",
"contributors": ["it-is-not", "jguddas", "danielbayley", "ericfennis"],
"tags": ["letter", "font size", "text", "formatting", "smaller"],
"categories": ["text", "design"]
}

View File

@@ -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="M6 7C6 4.2 8.2 2 11 2H13C15.8 2 18 4.2 18 7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.5 11H19.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 11L6.8 20C6.9 21.1 7.9 22 9 22H12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18 15.0249V22.0249" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14.5 18.5249H21.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 736 B

View File

@@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 7C6 4.2 8.2 2 11 2H13C15.8 2 18 4.2 18 7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.5 11H19.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18 11L17.2 20C17.1 21.1 16.1 22 15 22H9C7.9 22 6.9 21.1 6.8 20L6 11" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 529 B