Merge pull request #4845 from penpot/ladybenko-8265-raw-svg-component

RawSVG component (design system) + generate SVG sprite for assets
This commit is contained in:
Eva Marco 2024-07-05 09:44:35 +02:00 committed by GitHub
commit 9ce3f6da45
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 916 additions and 19 deletions

View file

@ -7,11 +7,16 @@
(ns app.main.ui.ds
(:require
[app.main.ui.ds.foundations.icon :refer [icon* icon-list]]
[app.main.ui.ds.foundations.raw-svg :refer [raw-svg* raw-svg-list]]
[app.main.ui.ds.storybook :as sb]))
(def default
"A export used for storybook"
#js {:Icon icon*
:RawSvg raw-svg*
;; meta / misc
:meta #js {:icons icon-list}
:storybook #js {:StoryWrapper sb/story-wrapper* :IconGrid sb/icon-grid*}})
:meta #js {:icons icon-list :svgs raw-svg-list}
:storybook #js {:StoryGrid sb/story-grid*
:StoryGridCell sb/story-grid-cell*
:StoryHeader sb/story-header*
:StoryWrapper sb/story-wrapper*}})

View file

@ -253,7 +253,7 @@
{::mf/props :obj}
[{:keys [icon size class] :rest props}]
(let [class (dm/str (or class "") " " (stl/css :icon))
props (mf/spread props {:class class :width icon-size-m :height icon-size-m})
props (mf/spread-props props {:class class :width icon-size-m :height icon-size-m})
size-px (cond (= size "s") icon-size-s :else icon-size-m)
offset (/ (- icon-size-m size-px) 2)]
[:> "svg" props

View file

@ -2,7 +2,8 @@ import * as React from "react";
import Components from "@target/components";
const { Icon } = Components;
const { StoryWrapper, IconGrid } = Components.storybook;
const { StoryWrapper, StoryGrid, StoryGridCell, StoryHeader } =
Components.storybook;
const { icons } = Components.meta;
export default {
@ -17,23 +18,32 @@ const iconList = Object.entries(icons)
export const AllIcons = {
render: () => (
<StoryWrapper theme="default">
<h1>All Icons</h1>
<p>Hover on an icon to see its ID</p>
<IconGrid>
<StoryHeader>
<h1>All Icons</h1>
<p>Hover on an icon to see its ID</p>
</StoryHeader>
<StoryGrid>
{iconList.map((iconId) => (
<div title={iconId} key={iconId}>
<StoryGridCell
title={iconId}
key={iconId}
style={{ color: "var(--color-accent-primary)" }}
>
<Icon icon={iconId} />
</div>
</StoryGridCell>
))}
</IconGrid>
</StoryGrid>
</StoryWrapper>
),
parameters: {
backgrounds: { disable: true },
},
};
export const Default = {
render: () => (
<StoryWrapper theme="default">
<Icon icon="pin" />
<Icon icon={icons.Pin} />
</StoryWrapper>
),
};
@ -41,7 +51,7 @@ export const Default = {
export const Small = {
render: () => (
<StoryWrapper theme="default">
<Icon icon="pin" size="s" />
<Icon icon={icons.Pin} size="s" />
</StoryWrapper>
),
};

View file

@ -0,0 +1,21 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.ds.foundations.raw-svg
(:require
[clojure.core :as c]
[cuerdas.core :as str]
[rumext.v2]))
(defmacro collect-raw-svgs
[]
(let [ns-info (:ns &env)]
`(cljs.core/js-obj
~@(->> (:defs ns-info)
(map val)
(filter (fn [entry] (-> entry :meta :svg-id)))
(mapcat (fn [{:keys [name]}]
[(-> name c/name str/camel str/capital) name]))))))

View file

@ -0,0 +1,37 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.ds.foundations.raw-svg
(:refer-clojure :exclude [mask])
(:require-macros
[app.common.data.macros :as dm]
[app.main.ui.ds.foundations.raw-svg :refer [collect-raw-svgs]])
(:require
[rumext.v2 :as mf]))
(def ^:svg-id brand-openid "brand-openid")
(def ^:svg-id brand-github "brand-github")
(def ^:svg-id brand-gitlab "brand-gitlab")
(def ^:svg-id brand-google "brand-google")
(def ^:svg-id loader "loader")
(def ^:svg-id logo "penpot-logo")
(def ^:svg-id logo-icon "penpot-logo-icon")
(def ^:svg-id logo-error-screen "logo-error-screen")
(def ^:svg-id login-illustration "login-illustration")
(def ^:svg-id v2-icon-1 "v2-icon-1")
(def ^:svg-id v2-icon-2 "v2-icon-2")
(def ^:svg-id v2-icon-3 "v2-icon-3")
(def ^:svg-id v2-icon-4 "v2-icon-4")
(def raw-svg-list "A collection of all raw SVG assets" (collect-raw-svgs))
(mf/defc raw-svg*
{::mf/props :obj}
[{:keys [asset] :rest props}]
[:> "svg" props
[:use {:href (dm/str "#asset-" asset)}]])

View file

@ -0,0 +1,28 @@
import { Canvas, Meta } from "@storybook/blocks";
import * as RawSvgStories from "./raw_svg.stories";
<Meta of={RawSvgStories} />
# SVG Assets
## Technical notes
There are some SVG assets that are not icons or cursors, and that are mostly
meant to be used as is (although depending on the context, some will be required
to be used with a specific color or size).
The assets are located in the `frontend/resources/images/assets` folder.
### Using asset IDs
For convenience, asset IDs are available in the component namespace.
```clj
(ns app.main.ui.foo
(:require
[app.main.ui.ds.foundations.raw-svg :as svg]))
```
```clj
[:> svg/svg-asset* {:asset svg/logo}]
```

View file

@ -0,0 +1,46 @@
import * as React from "react";
import Components from "@target/components";
const { RawSvg } = Components;
const { StoryWrapper, StoryGrid, StoryGridCell, StoryHeader } =
Components.storybook;
const { svgs } = Components.meta;
export default {
title: "Foundations/RawSvg",
component: Components.RawSvg,
};
const assetList = Object.entries(svgs)
.map(([_, value]) => value)
.sort();
export const AllAssets = {
render: () => (
<StoryWrapper theme="light">
<StoryHeader>
<h1>All assets</h1>
<p>Hover on a asset to see its id.</p>
</StoryHeader>
<StoryGrid size="200">
{assetList.map((x) => (
<StoryGridCell key={x} title={x}>
<RawSvg asset={x} style={{ maxWidth: "100%" }} />
</StoryGridCell>
))}
</StoryGrid>
</StoryWrapper>
),
parameters: {
backgrounds: { values: [{ name: "debug", value: "#ccc" }] },
},
};
export const Default = {
render: () => (
<StoryWrapper theme="default">
<RawSvg asset={svgs.BrandGitlab} width="200" />
</StoryWrapper>
),
};

View file

@ -6,7 +6,9 @@
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.ds.storybook
(:require-macros [app.main.style :as stl])
(:require-macros
[app.common.data.macros :as dm]
[app.main.style :as stl])
(:require
[rumext.v2 :as mf]))
@ -20,7 +22,26 @@
[:section {:class "default"} children]
[:section {:class "light"} children]])])
(mf/defc icon-grid*
(mf/defc story-grid*
{::mf/props :obj}
[{:keys [children]}]
[:article {:class (stl/css :icon-grid)} children])
[{:keys [children size style] :rest other}]
(let [class (stl/css :story-grid)
size (or size 16)
style (or style {})
style (mf/spread style :--component-grid-size (dm/str size "px"))
props (mf/spread-props other {:class class :style style})]
[:> "article" props children]))
(mf/defc story-grid-cell*
{::mf/props :obj}
[{:keys [children] :rest other}]
(let [class (stl/css :story-grid-cell)
props (mf/spread-props other {:class class})]
[:> "article" props children]))
(mf/defc story-header*
{::mf/props :obj}
[{:keys [children] :rest other}]
(let [class (stl/css :story-header)
props (mf/spread-props other {:class class})]
[:> "header" props children]))

View file

@ -5,9 +5,17 @@
row-gap: 1rem;
}
.icon-grid {
.story-grid {
display: grid;
grid-template-columns: repeat(auto-fit, 16px);
grid-template-columns: repeat(auto-fit, var(--component-grid-size, 16px));
gap: 1rem;
color: var(--color-foreground-primary);
}
.story-grid-cell {
max-width: 100%;
}
.story-header {
color: var(--color-foreground-primary);
}