0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-18 10:41:29 -05:00

Merge pull request #2654 from penpot/eva-a11y-dashboard

 Improve dashboard accessibility
This commit is contained in:
Alejandro 2022-12-16 12:52:53 +01:00 committed by GitHub
commit ac7cb3c8c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 761 additions and 283 deletions

View file

@ -9,6 +9,7 @@
height: 100%;
overflow: hidden;
overflow-y: auto;
margin-bottom: 0;
.grid-row {
display: grid;
@ -26,6 +27,9 @@
margin: $size-3 $size-4 $size-4 $size-2;
position: relative;
text-align: center;
a {
width: 100%;
}
@media #{$bp-max-1366} {
height: 200px;
flex: 1 0 230px;
@ -193,13 +197,15 @@
&.project-th {
background-color: $color-white;
&:hover {
&:hover,
&:focus,
&:focus-within {
.project-th-actions {
opacity: 1;
}
}
&.selected {
.selected {
.grid-item-th {
border: 2px solid $color-primary;
}
@ -241,7 +247,8 @@
width: 18px;
}
&:hover {
&:hover,
&:focus {
> svg {
fill: $color-primary-dark;
}
@ -312,7 +319,7 @@
background-repeat: no-repeat;
border-top-left-radius: $br-small;
border-top-right-radius: $br-small;
height: 70%;
height: 230px;
max-height: 160px;
overflow: hidden;
position: relative;
@ -430,6 +437,7 @@
background-color: rgba(227, 227, 227, 0.3);
padding: 13px;
margin-right: 13px;
height: 230px;
&.loader {
justify-items: center;
}

View file

@ -75,6 +75,7 @@
.dashboard-title {
display: flex;
align-items: center;
margin-left: 13px;
h1 {
color: $color-black;
@ -83,7 +84,6 @@
font-size: $fs22;
font-weight: 600;
z-index: 10;
margin-left: 13px;
}
.context-menu.is-open {

View file

@ -7,13 +7,10 @@
.dashboard-sidebar {
background-color: $color-white;
z-index: 1;
.sidebar-inside {
display: flex;
flex-direction: column;
height: 100%;
padding-top: $size-2;
}
display: flex;
flex-direction: column;
height: 100%;
padding-top: $size-2;
.sidebar-content {
display: flex;
@ -67,7 +64,8 @@
justify-content: center;
align-items: center;
cursor: pointer;
background-color: transparent;
border: none;
svg {
width: 15px;
height: 13px;
@ -78,15 +76,18 @@
.current-team {
cursor: pointer;
display: flex;
align-items: center;
flex-grow: 1;
font-size: $fs14;
padding: 0px 10px;
background-color: transparent;
border: none;
}
.team-name {
flex-grow: 1;
display: flex;
height: 48px;
height: 40px;
align-items: center;
&.action {
@ -151,7 +152,7 @@
.switch-icon {
display: flex;
align-items: center;
justify-content: center;
svg {
width: 10px;
height: 10px;
@ -200,6 +201,9 @@
display: flex;
flex-shrink: 0;
padding: $size-2;
a {
width: 100%;
}
svg {
fill: $color-black;

View file

@ -72,7 +72,7 @@
border-radius: 8px;
min-height: 211px;
.img {
.thumbnail {
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
padding: 30px;
@ -133,15 +133,10 @@
display: none;
width: 0;
}
.text {
.info {
// font-size: $fs12;
}
}
}
}
.walkthrough {
.img {
.thumbnail {
background-image: url("/images/walkthrough-cover.png");
background-position: center;
background-repeat: no-repeat;
@ -149,7 +144,7 @@
}
}
.tutorial {
.img {
.thumbnail {
background-image: url("/images/hands-on-tutorial.png");
background-position: center;
background-repeat: no-repeat;
@ -258,7 +253,8 @@
}
.project-actions {
display: none;
display: flex;
opacity: 0;
margin-left: $size-6;
.btn-small {
@ -273,6 +269,8 @@
display: flex;
align-items: center;
margin-right: 14px;
background-color: transparent;
border: none;
svg {
width: 15px;
height: 15px;
@ -286,9 +284,12 @@
}
}
}
&:hover {
&:hover,
&:focus,
&:focus-within {
.project-actions {
display: flex;
opacity: 1;
}
}
}
@ -439,7 +440,8 @@
width: 100%;
text-align: right;
height: 56px;
div {
button {
border: none;
cursor: pointer;
height: 58px;
display: inline-block;
@ -518,11 +520,13 @@
.card-container {
width: 275px;
height: 100%;
margin-top: 20px;
display: inline-block;
text-align: center;
vertical-align: top;
background-color: transparent;
border: none;
padding: 0;
}
.template-card {

View file

@ -38,6 +38,10 @@
&:hover {
background-color: $color-primary-lighter;
}
&:focus {
border: 1px black solid;
}
}
&.with-check {

View file

@ -37,7 +37,7 @@
<body>
{{>../public/images/sprites/symbol/icons.svg}}
{{>../public/images/sprites/symbol/cursors.svg}}
<section id="app" tabindex="1"></section>
<div id="app" tabindex="0"></div>
<section id="modal"></section>
{{# manifest}}
<script src="{{& shared}}"></script>

View file

@ -17,7 +17,7 @@
{{/manifest}}
</head>
<body>
<section id="app" tabindex="1"></section>
<div id="app" tabindex="0"></div>
{{# manifest}}
<script src="{{& shared}}"></script>
<script src="{{& render}}"></script>

View file

@ -250,20 +250,20 @@
(when (contains? @cf/flags :login)
[:div.link-entry
[:& lk/link {:action #(st/emit! (rt/nav :auth-recovery-request))
:name (tr "auth.forgot-password")
:data-test "forgot-password"}]])
:data-test "forgot-password"}
(tr "auth.forgot-password")]])
(when (contains? @cf/flags :registration)
[:div.link-entry
[:span (tr "auth.register") " "]
[:& lk/link {:action #(st/emit! (rt/nav :auth-register {} params))
:name (tr "auth.register-submit")
:data-test "register-submit"}]])]
:data-test "register-submit"}
(tr "auth.register-submit")]])]
(when (contains? @cf/flags :demo-users)
[:div.links.demo
[:div.link-entry
[:span (tr "auth.create-demo-profile") " "]
[:& lk/link {:action #(st/emit! (du/create-demo-profile))
:name (tr "auth.create-demo-account")
:data-test "demo-account-link"}]]])]])
:data-test "demo-account-link"}
(tr "auth.create-demo-account")]]])]])

View file

@ -101,5 +101,5 @@
[:div.links
[:div.link-entry
[:& lk/link {:action go-back
:name (tr "labels.go-back")
:data-test "go-back-link"}]]]]]))
:data-test "go-back-link"}
(tr "labels.go-back")]]]]]))

View file

