1
1
mirror of https://github.com/penpot/penpot.git synced 2024-10-03 19:49:26 +03:00

Merge pull request #4832 from penpot/ladybenko-7866-icon-component

Implement icon component (design system)
This commit is contained in:
Andrey Antukh 2024-07-03 19:33:16 +02:00 committed by GitHub
commit bafe2ab985
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 472 additions and 84 deletions

View File

@ -1 +1,6 @@
<link href="/css/main.css" rel="stylesheet" type="text/css" />
<link href="/css/main.css" rel="stylesheet" type="text/css" />
<style>
body {
overflow-y: scroll;
}
</style>

View File

@ -1,2 +1 @@
{{>../public/images/sprites/symbol/icons.svg}}
{{> ../public/images/sprites/symbol/icons.svg }}

View File

@ -353,11 +353,13 @@ async function generateTemplates() {
await fs.writeFile("./resources/public/index.html", content);
content = await renderTemplate("resources/templates/preview-body.mustache", {
manifest: manifest,
translations: JSON.stringify(translations),
});
content = await renderTemplate(
"resources/templates/preview-body.mustache",
{
manifest: manifest,
},
partials,
);
await fs.writeFile("./.storybook/preview-body.html", content);
content = await renderTemplate("resources/templates/render.mustache", {

View File

@ -6,10 +6,12 @@
(ns app.main.ui.ds
(:require
[app.main.ui.ds.buttons.simple-button :refer [simple-button]]
[app.main.ui.ds.foundations.icon :refer [icon* icon-list]]
[app.main.ui.ds.storybook :as sb]))
(def default
"A export used for storybook"
#js {:SimpleButton simple-button
:StoryWrapper sb/story-wrapper})
#js {:Icon icon*
;; meta / misc
:meta #js {:icons icon-list}
:storybook #js {:StoryWrapper sb/story-wrapper* :IconGrid sb/icon-grid*}})

View File

@ -1,10 +0,0 @@
(ns app.main.ui.ds.buttons.simple-button
(:require-macros [app.main.style :as stl])
(:require
[rumext.v2 :as mf]))
(mf/defc simple-button
{::mf/wrap-props false}
[{:keys [on-click children]}]
[:button {:on-click on-click :class (stl/css :button)} children])

View File

@ -1,12 +0,0 @@
import { Canvas, Meta } from '@storybook/blocks';
import * as SimpleButtonStories from "./simple_button.stories"
<Meta of={SimpleButtonStories} />
# Lorem ipsum
This is an example of **markdown** docs within storybook, for the component `<SimpleButton>`.
Here's how we can render a simple button:
<Canvas of={SimpleButtonStories.Default} />

View File

@ -1,7 +0,0 @@
@use "../colors.scss" as *;
.button {
appearance: none;
border: 0;
background: var(--color-accent-primary);
}

View File

@ -1,16 +0,0 @@
import * as React from "react";
import Components from "@target/components";
export default {
title: "Buttons/Simple Button",
component: Components.SimpleButton,
};
export const Default = {
render: () => (
<Components.StoryWrapper>
<Components.SimpleButton>Simple Button</Components.SimpleButton>
</Components.StoryWrapper>
),
};

View File

@ -1,10 +0,0 @@
import { expect, test } from 'vitest'
test('use jsdom in this test file', () => {
const element = document.createElement('div')
expect(element).not.toBeNull()
})
test('adds 1 + 2 to equal 3', () => {
expect(1 +2).toBe(3)
});

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.icon
(:require
[clojure.core :as c]
[cuerdas.core :as str]
[rumext.v2]))
(defmacro collect-icons
[]
(let [ns-info (:ns &env)]
`(cljs.core/js-obj
~@(->> (:defs ns-info)
(map val)
(filter (fn [entry] (-> entry :meta :icon-id)))
(mapcat (fn [{:keys [name]}]
[(-> name c/name str/camel str/capital) name]))))))

View File

@ -0,0 +1,260 @@
;; 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.icon
(:refer-clojure :exclude [mask])
(:require-macros
[app.common.data.macros :as dm]
[app.main.style :as stl]
[app.main.ui.ds.foundations.icon :refer [collect-icons]])
(:require
[rumext.v2 :as mf]))
(def ^:icon-id absolute "absolute")
(def ^:icon-id add "add")
(def ^:icon-id align-bottom "align-bottom")
(def ^:icon-id align-content-column-around "align-content-column-around")
(def ^:icon-id align-content-column-between "align-content-column-between")
(def ^:icon-id align-content-column-center "align-content-column-center")
(def ^:icon-id align-content-column-end "align-content-column-end")
(def ^:icon-id align-content-column-evenly "align-content-column-evenly")
(def ^:icon-id align-content-column-start "align-content-column-start")
(def ^:icon-id align-content-column-stretch "align-content-column-stretch")
(def ^:icon-id align-content-row-around "align-content-row-around")
(def ^:icon-id align-content-row-between "align-content-row-between")
(def ^:icon-id align-content-row-center "align-content-row-center")
(def ^:icon-id align-content-row-end "align-content-row-end")
(def ^:icon-id align-content-row-evenly "align-content-row-evenly")
(def ^:icon-id align-content-row-start "align-content-row-start")
(def ^:icon-id align-content-row-stretch "align-content-row-stretch")
(def ^:icon-id align-horizontal-center "align-horizontal-center")
(def ^:icon-id align-items-column-center "align-items-column-center")
(def ^:icon-id align-items-column-end "align-items-column-end")
(def ^:icon-id align-items-column-start "align-items-column-start")
(def ^:icon-id align-items-row-center "align-items-row-center")
(def ^:icon-id align-items-row-end "align-items-row-end")
(def ^:icon-id align-items-row-start "align-items-row-start")
(def ^:icon-id align-left "align-left")
(def ^:icon-id align-right "align-right")
(def ^:icon-id align-self-column-bottom "align-self-column-bottom")
(def ^:icon-id align-self-column-center "align-self-column-center")
(def ^:icon-id align-self-column-stretch "align-self-column-stretch")
(def ^:icon-id align-self-column-top "align-self-column-top")
(def ^:icon-id align-self-row-center "align-self-row-center")
(def ^:icon-id align-self-row-left "align-self-row-left")
(def ^:icon-id align-self-row-right "align-self-row-right")
(def ^:icon-id align-self-row-stretch "align-self-row-stretch")
(def ^:icon-id align-top "align-top")
(def ^:icon-id align-vertical-center "align-vertical-center")
(def ^:icon-id arrow "arrow")
(def ^:icon-id asc-sort "asc-sort")
(def ^:icon-id board "board")
(def ^:icon-id boards-thumbnail "boards-thumbnail")
(def ^:icon-id boolean-difference "boolean-difference")
(def ^:icon-id boolean-exclude "boolean-exclude")
(def ^:icon-id boolean-flatten "boolean-flatten")
(def ^:icon-id boolean-intersection "boolean-intersection")
(def ^:icon-id boolean-union "boolean-union")
(def ^:icon-id bug "bug")
(def ^:icon-id clip-content "clip-content")
(def ^:icon-id clipboard "clipboard")
(def ^:icon-id close-small "close-small")
(def ^:icon-id close "close")
(def ^:icon-id code "code")
(def ^:icon-id column-reverse "column-reverse")
(def ^:icon-id column "column")
(def ^:icon-id comments "comments")
(def ^:icon-id component-copy "component-copy")
(def ^:icon-id component "component")
(def ^:icon-id constraint-horizontal "constraint-horizontal")
(def ^:icon-id constraint-vertical "constraint-vertical")
(def ^:icon-id corner-bottom-left "corner-bottom-left")
(def ^:icon-id corner-bottom-right "corner-bottom-right")
(def ^:icon-id corner-bottom "corner-bottom")
(def ^:icon-id corner-center "corner-center")
(def ^:icon-id corner-radius "corner-radius")
(def ^:icon-id corner-top "corner-top")
(def ^:icon-id corner-top-left "corner-top-left")
(def ^:icon-id corner-top-right "corner-top-right")
(def ^:icon-id curve "curve")
(def ^:icon-id delete-text "delete-text")
(def ^:icon-id delete "delete")
(def ^:icon-id desc-sort "desc-sort")
(def ^:icon-id detach "detach")
(def ^:icon-id detached "detached")
(def ^:icon-id distribute-horizontally "distribute-horizontally")
(def ^:icon-id distribute-vertical-spacing "distribute-vertical-spacing")
(def ^:icon-id document "document")
(def ^:icon-id download "download")
(def ^:icon-id drop-icon "drop")
(def ^:icon-id easing-ease-in-out "easing-ease-in-out")
(def ^:icon-id easing-ease-in "easing-ease-in")
(def ^:icon-id easing-ease-out "easing-ease-out")
(def ^:icon-id easing-ease "easing-ease")
(def ^:icon-id easing-linear "easing-linear")
(def ^:icon-id effects "effects")
(def ^:icon-id elipse "elipse")
(def ^:icon-id exit "exit")
(def ^:icon-id expand "expand")
(def ^:icon-id feedback "feedback")
(def ^:icon-id fill-content "fill-content")
(def ^:icon-id filter-icon "filter")
(def ^:icon-id fixed-width "fixed-width")
(def ^:icon-id flex-grid "flex-grid")
(def ^:icon-id flex-horizontal "flex-horizontal")
(def ^:icon-id flex-vertical "flex-vertical")
(def ^:icon-id flex "flex")
(def ^:icon-id flip-horizontal "flip-horizontal")
(def ^:icon-id flip-vertical "flip-vertical")
(def ^:icon-id gap-horizontal "gap-horizontal")
(def ^:icon-id gap-vertical "gap-vertical")
(def ^:icon-id graphics "graphics")
(def ^:icon-id grid-column "grid-column")
(def ^:icon-id grid-columns "grid-columns")
(def ^:icon-id grid-gutter "grid-gutter")
(def ^:icon-id grid-margin "grid-margin")
(def ^:icon-id grid "grid")
(def ^:icon-id grid-row "grid-row")
(def ^:icon-id grid-rows "grid-rows")
(def ^:icon-id grid-square "grid-square")
(def ^:icon-id group "group")
(def ^:icon-id gutter-horizontal "gutter-horizontal")
(def ^:icon-id gutter-vertical "gutter-vertical")
(def ^:icon-id help "help")
(def ^:icon-id hide "hide")
(def ^:icon-id history "history")
(def ^:icon-id hsva "hsva")
(def ^:icon-id hug-content "hug-content")
(def ^:icon-id icon "icon")
(def ^:icon-id img "img")
(def ^:icon-id interaction "interaction")
(def ^:icon-id join-nodes "join-nodes")
(def ^:icon-id external-link "external-link")
(def ^:icon-id justify-content-column-around "justify-content-column-around")
(def ^:icon-id justify-content-column-between "justify-content-column-between")
(def ^:icon-id justify-content-column-center "justify-content-column-center")
(def ^:icon-id justify-content-column-end "justify-content-column-end")
(def ^:icon-id justify-content-column-evenly "justify-content-column-evenly")
(def ^:icon-id justify-content-column-start "justify-content-column-start")
(def ^:icon-id justify-content-row-around "justify-content-row-around")
(def ^:icon-id justify-content-row-between "justify-content-row-between")
(def ^:icon-id justify-content-row-center "justify-content-row-center")
(def ^:icon-id justify-content-row-end "justify-content-row-end")
(def ^:icon-id justify-content-row-evenly "justify-content-row-evenly")
(def ^:icon-id justify-content-row-start "justify-content-row-start")
(def ^:icon-id layers "layers")
(def ^:icon-id library "library")
(def ^:icon-id locate "locate")
(def ^:icon-id lock "lock")
(def ^:icon-id margin "margin")
(def ^:icon-id margin-bottom "margin-bottom")
(def ^:icon-id margin-left "margin-left")
(def ^:icon-id margin-left-right "margin-left-right")
(def ^:icon-id margin-right "margin-right")
(def ^:icon-id margin-top "margin-top")
(def ^:icon-id margin-top-bottom "margin-top-bottom")
(def ^:icon-id mask "mask")
(def ^:icon-id masked "masked")
(def ^:icon-id menu "menu")
(def ^:icon-id merge-nodes "merge-nodes")
(def ^:icon-id move "move")
(def ^:icon-id msg-error "msg-error")
(def ^:icon-id msg-neutral "msg-neutral")
(def ^:icon-id msg-success "msg-success")
(def ^:icon-id msg-warning "msg-warning")
(def ^:icon-id open-link "open-link")
(def ^:icon-id padding-bottom "padding-bottom")
(def ^:icon-id padding-extended "padding-extended")
(def ^:icon-id padding-left "padding-left")
(def ^:icon-id padding-left-right "padding-left-right")
(def ^:icon-id padding-right "padding-right")
(def ^:icon-id padding-top "padding-top")
(def ^:icon-id padding-top-bottom "padding-top-bottom")
(def ^:icon-id path "path")
(def ^:icon-id pentool "pentool")
(def ^:icon-id picker "picker")
(def ^:icon-id pin "pin")
(def ^:icon-id play "play")
(def ^:icon-id rectangle "rectangle")
(def ^:icon-id reload "reload")
(def ^:icon-id remove-icon "remove")
(def ^:icon-id rgba "rgba")
(def ^:icon-id rgba-complementary "rgba-complementary")
(def ^:icon-id rocket "rocket")
(def ^:icon-id rotation "rotation")
(def ^:icon-id row "row")
(def ^:icon-id row-reverse "row-reverse")
(def ^:icon-id search "search")
(def ^:icon-id separate-nodes "separate-nodes")
(def ^:icon-id shown "shown")
(def ^:icon-id size-horizontal "size-horizontal")
(def ^:icon-id size-vertical "size-vertical")
(def ^:icon-id snap-nodes "snap-nodes")
(def ^:icon-id status-alert "status-alert")
(def ^:icon-id status-tick "status-tick")
(def ^:icon-id status-update "status-update")
(def ^:icon-id status-wrong "status-wrong")
(def ^:icon-id stroke-arrow "stroke-arrow")
(def ^:icon-id stroke-circle "stroke-circle")
(def ^:icon-id stroke-diamond "stroke-diamond")
(def ^:icon-id stroke-rectangle "stroke-rectangle")
(def ^:icon-id stroke-rounded "stroke-rounded")
(def ^:icon-id stroke-size "stroke-size")
(def ^:icon-id stroke-squared "stroke-squared")
(def ^:icon-id stroke-triangle "stroke-triangle")
(def ^:icon-id svg "svg")
(def ^:icon-id swatches "swatches")
(def ^:icon-id switch "switch")
(def ^:icon-id text "text")
(def ^:icon-id text-align-center "text-align-center")
(def ^:icon-id text-align-left "text-align-left")
(def ^:icon-id text-align-right "text-align-right")
(def ^:icon-id text-auto-height "text-auto-height")
(def ^:icon-id text-auto-width "text-auto-width")
(def ^:icon-id text-bottom "text-bottom")
(def ^:icon-id text-fixed "text-fixed")
(def ^:icon-id text-justify "text-justify")
(def ^:icon-id text-letterspacing "text-letterspacing")
(def ^:icon-id text-lineheight "text-lineheight")
(def ^:icon-id text-lowercase "text-lowercase")
(def ^:icon-id text-ltr "text-ltr")
(def ^:icon-id text-middle "text-middle")
(def ^:icon-id text-mixed "text-mixed")
(def ^:icon-id text-palette "text-palette")
(def ^:icon-id text-paragraph "text-paragraph")
(def ^:icon-id text-rtl "text-rtl")
(def ^:icon-id text-stroked "text-stroked")
(def ^:icon-id text-top "text-top")
(def ^:icon-id text-underlined "text-underlined")
(def ^:icon-id text-uppercase "text-uppercase")
(def ^:icon-id thumbnail "thumbnail")
(def ^:icon-id tick "tick")
(def ^:icon-id to-corner "to-corner")
(def ^:icon-id to-curve "to-curve")
(def ^:icon-id tree "tree")
(def ^:icon-id unlock "unlock")
(def ^:icon-id user "user")
(def ^:icon-id vertical-align-items-center "vertical-align-items-center")
(def ^:icon-id vertical-align-items-end "vertical-align-items-end")
(def ^:icon-id vertical-align-items-start "vertical-align-items-start")
(def ^:icon-id view-as-icons "view-as-icons")
(def ^:icon-id view-as-list "view-as-list")
(def ^:icon-id wrap "wrap")
(def icon-list "A collection of all icons" (collect-icons))
(def ^:private icon-size-m 16)
(def ^:private icon-size-s 12)
(mf/defc icon*
{::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})
size-px (cond (= size "s") icon-size-s :else icon-size-m)
offset (/ (- icon-size-m size-px) 2)]
[:> "svg" props
[:use {:href (dm/str "#icon-" icon) :width size-px :height size-px :x offset :y offset}]]))

View File

@ -0,0 +1,101 @@
import { Canvas, Meta } from '@storybook/blocks';
import * as IconStories from "./icon.stories"
<Meta of={IconStories} />
# Iconography
See the [list of all available icons](?path=/story/foundations-icons--all-icons).
## Variants
### Medium (default)
- Used in the majority of the interface, and **it is the default variant**.
- Displayed within a box of 16×16 pixels.
- 1 px stroke (centered).
This is enabled with `size` prop set to `"m"`.
### Small
- Displayed within a box of 16×16 pixels.
- Bigger padding (4 pixels)
- Best used when the space is limited. Use in components such text inputs os in
dense interfaces such layers or chevrons to indicate a collapsible element.
This is enabled with `size` prop set to `"s"`.
## Technical notes
### Using icons IDs
There are icon ID definitions you can use in your code rather than typing the
icon ID by hand.
**Using these IDs is recommended**, since they are invariant to the icon asset
filename.
Assuming the namespace is required as `i`:
```clj
(ns app.main.ui.foo
(:require
[app.main.ui.ds.foundations.icon :as i]))
```
You can now use the icon IDs defined in the namespace:
```clj
[:> i/icon* {:icon i/pin}]
```
### Customizing colors
Icon color is set internally with the `stroke` property and it's set to
`currentColor` by default.
If you need to override this behavior, you can use a `class` in the `<Icon>`
component and set `color` to whatever value you prefer:
```clj
[:> i/icon* {:icon i/add :class (stl/css :toolbar-icon)}]
```
```scss
.toolbar-icon {
color: var(--component-toolbar-icon-color);
}
```
### Accessibility
By default, icons do not have any accessible text attached to them. You should
add an `aria-label` attribute to set a proper text:
```clj
[:> i/icon* {:icon i/add :aria-label (tr "foo.bar")}]
```
## Usage guidelines for design
### Layout
Icon content should remain inside of the live area. In specific icons that need
to adjust visual weight, content may extend into the padding between live area
and trim area, never outside the trim area.
### Color
Icons are normally displayed with the chromatic characteristics of the component
in which they are included. They are normally based on foreground and neutral
colours, but may also, in some cases, be displayed in semantic colours when
their function is to accompany system status messages.
### Accessibility
Icons must meet the contrast requirement.
They should be designed with the correct transmission of the concept they
communicate in mind, reviewing cultural and generational differences to avoid
comprehension problems.

View File

@ -0,0 +1,4 @@
.icon {
fill: none;
stroke: currentColor;
}

View File

@ -0,0 +1,47 @@
import * as React from "react";
import Components from "@target/components";
const { Icon } = Components;
const { StoryWrapper, IconGrid } = Components.storybook;
const { icons } = Components.meta;
export default {
title: "Foundations/Icons",
component: Components.Icon,
};
const iconList = Object.entries(icons)
.map(([_, value]) => value)
.sort();
export const AllIcons = {
render: () => (
<StoryWrapper theme="default">
<h1>All Icons</h1>
<p>Hover on an icon to see its ID</p>
<IconGrid>
{iconList.map((iconId) => (
<div title={iconId} key={iconId}>
<Icon icon={iconId} />
</div>
))}
</IconGrid>
</StoryWrapper>
),
};
export const Default = {
render: () => (
<StoryWrapper theme="default">
<Icon icon="pin" />
</StoryWrapper>
),
};
export const Small = {
render: () => (
<StoryWrapper theme="default">
<Icon icon="pin" size="s" />
</StoryWrapper>
),
};

View File

@ -10,9 +10,17 @@
(:require
[rumext.v2 :as mf]))
(mf/defc story-wrapper
{::mf/wrap-props false}
[{:keys [children]}]
(mf/defc story-wrapper*
{::mf/props :obj}
[{:keys [theme children]}]
[:article {:class (stl/css :story-wrapper)}
[:section {:class "default"} children]
[:section {:class "light"} children]])
(if (some? theme)
[:section {:class theme} children]
[*
[:section {:class "default"} children]
[:section {:class "light"} children]])])
(mf/defc icon-grid*
{::mf/props :obj}
[{:keys [children]}]
[:article {:class (stl/css :icon-grid)} children])

View File

@ -4,3 +4,10 @@
display: grid;
row-gap: 1rem;
}
.icon-grid {
display: grid;
grid-template-columns: repeat(auto-fit, 16px);
gap: 1rem;
color: var(--color-foreground-primary);
}

View File

@ -285,19 +285,6 @@
"A collection of all icons"
(collect-icons))
(mf/defc debug-icons-preview
{::mf/wrap-props false}
[]
(let [entries (->> (seq (js/Object.entries default))
(sort-by first))]
[:section.debug-icons-preview
[:h2 "icons"]
(for [[key val] entries]
[:div.icon-item-old {:key key
:title key}
val
[:span key]])]))
(defn key->icon
[icon-key]
(when icon-key