@ -167,14 +167,14 @@
[:span (tr "auth.already-have-account") " "]
[:& lk/link {:action #(st/emit! (rt/nav :auth-login {} params))
:name (tr "auth.login-here")
:data-test "login-here-link"}]]
:data-test "login-here-link"}
(tr "auth.login-here")]]
(when (contains? @cf/flags :demo-users)
[:div.link-entry
[:span (tr "auth.create-demo-profile") " "]
[:& lk/link {:action #(st/emit! (du/create-demo-profile))
:name (tr "auth.create-demo-account")}]])]])
[:& lk/link {:action #(st/emit! (du/create-demo-profile))}
(tr "auth.create-demo-account")]])]])
;; --- PAGE: register validation
@ -271,8 +271,8 @@
[:div.links
[:div.link-entry
[:& lk/link {:action #(st/emit! (rt/nav :auth-register {} {}))
:name (tr "labels.go-back")}]]]])
[:& lk/link {:action #(st/emit! (rt/nav :auth-register {} {}))}
(tr "labels.go-back")]]]])
(mf/defc register-success-page
[{:keys [params] :as props}]

View file

@ -12,7 +12,7 @@
(mf/defc button-link [{:keys [action icon name klass]}]
[:a.btn-primary.btn-large.button-link
{:class klass
:tabindex "0"
:tab-index "0"
:on-click action
:on-key-down (fn [event]
(when (kbd/enter? event)

View file

@ -0,0 +1,121 @@
;; 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.components.dropdown-menu
(:require
[app.common.data :as d]
[app.config :as cfg]
[app.util.dom :as dom]
[app.util.globals :as globals]
[app.util.keyboard :as kbd]
[goog.events :as events]
[goog.object :as gobj]
[rumext.v2 :as mf])
(:import goog.events.EventType))
(mf/defc dropdown-menu-item
{::mf/wrap-props false}
[props]
(let [children (gobj/get props "children")
on-click (gobj/get props "on-click")
on-key-down (gobj/get props "on-key-down")
id (gobj/get props "id")
klass (gobj/get props "klass")
key (gobj/get props "klass")
data-test (gobj/get props "data-test")]
[:li {:id id
:class klass
:tab-index "0"
:on-key-down on-key-down
:on-click on-click
:key key
:role "menuitem"
:data-test data-test}
children]))
(mf/defc dropdown-menu'
{::mf/wrap-props false}
[props]
(let [children (gobj/get props "children")
on-close (gobj/get props "on-close")
ref (gobj/get props "container")
ids (gobj/get props "ids")
list-class (gobj/get props "list-class")
on-click
(fn [event]
(let [target (dom/get-target event)
;; MacOS ctrl+click sends two events: context-menu and click.
;; In order to not have two handlings we ignore ctrl+click for this platform
mac-ctrl-click? (and (cfg/check-platform? :macos) (kbd/ctrl? event))]
(when (and (not mac-ctrl-click?)
(not (.-data-no-close ^js target)))
(if ref
(let [parent (mf/ref-val ref)]
(when-not (or (not parent) (.contains parent target))
(on-close)))
(on-close)))))
on-keyup
(fn [event]
(when (kbd/esc? event)
(on-close)))
on-key-down
(fn [event]
(let [first-id (dom/get-element (first ids))
first-element (dom/get-element first-id)
len (count ids)]
(when (kbd/home? event)
(when first-element
(dom/focus! first-element)))
(when (kbd/up-arrow? event)
(let [actual-selected (dom/get-active)
actual-id (dom/get-attribute actual-selected "id")
actual-index (d/index-of ids actual-id)
previous-id (if (= 0 actual-index)
(last ids)
(nth ids (- actual-index 1)))]
(dom/focus! (dom/get-element previous-id))))
(when (kbd/down-arrow? event)
(let [actual-selected (dom/get-active)
actual-id (dom/get-attribute actual-selected "id")
actual-index (d/index-of ids actual-id)
next-id (if (= (- len 1) actual-index)
(first ids)
(nth ids (+ 1 actual-index)))]
(dom/focus! (dom/get-element next-id))))
(when (kbd/tab? event)
(on-close))))
on-mount
(fn []
(let [keys [(events/listen globals/document EventType.CLICK on-click)
(events/listen globals/document EventType.CONTEXTMENU on-click)
(events/listen globals/document EventType.KEYUP on-keyup)
(events/listen globals/document EventType.KEYDOWN on-key-down)]]
#(doseq [key keys]
(events/unlistenByKey key))))]
(mf/use-effect on-mount)
[:ul {:class list-class
:role "menu"}
children]))
(mf/defc dropdown-menu
{::mf/wrap-props false}
[props]
(assert (fn? (gobj/get props "on-close")) "missing `on-close` prop")
(assert (boolean? (gobj/get props "show")) "missing `show` prop")
(when (gobj/get props "show")
(mf/element dropdown-menu' props)))

View file

@ -39,5 +39,6 @@
:type "file"
:ref input-ref
:on-change on-files-selected
:data-test data-test}]]))
:data-test data-test
:aria-label "uploader"}]]))

View file

@ -104,7 +104,7 @@
:placeholder label
:on-change on-change
:type @type'
:tabindex "0")
:tab-index "0")
(cond-> (and value is-checkbox?) (assoc :default-checked value))
(cond-> (and touched? (:message error)) (assoc "aria-invalid" "true"
"aria-describedby" (dm/str "error-" input-name)))
@ -224,7 +224,7 @@
{:name "submit"
:class (when (or (not (:valid @form)) (true? disabled)) "btn-disabled")
:disabled (or (not (:valid @form)) (true? disabled))
:tabindex "0"
:tab-index "0"
:on-click on-click
:on-key-down (fn [event]
(when (kbd/enter? event)

View file

@ -9,12 +9,13 @@
[app.util.keyboard :as kbd]
[rumext.v2 :as mf]))
(mf/defc link [{:keys [action name klass data-test]}]
[:a {:on-click action
:klass klass
:on-key-down (fn [event]
(when (kbd/enter? event)
(action event)))
:tabindex "0"
:data-test data-test}
name])
(mf/defc link [{:keys [action klass data-test keyboard-action children]}]
(let [keyboard-action (or keyboard-action action)]
[:a {:on-click action
:class klass
:on-key-down (fn [event]
(when (kbd/enter? event)
(keyboard-action event)))
:tab-index "0"
:data-test data-test}
[:* children]]))

View file

@ -145,15 +145,20 @@
[:div.dashboard-templates-section {:class (when collapsed "collapsed")}
[:div.title
[:div {:on-click toggle-collapse}
[:button {:tab-index "0"
:on-click toggle-collapse}
[:span (tr "dashboard.libraries-and-templates")]
[:span.icon (if collapsed i/arrow-up i/arrow-down)]]]
[:div.content {:ref content-ref
:style {:left @card-offset :width (str container-size "px")}}
(for [num-item (range (count templates)) :let [item (nth templates num-item)]]
[:div.card-container {:id (str/concat "card-container-" num-item)
:key (:id item)
:on-click #(import-template item)}
[:a.card-container {:tab-index "0"
:id (str/concat "card-container-" num-item)
:key (:id item)
:on-click #(import-template item)
:on-key-down (fn [event]
(when (kbd/enter? event)
(import-template item)))}
[:div.template-card
[:div.img-container
[:img {:src (:thumbnail-uri item)
@ -163,14 +168,16 @@
[:div.card-container
[:div.template-card
[:div.img-container
[:a {:href "https://penpot.app/libraries-templates.html" :target "_blank" :on-click handle-template-link}
[:a {:tab-index "0"
:href "https://penpot.app/libraries-templates.html" :target "_blank" :on-click handle-template-link}
[:div.template-link
[:div.template-link-title (tr "dashboard.libraries-and-templates")]
[:div.template-link-text (tr "dashboard.libraries-and-templates.explore")]]]]]]]
(when (< @card-offset 0)
[:div.button.left {:on-click move-left} i/go-prev])
[:button.button.left {:on-click move-left} i/go-prev])
(when more-cards
[:div.button.right {:on-click move-right} i/go-next])]))
[:button.button.right {:on-click move-right
:aria-label (tr "labels.next")} i/go-next])]))
(mf/defc dashboard-content
[{:keys [team projects project section search-term profile] :as props}]
@ -285,7 +292,7 @@
;; components on team change. Many components assumes that the
;; team is already set so don't put the team into mf/deps.
(when team
[:section.dashboard-layout {:key (:id team)}
[:main.dashboard-layout {:key (:id team)}
[:& sidebar
{:team team
:projects projects

View file

@ -16,6 +16,7 @@
[app.main.ui.icons :as i]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
[potok.core :as ptk]
[rumext.v2 :as mf]))
@ -54,7 +55,11 @@
[:div.dashboard-comments-section
[:div.button
{:on-click show-dropdown
{:tab-index "0"
:on-click show-dropdown
:on-key-down (fn [event]
(when (kbd/enter? event)
(show-dropdown event)))
:data-test "open-comments"
:class (dom/classnames :open @show-dropdown?
:unread (boolean (seq tgroups)))}
@ -64,7 +69,13 @@
[:div.dropdown.comments-section.comment-threads-section.
[:div.header
[:h3 (tr "labels.comments")]
[:span.close {:on-click hide-dropdown} i/close]]
[:span.close {:tab-index (if @show-dropdown?
"0"
"-1")
:on-click hide-dropdown
:on-key-down (fn [event]
(when (kbd/enter? event)
(hide-dropdown event)))} i/close]]
[:hr]

View file

@ -17,6 +17,7 @@
[app.main.ui.icons :as i]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
[app.util.webapi :as wapi]
[beicon.core :as rx]
[cuerdas.core :as str]
@ -63,7 +64,7 @@
[:header.dashboard-header
(if (:is-default project)
[:div.dashboard-title
[:div.dashboard-title#dashboard-drafts-title
[:h1 (tr "labels.drafts")]]
(if (:edition @local)
@ -75,7 +76,9 @@
(with-meta {::ev/origin "project"}))))
(swap! local assoc :edition false)))}]
[:div.dashboard-title
[:h1 {:on-double-click on-edit :data-test "project-title"}
[:h1 {:on-double-click on-edit
:data-test "project-title"
:id (:id project)}
(:name project)]]))
[:& project-menu {:project project
@ -87,19 +90,35 @@
:on-import on-import}]
[:div.dashboard-header-actions
[:a.btn-secondary.btn-small {:on-click on-create-click :data-test "new-file"}
[:a.btn-secondary.btn-small
{:tab-index "0"
:on-click on-create-click
:data-test "new-file"
:on-key-down (fn [event]
(when (kbd/enter? event)
(on-create-click event)))}
(tr "dashboard.new-file")]
(when-not (:is-default project)
[:div.icon.pin-icon.tooltip.tooltip-bottom
{:class (when (:is-pinned project) "active")
:on-click toggle-pin :alt (tr "dashboard.pin-unpin")}
[:button.icon.pin-icon.tooltip.tooltip-bottom
{:tab-index "0"
:class (when (:is-pinned project) "active")
:on-click toggle-pin
:alt (tr "dashboard.pin-unpin")
:on-key-down (fn [event]
(when (kbd/enter? event)
(toggle-pin event)))}
(if (:is-pinned project)
i/pin-fill
i/pin)])
[:div.icon.tooltip.tooltip-bottom-left
{:on-click on-menu-click :alt (tr "dashboard.options")}
{:tab-index "0"
:on-click on-menu-click
:alt (tr "dashboard.options")
:on-key-down (fn [event]
(when (kbd/enter? event)
(on-menu-click event)))}
i/actions]]]))
(mf/defc files-section

View file

@ -51,7 +51,7 @@
(use-set-page-title team section)
[:header.dashboard-header
[:div.dashboard-title
[:div.dashboard-title#dashboard-fonts-title
[:h1 (tr "labels.fonts")]]
[:nav
#_[:ul
@ -134,8 +134,9 @@
[:& i18n/tr-html {:tag-name "span"
:label "dashboard.fonts.hero-text2"}]]]]
[:div.btn-primary
{:on-click on-click}
[:button.btn-primary
{:on-click on-click
:tab-index "0"}
[:span (tr "labels.add-custom-font")]
[:& file-uploader {:input-id "font-upload"
:accept cm/str-font-types

View file

@ -192,7 +192,7 @@
on-menu-close
(mf/use-fn
#(swap! local assoc :menu-open false))
#(swap! local assoc :menu-open false))
on-select
(fn [event]
@ -215,31 +215,31 @@
on-drag-start
(mf/use-fn
(mf/deps selected-files)
(fn [event]
(let [offset (dom/get-offset-position (.-nativeEvent event))
(mf/deps selected-files)
(fn [event]
(let [offset (dom/get-offset-position (.-nativeEvent event))
select-current? (not (contains? selected-files (:id file)))
select-current? (not (contains? selected-files (:id file)))
item-el (mf/ref-val node-ref)
counter-el (create-counter-element item-el
(if select-current?
1
(count selected-files)))]
(when select-current?
(st/emit! (dd/clear-selected-files))
(st/emit! (dd/toggle-file-select file)))
item-el (mf/ref-val node-ref)
counter-el (create-counter-element item-el
(if select-current?
1
(count selected-files)))]
(when select-current?
(st/emit! (dd/clear-selected-files))
(st/emit! (dd/toggle-file-select file)))
(dnd/set-data! event "penpot/files" "dummy")
(dnd/set-allowed-effect! event "move")
(dnd/set-data! event "penpot/files" "dummy")
(dnd/set-allowed-effect! event "move")
;; set-drag-image requires that the element is rendered and
;; visible to the user at the moment of creating the ghost
;; image (to make a snapshot), but you may remove it right
;; afterwards, in the next render cycle.
(dom/append-child! item-el counter-el)
(dnd/set-drag-image! event item-el (:x offset) (:y offset))
(ts/raf #(.removeChild ^js item-el counter-el)))))
(dom/append-child! item-el counter-el)
(dnd/set-drag-image! event item-el (:x offset) (:y offset))
(ts/raf #(.removeChild ^js item-el counter-el)))))
on-menu-click
(mf/use-fn
@ -276,44 +276,60 @@
(when (and (not selected?) (:menu-open @local))
(swap! local assoc :menu-open false)))
[:div.grid-item.project-th
{:class (dom/classnames :selected selected?
:library library-view?)
:ref node-ref
:draggable true
:on-click on-select
:on-double-click on-navigate
:on-drag-start on-drag-start
:on-context-menu on-menu-click}
[:li.grid-item.project-th
[:a
{:tab-index "0"
:class (dom/classnames :selected selected?
:library library-view?)
:ref node-ref
:draggable true
:on-click on-select
:on-key-down (fn [event]
(dom/stop-propagation event)
(when (kbd/enter? event)
(on-navigate event))
(when (kbd/shift? event)
(when (or (kbd/down-arrow? event) (kbd/left-arrow? event) (kbd/up-arrow? event) (kbd/right-arrow? event))
(on-select event)) ;; TODO Fix this
))
:on-double-click on-navigate
:on-drag-start on-drag-start
:on-context-menu on-menu-click}
[:div.overlay]
(if library-view?
[:& grid-item-library {:file file}]
[:& grid-item-thumbnail {:file file}])
(when (and (:is-shared file) (not library-view?))
[:div.item-badge i/library])
[:div.item-info
(if (:edition @local)
[:& inline-edition {:content (:name file)
:on-end edit}]
[:h3 (:name file)])
[:& grid-item-metadata {:modified-at (:modified-at file)}]]
[:div.project-th-actions {:class (dom/classnames
:force-display (:menu-open @local))}
[:div.project-th-icon.menu
{:ref menu-ref
:on-click on-menu-click}
i/actions
(when selected?
[:& file-menu {:files (vals selected-files)
:show? (:menu-open @local)
:left (+ 24 (:x (:menu-pos @local)))
:top (:y (:menu-pos @local))
:navigate? navigate?
:on-edit on-edit
:on-menu-close on-menu-close
:origin origin
:dashboard-local dashboard-local}])]]]))
[:div.overlay]
(if library-view?
[:& grid-item-library {:file file}]
[:& grid-item-thumbnail {:file file}])
(when (and (:is-shared file) (not library-view?))
[:div.item-badge i/library])
[:div.item-info
(if (:edition @local)
[:& inline-edition {:content (:name file)
:on-end edit}]
[:h3 (:name file)])
[:& grid-item-metadata {:modified-at (:modified-at file)}]]
[:div.project-th-actions {:class (dom/classnames
:force-display (:menu-open @local))}
[:div.project-th-icon.menu
{:tab-index "0"
:ref menu-ref
:on-click on-menu-click
:on-key-down (fn [event]
(when (kbd/enter? event)
(prn "entro en on-menu-click")
(on-menu-click event)))}
i/actions
(when selected?
[:& file-menu {:files (vals selected-files)
:show? (:menu-open @local)
:left (+ 24 (:x (:menu-pos @local)))
:top (:y (:menu-pos @local))
:navigate? navigate?
:on-edit on-edit
:on-menu-close on-menu-close
:origin origin
:dashboard-local dashboard-local}])]]]]
))
(mf/defc grid
@ -361,7 +377,7 @@
(reset! dragging? false)
(import-files (.-files (.-dataTransfer e))))))]
[:section.dashboard-grid
[:div.dashboard-grid
{:on-drag-enter on-drag-enter
:on-drag-over on-drag-over
:on-drag-leave on-drag-leave
@ -372,11 +388,11 @@
[:& loading-placeholder]
(seq files)
[:div.grid-row
[:ul.grid-row
{:style {:grid-template-columns (str "repeat(" limit ", 1fr)")}}
(when @dragging?
[:div.grid-item])
[:li.grid-item])
(for [item files]
[:& grid-item
@ -396,11 +412,11 @@
[{:keys [files selected-files dragging? limit] :as props}]
(let [elements limit
limit (if dragging? (dec limit) limit)]
[:div.grid-row.no-wrap
[:ul.grid-row.no-wrap
{:style {:grid-template-columns (dm/str "repeat(" elements ", 1fr)")}}
(when dragging?
[:div.grid-item.dragged])
[:li.grid-item.dragged])
(for [item (take limit files)]
[:& grid-item
{:id (:id item)
@ -481,10 +497,10 @@
mdata {:on-success on-drop-success}]
(st/emit! (dd/move-files (with-meta data mdata))))))))]
[:section.dashboard-grid {:on-drag-enter on-drag-enter
:on-drag-over on-drag-over
:on-drag-leave on-drag-leave
:on-drop on-drop}
[:div.dashboard-grid {:on-drag-enter on-drag-enter
:on-drag-over on-drag-over
:on-drag-leave on-drag-leave
:on-drop on-drop}
(cond
(nil? files)
[:& loading-placeholder]

View file

@ -51,7 +51,7 @@
[{:keys [project-id on-finish-import]} external-ref]
(let [on-file-selected (use-import-file project-id on-finish-import)]
[:form.import-file
[:form.import-file {:aria-hidden "true"}
[:& file-uploader {:accept ".penpot,.zip"
:multi true
:ref external-ref

View file

@ -71,7 +71,7 @@
[:*
[:header.dashboard-header {:ref rowref}
[:div.dashboard-title
[:div.dashboard-title#dashboard-libraries-title
[:h1 (tr "dashboard.libraries-title")]]]
[:section.dashboard-container.no-bg.dashboard-shared
[:& grid {:files files

View file

@ -19,9 +19,9 @@
(create-fn "dashboard:empty-folder-placeholder")))]
(cond
(true? dragging?)
[:div.grid-row.no-wrap
[:ul.grid-row.no-wrap
{:style {:grid-template-columns (str "repeat(" limit ", 1fr)")}}
[:div.grid-item]]
[:li.grid-item]]
(= :libraries origin)
[:div.grid-empty-placeholder.libs {:data-test "empty-placeholder"}

View file

@ -21,6 +21,7 @@
[app.main.ui.icons :as i]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
[app.util.router :as rt]
[app.util.time :as dt]
[app.util.webapi :as wapi]
@ -35,9 +36,9 @@
[]
(let [on-click (mf/use-fn #(st/emit! (dd/create-project)))]
[:header.dashboard-header
[:div.dashboard-title
[:div.dashboard-title#dashboard-projects-title
[:h1 (tr "dashboard.projects-title")]]
[:a.btn-secondary.btn-small
[:button.btn-secondary.btn-small
{:on-click on-click
:data-test "new-project-button"}
(tr "dashboard.new-project")]]))
@ -112,11 +113,11 @@
(swap! state #(assoc % :status :importing))
(st/emit! (with-meta (dd/clone-template (with-meta params mdata))
{::ev/origin "get-started-hero-block"})))))]
[:div.tutorial
[:div.img]
[:article.tutorial
[:div.thumbnail]
[:div.text
[:div.title (tr "dasboard.tutorial-hero.title")]
[:div.info (tr "dasboard.tutorial-hero.info")]
[:h2.title (tr "dasboard.tutorial-hero.title")]
[:p.info (tr "dasboard.tutorial-hero.info")]
[:button.btn-primary.action {:on-click download-tutorial}
(case (:status @state)
:waiting (tr "dasboard.tutorial-hero.start")
@ -136,12 +137,15 @@
(st/emit! (ptk/event ::ev/event {::ev/name "show-walkthrough"
::ev/origin "get-started-hero-block"
:section "dashboard"})))]
[:div.walkthrough
[:div.img]
[:article.walkthrough
[:div.thumbnail]
[:div.text
[:div.title (tr "dasboard.walkthrough-hero.title")]
[:div.info (tr "dasboard.walkthrough-hero.info")]
[:a.btn-primary.action {:href " https://design.penpot.app/walkthrough" :target "_blank" :on-click handle-walkthrough-link}
[:h2.title (tr "dasboard.walkthrough-hero.title")]
[:p.info (tr "dasboard.walkthrough-hero.info")]
[:a.btn-primary.action
{:href " https://design.penpot.app/walkthrough"
:target "_blank"
:on-click handle-walkthrough-link}
(tr "dasboard.walkthrough-hero.start")]]
[:button.close
{:on-click close-walkthrough
@ -165,8 +169,8 @@
width (mf/use-state nil)
rowref (mf/use-ref)
itemsize (if (>= @width 1030)
280
230)
280
230)
ratio (if (some? @width) (/ @width itemsize) 0)
nitems (mth/floor ratio)
@ -256,9 +260,9 @@
(vreset! mnt? false)
(rx/dispose! sub))))
[:div.dashboard-project-row
[:article.dashboard-project-row
{:class (when first? "first")}
[:div.project {:ref rowref}
[:header.project {:ref rowref}
[:div.project-name-wrapper
(if (:edition? @local)
[:& inline-edition {:content (:name project)
@ -285,23 +289,39 @@
[:span.recent-files-row-title-info (str ", " time)]))
[:div.project-actions
(when-not (:is-default project)
[:span.pin-icon.tooltip.tooltip-bottom
[:button.pin-icon.tooltip.tooltip-bottom
{:class (when (:is-pinned project) "active")
:on-click toggle-pin :alt (tr "dashboard.pin-unpin")}
:on-click toggle-pin
:alt (tr "dashboard.pin-unpin")
:aria-label (tr "dashboard.pin-unpin")
:on-key-down (fn [event]
(when (kbd/enter? event)
(toggle-pin event)))
:tab-index "0"}
(if (:is-pinned project)
i/pin-fill
i/pin)])
[:a.btn-secondary.btn-small.tooltip.tooltip-bottom
[:button.btn-secondary.btn-small.tooltip.tooltip-bottom
{:on-click on-create-click
:alt (tr "dashboard.new-file")
:data-test "project-new-file"}
:aria-label (tr "dashboard.new-file")
:data-test "project-new-file"
:tab-index "0"
:on-key-down (fn [event]
(when (kbd/enter? event)
(on-create-click event)))}
i/close]
[:a.btn-secondary.btn-small.tooltip.tooltip-bottom
[:button.btn-secondary.btn-small.tooltip.tooltip-bottom
{:on-click on-menu-click
:alt (tr "dashboard.options")
:data-test "project-options"}
:aria-label (tr "dashboard.options")
:data-test "project-options"
:tab-index "0"
:on-key-down (fn [event]
(when (kbd/enter? event)
(on-menu-click event)))}
i/actions]]]
(when (and (> limit 0)
@ -387,7 +407,7 @@
[:& interface-walkthrough
{:close-walkthrough close-walkthrough}])])
[:section.dashboard-container.no-bg
[:div.dashboard-container.no-bg
(for [{:keys [id] :as project} projects]
(let [files (when recent-map
(->> (vals recent-map)

View file

@ -64,7 +64,7 @@
(rx/dispose! sub)))))
[:*
[:header.dashboard-header
[:div.dashboard-title
[:div.dashboard-title#dashboard-search-title
[:h1 (tr "dashboard.title-search")]]]
[:section.dashboard-container.search.no-bg {:ref rowref}

View file

@ -17,7 +17,8 @@
[app.main.data.users :as du]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.components.dropdown-menu :refer [dropdown-menu dropdown-menu-item]]
[app.main.ui.components.link :refer [link]]
[app.main.ui.dashboard.comments :refer [comments-section]]
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
[app.main.ui.dashboard.project-menu :refer [project-menu]]
@ -29,6 +30,7 @@
[app.util.keyboard :as kbd]
[app.util.object :as obj]
[app.util.router :as rt]
[app.util.timers :as ts]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[goog.functions :as f]
@ -54,6 +56,20 @@
(fn []
(st/emit! (dd/go-to-files (:id item)))))
on-key-down
(mf/use-callback
(mf/deps item)
(fn [event]
(when (kbd/enter? event)
(st/emit! (dd/go-to-files (:id item))
(ts/schedule-on-idle
(fn []
(let [project-title (dom/get-element (str (:id item)))]
(when project-title
(dom/set-attribute! project-title "tabindex" "0")
(dom/focus! project-title)
(dom/set-attribute! project-title "tabindex" "-1")))))))))
on-menu-click
(mf/use-callback
(fn [event]
@ -79,25 +95,25 @@
on-drag-enter
(mf/use-callback
(mf/deps selected-project)
(fn [e]
(when (dnd/has-type? e "penpot/files")
(dom/prevent-default e)
(when-not (dnd/from-child? e)
(when (not= selected-project (:id item))
(swap! local assoc :dragging? true))))))
(mf/deps selected-project)
(fn [e]
(when (dnd/has-type? e "penpot/files")
(dom/prevent-default e)
(when-not (dnd/from-child? e)
(when (not= selected-project (:id item))
(swap! local assoc :dragging? true))))))
on-drag-over
(mf/use-callback
(fn [e]
(when (dnd/has-type? e "penpot/files")
(dom/prevent-default e))))
(fn [e]
(when (dnd/has-type? e "penpot/files")
(dom/prevent-default e))))
on-drag-leave
(mf/use-callback
(fn [e]
(when-not (dnd/from-child? e)
(swap! local assoc :dragging? false))))
(fn [e]
(when-not (dnd/from-child? e)
(swap! local assoc :dragging? false))))
on-drop-success
(mf/use-callback
@ -117,9 +133,11 @@
(st/emit! (dd/move-files (with-meta data mdata)))))))]
[:*
[:li {:class (if selected? "current"
(when (:dragging? @local) "dragging"))
[:li {:tab-index "0"
:class (if selected? "current"
(when (:dragging? @local) "dragging"))
:on-click on-click
:on-key-down on-key-down
:on-double-click on-edit-open
:on-context-menu on-menu-click
:on-drag-enter on-drag-enter
@ -143,15 +161,6 @@
focused? (mf/use-state false)
emit! (mf/use-memo #(f/debounce st/emit! 500))
on-search-focus
(mf/use-callback
(mf/deps team-id)
(fn [event]
(reset! focused? true)
(let [value (dom/get-target-val event)]
(dom/select-text! (dom/get-target event))
(emit! (dd/go-to-search value)))))
on-search-blur
(mf/use-callback
(fn [_]
@ -167,16 +176,25 @@
on-clear-click
(mf/use-callback
(mf/deps team-id)
(fn [_]
(fn [e]
(let [search-input (dom/get-element "search-input")]
(dom/clean-value! search-input)
(dom/focus! search-input)
(emit! (dd/go-to-search)))))
(emit! (dd/go-to-search))
(dom/prevent-default e)
(dom/stop-propagation e))))
on-key-press
(mf/use-callback
(fn [e]
(when (kbd/enter? e)
(ts/schedule-on-idle
(fn []
(let [search-title (dom/get-element (str "dashboard-search-title"))]
(when search-title
(dom/set-attribute! search-title "tabindex" "0")
(dom/focus! search-title)
(dom/set-attribute! search-title "tabindex" "-1")))))
(dom/prevent-default e)
(dom/stop-propagation e))))]
@ -185,10 +203,11 @@
{:key "images-search-box"
:id "search-input"
:type "text"
:aria-label (tr "dashboard.search-placeholder")
:placeholder (tr "dashboard.search-placeholder")
:default-value search-term
:auto-complete "off"
:on-focus on-search-focus
;; :on-focus on-search-focus
:on-blur on-search-blur
:on-change on-search-change
:on-key-press on-key-press
@ -196,18 +215,20 @@
(if (or @focused? (seq search-term))
[:div.clear-search
{:on-click on-clear-click}
{:tab-index "0"
:on-click on-clear-click
:on-key-down (fn [event]
(when (kbd/enter? event)
(on-clear-click event)))}
i/close]
[:div.search
{:on-click on-clear-click}
i/search])]))
(mf/defc teams-selector-dropdown
[{:keys [team profile] :as props}]
(let [teams (mf/deref refs/teams)
on-create-clicked
(mf/defc teams-selector-dropdown-items
[{:keys [team profile teams] :as props}]
(let [on-create-clicked
(mf/use-callback
#(st/emit! (modal/show :team-form {})))
@ -216,24 +237,40 @@
(fn [team-id]
(st/emit! (dd/go-to-projects team-id))))]
[:ul.dropdown.teams-dropdown
[:li.team-name {:on-click (partial team-selected (:default-team-id profile))}
[:*
[:& dropdown-menu-item {:on-click (partial team-selected (:default-team-id profile))
:on-key-down (fn [event]
(when (kbd/enter? event)
(team-selected (:default-team-id profile) event)))
:id "teams-selector-default-team"
:klass "team-name"}
[:span.team-icon i/logo-icon]
[:span.team-text (tr "dashboard.your-penpot")]
(when (= (:default-team-id profile) (:id team))
[:span.icon i/tick])]
(for [team-item (remove :is-default (vals teams))]
[:li.team-name {:on-click (partial team-selected (:id team-item))
:key (dm/str (:id team-item))}
[:& dropdown-menu-item {:on-click (partial team-selected (:id team-item))
:on-key-down (fn [event]
(when (kbd/enter? event)
(team-selected (:id team-item) event)))
:id (str "teams-selector-" (:id team-item))
:klass "team-name"
:key (dm/str (:id team-item))}
[:span.team-icon
[:img {:src (cf/resolve-team-photo-url team-item)
:alt (:name team-item)}]]
[:span.team-text {:title (:name team-item)} (:name team-item)]
(when (= (:id team-item) (:id team))
[:span.icon i/tick])])
[:hr]
[:li.team-name.action {:on-click on-create-clicked :data-test "create-new-team"}
[:hr {:role "separator"}]
[:& dropdown-menu-item {:on-click on-create-clicked
:on-key-down (fn [event]
(when (kbd/enter? event)
(on-create-clicked event)))
:id "teams-selector-create-team"
:klass "team-name action"
:key "teams-selector-create-team"}
[:span.team-icon.new-team i/close]
[:span.team-text (tr "dashboard.create-new-team")]]]))
@ -288,11 +325,11 @@
on-leave-clicked
#(st/emit! (modal/show
{:type :confirm
:title (tr "modals.leave-confirm.title")
:message (tr "modals.leave-confirm.message")
:accept-label (tr "modals.leave-confirm.accept")
:on-accept leave-fn}))
{:type :confirm
:title (tr "modals.leave-confirm.title")
:message (tr "modals.leave-confirm.message")
:accept-label (tr "modals.leave-confirm.accept")
:on-accept leave-fn}))
on-leave-as-owner-clicked
(fn []
@ -305,55 +342,145 @@
leave-and-close
#(st/emit! (modal/show
{:type :confirm
:title (tr "modals.leave-confirm.title")
:message (tr "modals.leave-and-close-confirm.message" (:name team))
:scd-message (tr "modals.leave-and-close-confirm.hint")
:accept-label (tr "modals.leave-confirm.accept")
:on-accept delete-fn}))
{:type :confirm
:title (tr "modals.leave-confirm.title")
:message (tr "modals.leave-and-close-confirm.message" (:name team))
:scd-message (tr "modals.leave-and-close-confirm.hint")
:accept-label (tr "modals.leave-confirm.accept")
:on-accept delete-fn}))
on-delete-clicked
#(st/emit!
(modal/show
{:type :confirm
:title (tr "modals.delete-team-confirm.title")
:message (tr "modals.delete-team-confirm.message")
:accept-label (tr "modals.delete-team-confirm.accept")
:on-accept delete-fn}))]
(modal/show
{:type :confirm
:title (tr "modals.delete-team-confirm.title")
:message (tr "modals.delete-team-confirm.message")
:accept-label (tr "modals.delete-team-confirm.accept")
:on-accept delete-fn}))]
[:*
[:& dropdown-menu-item {:on-click go-members
:on-key-down (fn [event]
(when (kbd/enter? event)
(go-members)))
:id "teams-options-members"
:key "teams-options-members"
:data-test "team-members"}
(tr "labels.members")]
[:& dropdown-menu-item {:on-click go-invitations
:on-key-down (fn [event]
(when (kbd/enter? event)
(go-invitations)))
:id "teams-options-invitations"
:key "teams-options-invitations"
:data-test "team-invitations"}
(tr "labels.invitations")]
[:ul.dropdown.options-dropdown
[:li {:on-click go-members :data-test "team-members"} (tr "labels.members")]
[:li {:on-click go-invitations :data-test "team-invitations"} (tr "labels.invitations")]
(when (contains? @cf/flags :webhooks)
[:li {:on-click go-webhooks :data-test "team-webhooks"} (tr "labels.webhooks")])
[:li {:on-click go-settings :data-test "team-settings"} (tr "labels.settings")]
[:& dropdown-menu-item {:on-click go-webhooks
:on-key-down (fn [event]
(when (kbd/enter? event)
(go-webhooks)))
:id "teams-options-webhooks"
:key "teams-options-webhooks"}
(tr "labels.webhooks")])
[:& dropdown-menu-item {:on-click go-settings
:on-key-down (fn [event]
(when (kbd/enter? event)
(go-settings)))
:id "teams-options-settings"
:key "teams-options-settings"
:data-test "team-settings"}
(tr "labels.settings")]
[:hr]
(when can-rename?
[:li {:on-click on-rename-clicked :data-test "rename-team"} (tr "labels.rename")])
[:& dropdown-menu-item {:on-click on-rename-clicked
:on-key-down (fn [event]
(when (kbd/enter? event)
(on-rename-clicked)))
:id "teams-options-rename"
:key "teams-options-rename"
:data-test "rename-team"}
(tr "labels.rename")])
(cond
(= (count members) 1)
[:li {:on-click leave-and-close} (tr "dashboard.leave-team")]
[:& dropdown-menu-item {:on-click leave-and-close
:on-key-down (fn [event]
(when (kbd/enter? event)
(leave-and-close)))
:id "teams-options-leave-team"
:key "teams-options-leave-team"}
(tr "dashboard.leave-team")]
(get-in team [:permissions :is-owner])
[:li {:on-click on-leave-as-owner-clicked :data-test "leave-team"} (tr "dashboard.leave-team")]
[:& dropdown-menu-item {:on-click on-leave-as-owner-clicked
:on-key-down (fn [event]
(when (kbd/enter? event)
(on-leave-as-owner-clicked)))
:id "teams-options-leave-team"
:key "teams-options-leave-team"
:data-test "leave-team"}
(tr "dashboard.leave-team")]
(> (count members) 1)
[:li {:on-click on-leave-clicked} (tr "dashboard.leave-team")])
[:& dropdown-menu-item {:on-click on-leave-clicked
:on-key-down (fn [event]
(when (kbd/enter? event)
(on-leave-clicked)))
:id "teams-options-leave-team"
:key "teams-options-leave-team"}
(tr "dashboard.leave-team")])
(when (get-in team [:permissions :is-owner])
[:li.warning {:on-click on-delete-clicked :data-test "delete-team"} (tr "dashboard.delete-team")])]))
[:& dropdown-menu-item {:on-click on-delete-clicked
:on-key-down (fn [event]
(when (kbd/enter? event)
(on-delete-clicked)))
:id "teams-options-delete-team"
:key "teams-options-delete-team"
:klass "warning"
:data-test "delete-team"}
(tr "dashboard.delete-team")])]))
(mf/defc sidebar-team-switch
[{:keys [team profile] :as props}]
(let [show-team-opts-ddwn? (mf/use-state false)
show-teams-ddwn? (mf/use-state false)]
(let [teams (mf/deref refs/teams)
teams-without-default (into {} (filter (fn [[_ v]] (= false (:is-default v))) teams))
team-ids (map #(str "teams-selector-" %) (keys teams-without-default))
ids (concat ["teams-selector-default-team"] team-ids ["teams-selector-create-team"])
show-team-opts-ddwn? (mf/use-state false)
show-teams-ddwn? (mf/use-state false)
can-rename? (or (get-in team [:permissions :is-owner]) (get-in team [:permissions :is-admin]))
options-ids ["teams-options-members"
"teams-options-invitations"
(when (contains? @cf/flags :webhooks)
"teams-options-webhooks")
"teams-options-settings"
(when can-rename?
"teams-options-rename")
"teams-options-leave-team"
(when (get-in team [:permissions :is-owner])
"teams-options-delete-team")]]
[:div.sidebar-team-switch
[:div.switch-content
[:div.current-team {:on-click #(reset! show-teams-ddwn? true)}
[:button.current-team {:tab-index "0"
:on-click #(reset! show-teams-ddwn? true)
:on-key-down (fn [event]
(when (or (kbd/space? event) (kbd/enter? event))
(dom/prevent-default event)
(reset! show-teams-ddwn? true)
(ts/schedule-on-idle
(fn []
(let [first-element (dom/get-element (first ids))]
(when first-element
(dom/focus! first-element)))))))}
(if (:is-default team)
[:div.team-name
[:span.team-icon i/logo-icon]
@ -368,17 +495,33 @@
i/arrow-down]]
(when-not (:is-default team)
[:div.switch-options {:on-click #(reset! show-team-opts-ddwn? true)}
[:button.switch-options {:on-click #(reset! show-team-opts-ddwn? true)
:tab-index "0"
:on-key-down (fn [event]
(when (or (kbd/space? event) (kbd/enter? event))
(dom/prevent-default event)
(reset! show-team-opts-ddwn? true)
(ts/schedule-on-idle
(fn []
(let [first-element (dom/get-element (first options-ids))]
(when first-element
(dom/focus! first-element)))))))}
i/actions])]
;; Teams Dropdown
[:& dropdown {:show @show-teams-ddwn?
:on-close #(reset! show-teams-ddwn? false)}
[:& teams-selector-dropdown {:team team
:profile profile}]]
[:& dropdown-menu {:show @show-teams-ddwn?
:on-close #(reset! show-teams-ddwn? false)
:ids ids
:list-class "dropdown teams-dropdown"}
[:& teams-selector-dropdown-items {:ids ids
:team team
:profile profile
:teams teams}]]
[:& dropdown {:show @show-team-opts-ddwn?
:on-close #(reset! show-team-opts-ddwn? false)}
[:& dropdown-menu {:show @show-team-opts-ddwn?
:on-close #(reset! show-team-opts-ddwn? false)
:ids options-ids
:list-class "dropdown options-dropdown"}
[:& team-options-dropdown {:team team
:profile profile}]]]))
@ -400,11 +543,34 @@
(mf/deps team)
#(st/emit! (rt/nav :dashboard-projects {:team-id (:id team)})))
go-projects-with-key
(mf/use-callback
(mf/deps team)
#(st/emit! (rt/nav :dashboard-projects {:team-id (:id team)})
(ts/schedule-on-idle
(fn []
(let [projects-title (dom/get-element "dashboard-projects-title")]
(when projects-title
(dom/set-attribute! projects-title "tabindex" "0")
(dom/focus! projects-title)
(dom/set-attribute! projects-title "tabindex" "-1")))))))
go-fonts
(mf/use-callback
(mf/deps team)
#(st/emit! (rt/nav :dashboard-fonts {:team-id (:id team)})))
go-fonts-with-key
(mf/use-callback
(mf/deps team)
#(st/emit! (rt/nav :dashboard-fonts {:team-id (:id team)})
(ts/schedule-on-idle
(fn []
(let [font-title (dom/get-element "dashboard-fonts-title")]
(when font-title
(dom/set-attribute! font-title "tabindex" "0")
(dom/focus! font-title)
(dom/set-attribute! font-title "tabindex" "-1")))))))
go-drafts
(mf/use-callback
(mf/deps team default-project-id)
@ -412,11 +578,36 @@
(st/emit! (rt/nav :dashboard-files
{:team-id (:id team)
:project-id default-project-id}))))
go-drafts-with-key
(mf/use-callback
(mf/deps team default-project-id)
#(st/emit! (rt/nav :dashboard-files {:team-id (:id team)
:project-id default-project-id})
(ts/schedule-on-idle
(fn []
(let [drafts-title (dom/get-element "dashboard-drafts-title")]
(when drafts-title
(dom/set-attribute! drafts-title "tabindex" "0")
(dom/focus! drafts-title)
(dom/set-attribute! drafts-title "tabindex" "-1")))))))
go-libs
(mf/use-callback
(mf/deps team)
#(st/emit! (rt/nav :dashboard-libraries {:team-id (:id team)})))
go-libs-with-key
(mf/use-callback
(mf/deps team)
#(st/emit! (rt/nav :dashboard-libraries {:team-id (:id team)})
(ts/schedule-on-idle
(fn []
(let [libs-title (dom/get-element "dashboard-libraries-title")]
(when libs-title
(dom/set-attribute! libs-title "tabindex" "0")
(dom/focus! libs-title)
(dom/set-attribute! libs-title "tabindex" "-1")))))))
pinned-projects
(->> (vals projects)
(remove :is-default)
@ -430,28 +621,32 @@
[:div.sidebar-content-section
[:ul.sidebar-nav.no-overflow
[:li.recent-projects
{:on-click go-projects
:class-name (when projects? "current")}
[:span.element-title (tr "labels.projects")]]
{:class-name (when projects? "current")}
[:& link {:action go-projects
:keyboard-action go-projects-with-key}
[:span.element-title (tr "labels.projects")]]]
[:li {:on-click go-drafts
:class-name (when drafts? "current")}
[:span.element-title (tr "labels.drafts")]]
[:li {:class-name (when drafts? "current")}
[:& link {:action go-drafts
:keyboard-action go-drafts-with-key}
[:span.element-title (tr "labels.drafts")]]]
[:li {:on-click go-libs
:class-name (when libs? "current")}
[:span.element-title (tr "labels.shared-libraries")]]]]
[:li {:class-name (when libs? "current")}
[:& link {:action go-libs
:keyboard-action go-libs-with-key}
[:span.element-title (tr "labels.shared-libraries")]]]]]
[:hr]
[:div.sidebar-content-section
[:ul.sidebar-nav.no-overflow
[:li
{:on-click go-fonts
:data-test "fonts"
:class-name (when fonts? "current")}
[:span.element-title (tr "labels.fonts")]]]]
[:li {:class-name (when fonts? "current")}
[:& link {:action go-fonts
:keyboard-action go-fonts-with-key
:data-test "fonts"}
[:span.element-title (tr "labels.fonts")]]]]]
[:hr]
[:div.sidebar-content-section {:data-test "pinned-projects"}
@ -492,42 +687,106 @@
(st/emit! (modal/show {:type :release-notes :version version}))))))]
[:div.profile-section
[:div.profile {:on-click #(reset! show true)
[:div.profile {:tab-index "0"
:on-click #(reset! show true)
:on-key-down (fn [event]
(when (kbd/enter? event)
(reset! show true)))
:data-test "profile-btn"}
[:img {:src photo
:alt (:fullname profile)}]
[:span (:fullname profile)]]
[:& dropdown {:on-close #(reset! show false)
:show @show}
[:& dropdown-menu {:on-close #(reset! show false)
:show @show}
[:ul.dropdown
[:li {:on-click (partial on-click :settings-profile)
[:li {:tab-index (if show
"0"
"-1")
:on-click (partial on-click :settings-profile)
:on-key-down (fn [event]
(when (kbd/enter? event)
(on-click :settings-profile event)))
:data-test "profile-profile-opt"}
[:span.text (tr "labels.your-account")]]
[:li.separator {:on-click #(dom/open-new-window "https://help.penpot.app")
[:li.separator {:tab-index (if show
"0"
"-1")
:on-click #(dom/open-new-window "https://help.penpot.app")
:on-key-down (fn [event]
(when (kbd/enter? event)
(dom/open-new-window "https://help.penpot.app")))
:data-test "help-center-profile-opt"}
[:span.text (tr "labels.help-center")]]
[:li {:on-click #(dom/open-new-window "https://community.penpot.app")}
[:li {:tab-index (if show
"0"
"-1")
:on-click #(dom/open-new-window "https://community.penpot.app")
:on-key-down (fn [event]
(when (kbd/enter? event)
(dom/open-new-window "https://community.penpot.app")))}
[:span.text (tr "labels.community")]]
[:li {:on-click #(dom/open-new-window "https://www.youtube.com/c/Penpot")}
[:li {:tab-index (if show
"0"
"-1")
:on-click #(dom/open-new-window "https://www.youtube.com/c/Penpot")
:on-key-down (fn [event]
(when (kbd/enter? event)
(dom/open-new-window "https://www.youtube.com/c/Penpot")))}
[:span.text (tr "labels.tutorials")]]
[:li {:on-click show-release-notes}
[:li {:tab-index (if show
"0"
"-1")
:on-click show-release-notes
:on-key-down (fn [event]
(when (kbd/enter? event)
(show-release-notes)))}
[:span (tr "labels.release-notes")]]
[:li.separator {:on-click #(dom/open-new-window "https://penpot.app/libraries-templates.html")
[:li.separator {:tab-index (if show
"0"
"-1")
:on-click #(dom/open-new-window "https://penpot.app/libraries-templates.html")
:on-key-down (fn [event]
(when (kbd/enter? event)
(dom/open-new-window "https://penpot.app/libraries-templates.html")))
:data-test "libraries-templates-profile-opt"}
[:span.text (tr "labels.libraries-and-templates")]]
[:li {:on-click #(dom/open-new-window "https://github.com/penpot/penpot")}
[:li {:tab-index (if show
"0"
"-1")
:on-click #(dom/open-new-window "https://github.com/penpot/penpot")
:on-key-down (fn [event]
(when (kbd/enter? event)
(dom/open-new-window "https://github.com/penpot/penpot")))}
[:span (tr "labels.github-repo")]]
[:li {:on-click #(dom/open-new-window "https://penpot.app/terms.html")}
[:li {:tab-index (if show
"0"
"-1")
:on-click #(dom/open-new-window "https://penpot.app/terms.html")
:on-key-down (fn [event]
(when (kbd/enter? event)
(dom/open-new-window "https://penpot.app/terms.html")))}
[:span (tr "auth.terms-of-service")]]
(when (contains? @cf/flags :user-feedback)
[:li.separator {:on-click (partial on-click :settings-feedback)
[:li.separator {:tab-index (if show
"0"
"-1")
:on-click (partial on-click :settings-feedback)
:on-key-down (fn [event]
(when (kbd/enter? event)
(on-click :settings-feedback event)))
:data-test "feedback-profile-opt"}
[:span.text (tr "labels.give-feedback")]])
[:li.separator {:on-click #(on-click (du/logout) %)
[:li.separator {:tab-index (if show
"0"
"-1")
:on-click #(on-click (du/logout) %)
:on-key-down (fn [event]
(when (kbd/enter? event)
(on-click (du/logout) event)))
:data-test "logout-profile-opt"}
[:span.icon i/exit]
[:span.text (tr "labels.logout")]]]]
@ -542,9 +801,8 @@
[props]
(let [team (obj/get props "team")
profile (obj/get props "profile")]
[:div.dashboard-sidebar
[:div.sidebar-inside
[:> sidebar-content props]
[:& profile-section
{:profile profile
:team team}]]]))
[:nav.dashboard-sidebar
[:> sidebar-content props]
[:& profile-section
{:profile profile
:team team}]]))

View file

@ -101,8 +101,7 @@
{::mf/wrap [mf/memo]}
[{:keys [profile locale section]}]
[:div.dashboard-sidebar.settings
[:div.sidebar-inside
[:& sidebar-content {:profile profile
:section section}]
[:& profile-section {:profile profile
:locale locale}]]])
[:& sidebar-content {:profile profile
:section section}]
[:& profile-section {:profile profile
:locale locale}]])

View file

@ -40,11 +40,15 @@
(def space? (is-key? " "))
(def up-arrow? (is-key? "ArrowUp"))
(def down-arrow? (is-key? "ArrowDown"))
(def left-arrow? (is-key? "ArrowLeft"))
(def right-arrow? (is-key? "ArrowRight"))
(def alt-key? (is-key? "Alt"))
(def ctrl-key? (is-key? "Control"))
(def meta-key? (is-key? "Meta"))
(def comma? (is-key? ","))
(def backspace? (is-key? "Backspace"))
(def home? (is-key? "Home"))
(def tab? (is-key? "Tab"))
(defn editing? [e]
(.-editing ^js e))