diff --git a/shadow-cljs.edn b/shadow-cljs.edn
index 739148cfcd..bffefb44b8 100644
--- a/shadow-cljs.edn
+++ b/shadow-cljs.edn
@@ -10,10 +10,6 @@
:output-dir "resources/public/js/compiled"
:asset-path "js/compiled"
:modules {:app {:init-fn athens.core/init}}
- ;; Don't try to polyfill for generators, we don't try to support older browsers
- ;; and it breaks some libraries we use (ForceGraph2D) when other imports change.
- ;; https://github.com/thheller/shadow-cljs/issues/854
- :js-options {:babel-preset-config {:targets {:chrome 80}}}
:compiler-options {:closure-warnings {:global-this :off}
:infer-externs :auto
:closure-defines {re-frame.trace.trace-enabled? true}
@@ -31,7 +27,6 @@
:output-dir "resources/public/js/compiled"
:asset-path "js/compiled"
:modules {:renderer {:init-fn athens.core/init}}
- :js-options {:babel-preset-config {:targets {:chrome 80}}}
:compiler-options {:closure-warnings {:global-this :off}
:infer-externs :auto
:closure-defines {re-frame.trace.trace-enabled? true}
@@ -50,7 +45,6 @@
:main {:target :node-script
:output-to "resources/main.js"
:main athens.main.core/main
- :js-options {:babel-preset-config {:targets {:chrome 80}}}
:compiler-options {:output-feature-set :es-next
:reader-features #{:electron}}}
diff --git a/src/cljs/athens/components.cljs b/src/cljs/athens/components.cljs
index e16f8854f9..4fb05b60aa 100644
--- a/src/cljs/athens/components.cljs
+++ b/src/cljs/athens/components.cljs
@@ -1,15 +1,16 @@
(ns athens.components
(:require
- ["@chakra-ui/react" :refer [Checkbox Box Button]]
["@material-ui/icons/Edit" :default Edit]
[athens.db :as db]
[athens.parse-renderer :refer [component]]
[athens.reactive :as reactive]
+ [athens.style :refer [color]]
[athens.util :refer [recursively-modify-block-for-embed]]
[athens.views.blocks.core :as blocks]
[clojure.string :as str]
[re-frame.core :as rf :refer [dispatch subscribe]]
- [reagent.core :as r]))
+ [reagent.core :as r]
+ [stylefy.core :as stylefy :refer [use-style]]))
(defn todo-on-click
@@ -29,27 +30,25 @@
TODO() - might be a good idea to keep an edit icon at top right
for every component."
[children]
- [:span {:style {:display "contents"}
- :on-click (fn [e] (.. e stopPropagation))}
+ [:span {:on-click (fn [e]
+ (.. e stopPropagation))}
children])
(defmethod component :todo
[_content uid]
[span-click-stop
- [:> Checkbox {:isChecked false
- :verticalAlign "middle"
- :transform "translateY(-2px)"
- :onChange #(todo-on-click uid #"\{\{\[\[TODO\]\]\}\}" "{{[[DONE]]}}")}]])
+ [:input {:type "checkbox"
+ :checked false
+ :on-change #(todo-on-click uid #"\{\{\[\[TODO\]\]\}\}" "{{[[DONE]]}}")}]])
(defmethod component :done
[_content uid]
[span-click-stop
- [:> Checkbox {:isChecked true
- :verticalAlign "middle"
- :transform "translateY(-2px)"
- :onChange #(todo-on-click uid #"\{\{\[\[DONE\]\]\}\}" "{{[[TODO]]}}")}]])
+ [:input {:type "checkbox"
+ :checked true
+ :on-change #(todo-on-click uid #"\{\{\[\[DONE\]\]\}\}" "{{[[TODO]]}}")}]])
(defmethod component :youtube
@@ -70,11 +69,25 @@
(defmethod component :self
[content _uid]
[span-click-stop
- [:> Button {:variant "link"
- :color "red"}
+ [:button {:style {:color "red"
+ :font-family "IBM Plex Mono"}}
content]])
+(def block-embed-adjustments
+ {:background (color :background-minus-2 :opacity-med)
+ :position "relative"
+ ::stylefy/manual [[:>.block-container {:margin-left "0"
+ :padding-right "1.3rem"
+ ::stylefy/manual [[:textarea {:background "transparent"}]]}]
+ [:>svg {:position "absolute"
+ :right "5px"
+ :top "5px"
+ :font-size "1rem"
+ :z-index "5"
+ :cursor "pointer"}]]})
+
+
(defmethod component :block-embed
[content uid]
;; bindings are eval only once in with-let
@@ -84,18 +97,7 @@
;; todo -- not reactive. some cases where delete then ctrl-z doesn't work
(if (db/e-by-av :block/uid block-uid)
(r/with-let [embed-id (random-uuid)]
- [:> Box {:class "block-embed"
- :bg "background.basement"
- :position "relative"
- :sx {"> .block-container" {:ml 0
- :pr "1.3rem"
- "textarea" {:background "transparent"}}
- "> svg" {:position "absolute"
- :right "5px"
- :top "5px"
- :fontSize "1rem"
- :zIndex "5"
- :cursor "pointer"}}}
+ [:div.block-embed (use-style block-embed-adjustments)
(let [block (reactive/get-reactive-block-document [:block/uid block-uid])]
[:<>
[blocks/block-el
diff --git a/src/cljs/athens/effects.cljs b/src/cljs/athens/effects.cljs
index 2ea70ec0d4..807e88c362 100644
--- a/src/cljs/athens/effects.cljs
+++ b/src/cljs/athens/effects.cljs
@@ -17,7 +17,8 @@
[goog.dom.selection :refer [setCursorPosition]]
[malli.core :as m]
[malli.error :as me]
- [re-frame.core :as rf]))
+ [re-frame.core :as rf]
+ [stylefy.core :as stylefy]))
;; Effects
@@ -132,6 +133,12 @@
100)))
+(rf/reg-fx
+ :stylefy/tag
+ (fn [[tag properties]]
+ (stylefy/tag tag properties)))
+
+
(rf/reg-fx
:alert/js!
(fn [message]
diff --git a/src/cljs/athens/electron/db_menu/core.cljs b/src/cljs/athens/electron/db_menu/core.cljs
index 23fd9ad777..e2b352feb4 100644
--- a/src/cljs/athens/electron/db_menu/core.cljs
+++ b/src/cljs/athens/electron/db_menu/core.cljs
@@ -1,84 +1,124 @@
(ns athens.electron.db-menu.core
(:require
- ["@chakra-ui/react" :refer [Box IconButton Tooltip Heading VStack ButtonGroup PopoverTrigger ButtonGroup Popover PopoverContent Portal Button]]
- ["react-focus-lock" :default FocusLock]
+ ["/components/Button/Button" :refer [Button]]
+ ["@material-ui/core/Popover" :as Popover]
+ ["@material-ui/icons/AddCircleOutline" :default AddCircleOutline]
[athens.electron.db-menu.db-icon :refer [db-icon]]
[athens.electron.db-menu.db-list-item :refer [db-list-item]]
- [athens.electron.db-modal :as db-modal]
[athens.electron.dialogs :as dialogs]
+ [athens.style :refer [color DEPTH-SHADOWS]]
+ [athens.views.dropdown :refer [menu-style menu-separator-style]]
[re-frame.core :refer [dispatch subscribe]]
- [reagent.core :as r]))
+ [reagent.core :as r]
+ [stylefy.core :as stylefy :refer [use-style]]))
+
+
+;; -------------------------------------------------------------------
+;; --- material ui ---
+
+(def m-popover (r/adapt-react-class (.-default Popover)))
+
+
+;; Style
+
+(def dropdown-style
+ {::stylefy/manual [[:.menu {:background (color :background-plus-2)
+ :color (color :body-text-color)
+ :border-radius "calc(0.25rem + 0.25rem)" ; Button corner radius + container padding makes "concentric" container radius
+ :padding "0.25rem"
+ :display "inline-flex"
+ :box-shadow [[(:64 DEPTH-SHADOWS) ", 0 0 0 1px rgba(0, 0, 0, 0.05)"]]}]]})
+
+
+(def db-menu-button-style
+ {:color (color :body-text-color :opacity-high)
+ :background "inherit"
+ :padding "0"
+ :align-items "stretch"
+ :justify-content "stretch"
+ :justify-items "stretch"
+ :width "1.75em"
+ :height "1.75em"
+ :border "1px solid transparent"})
+
+
+(def current-db-area-style
+ {:background "rgba(144, 144, 144, 0.05)"
+ :margin "-0.25rem -0.25rem 0.125rem"
+ :border-bottom [["1px solid " (color :border-color)]]
+ :padding "0.25rem"})
+
+
+(def current-db-tools-style
+ {:margin-left "2rem"})
;; Components
(defn current-db-tools
- ([{:keys [db]} all-dbs merge-open?]
- (when-not (:is-remote db)
- [:> ButtonGroup {:size "xs" :pr 4 :pl 10 :ml "auto" :width "100%"}
- [:> Button {:onClick #(dialogs/move-dialog!)} "Move"]
- [:> Button {:mr "auto" :onClick #(reset! merge-open? true)} "Merge from Roam"]
- [:> Tooltip {:label "Can't remove last database" :placement "right" :isDisabled (< 1 (count all-dbs))}
- [:> Button {:isDisabled (= 1 (count all-dbs))
- :onClick #(dialogs/delete-dialog! db)}
- "Remove"]]])))
+ ([{:keys [db]} all-dbs]
+ [:div (use-style current-db-tools-style)
+ (if (:is-remote db)
+ [:<>
+ [:> Button "Import"]
+ [:> Button "Copy Link"]
+ [:> Button "Remove"]]
+ [:<>
+ [:> Button {:onClick #(dialogs/move-dialog!)} "Move"]
+ ;; [:> Button {:onClick "Rename"]
+ [:> Button {:onClick #(if (= 1 (count all-dbs))
+ (js/alert "Can't remove last db from the list")
+ (dialogs/delete-dialog! db))}
+ "Delete"]])]))
(defn db-menu
[]
- (let [all-dbs @(subscribe [:db-picker/all-dbs])
- merge-open? (r/atom false)
- active-db @(subscribe [:db-picker/selected-db])
- inactive-dbs (dissoc all-dbs (:id active-db))
- sync-status (if @(subscribe [:db/synced])
- :running
- :synchronising)]
- [:<>
- [db-modal/merge-modal merge-open?]
- [:> Popover {:placement "bottom-start"}
- [:> PopoverTrigger
- [:> IconButton {:p 0
- :bg "background.floor"}
- ;; DB Icon + Dropdown toggle
- [db-icon {:db active-db
- :status sync-status}]]]
- ;; Dropdown menu
- [:> Portal
- [:> PopoverContent {:overflow-y "auto"}
- [:> FocusLock
- [:> VStack {:align "stretch"
- :overflow "hidden"
- :spacing 0}
- ;; Show active DB first
- [:> Box {:bg "background.floor"
- :pb 4}
- [db-list-item {:db active-db
- :is-current true
- :key (:id active-db)}]
- [current-db-tools {:db active-db} all-dbs merge-open?]]
- ;; Show all inactive DBs and a separator
- [:> Heading {:fontSize "xs"
- :py 4
- :pb 3
- :borderTop "1px solid"
- :borderTopColor "separator.divider"
- ;; :bg "background.floor"
- :px 10
- :letterSpacing "wide"
- :textTransform "uppercase"
- :fontWeight "bold"
- :color "foreground.secondary"}
- "Other databases"]
- [:> VStack {:align "stretch"
- :spacing 0
- :overflow-y "auto"}
- {:align "stretch" :spacing 0}
- (doall
- (for [[key db] inactive-dbs]
- [db-list-item {:db db
- :is-current false
- :key key}]))]
- ;; Add DB control
- [:> ButtonGroup {:borderTop "1px solid" :borderTopColor "separator.divider" :p 2 :pt 0 :pl 10 :size "sm" :width "100%" :ml 10 :justifyContent "flex-start"}
- [:> Button {:onClick #(dispatch [:modal/toggle])}
- "Add Database"]]]]]]]]))
+ (r/with-let [ele (r/atom nil)]
+ (let [all-dbs @(subscribe [:db-picker/all-dbs])
+ active-db @(subscribe [:db-picker/selected-db])
+ inactive-dbs (dissoc all-dbs (:id active-db))
+ sync-status (if @(subscribe [:db/synced])
+ :running
+ :synchronising)]
+ [:<>
+ ;; DB Icon + Dropdown toggle
+ [:> Button {:class [(when @ele "is-active")]
+ :on-click #(reset! ele (.-currentTarget %))
+ :style db-menu-button-style}
+ [db-icon {:db active-db
+ :status sync-status}]]
+ ;; Dropdown menu
+ [m-popover
+ (merge (use-style dropdown-style)
+ {:style {:font-size "14px"}
+ :open (boolean @ele)
+ :anchorEl @ele
+ :onClose #(reset! ele nil)
+ :anchorOrigin #js{:vertical "bottom"
+ :horizontal "left"}
+ :marginThreshold 10
+ :transformOrigin #js{:vertical "top"
+ :horizontal "left"}
+ :classes {:root "backdrop"
+ :paper "menu"}})
+ [:div (use-style (merge menu-style
+ {:overflow "visible"}))
+ [:<>
+ ;; Show active DB first
+ [:div (use-style current-db-area-style)
+ [db-list-item {:db active-db
+ :is-current true
+ :key (:id active-db)}]
+ [current-db-tools {:db active-db} all-dbs]]
+ ;; Show all inactive DBs and a separator
+ (doall
+ (for [[key db] inactive-dbs]
+ [db-list-item {:db db
+ :is-current false
+ :key key}]))
+ [:hr (use-style menu-separator-style)]
+ ;; Add DB control
+ [:> Button {:on-click #(dispatch [:modal/toggle])}
+ [:> AddCircleOutline]
+ [:span "Add Database"]]]]]])))
diff --git a/src/cljs/athens/electron/db_menu/db_icon.cljs b/src/cljs/athens/electron/db_menu/db_icon.cljs
index 14e2e6a6fc..27fef1f938 100644
--- a/src/cljs/athens/electron/db_menu/db_icon.cljs
+++ b/src/cljs/athens/electron/db_menu/db_icon.cljs
@@ -1,31 +1,30 @@
(ns athens.electron.db-menu.db-icon
(:require
- ["@chakra-ui/react" :refer [Box]]
- [athens.electron.db-menu.status-indicator :refer [status-indicator]]))
+ [athens.electron.db-menu.status-indicator :refer [status-indicator]]
+ [stylefy.core :as stylefy :refer [use-style]]))
+
+
+(def db-icon-style
+ {:position "relative"
+ :width "1.75em"
+ :height "1.75em"
+ ::stylefy/manual [[:text {:font-size "16px"}]]})
(defn db-icon
[{:keys [db status]}]
- [:> Box {:class "icon"
- :position "relative"
- :flex "0 0 auto"
- :width "1.75em"
- :height "1.75em"
- :sx {"text" {:fontSize "16px"}}}
- [:> Box {:as "svg"
- :viewBox "0 0 24 24"
- :margin 0}
- [:> Box
- {:as "rect"
- :fill "var(--link-color)"
- :height "100%"
- :width "100%"
+ [:div.icon (use-style db-icon-style)
+ [:svg {:viewBox "0 0 24 24"
+ :style {:margin 0}}
+ [:rect
+ {:fill "var(--link-color)"
+ :height "24"
:rx "4"
+ :width "24"
:x "0"
:y "0"}]
- [:> Box
- {:as "text"
- :fill "white"
+ [:text
+ {:fill "white"
:fontSize "100%"
:fontWeight "bold"
:textAnchor "middle"
diff --git a/src/cljs/athens/electron/db_menu/db_list_item.cljs b/src/cljs/athens/electron/db_menu/db_list_item.cljs
index 723cb4e7a1..8b49b4dd36 100644
--- a/src/cljs/athens/electron/db_menu/db_list_item.cljs
+++ b/src/cljs/athens/electron/db_menu/db_list_item.cljs
@@ -1,103 +1,75 @@
(ns athens.electron.db-menu.db-list-item
(:require
- ["@chakra-ui/react" :refer [VStack Box Flex Text Button IconButton]]
["@material-ui/icons/Clear" :default Clear]
["@material-ui/icons/Link" :default Link]
[athens.electron.db-menu.db-icon :refer [db-icon]]
[athens.electron.dialogs :as dialogs]
- [re-frame.core :refer [dispatch]]))
+ [athens.style :refer [color]]
+ [re-frame.core :refer [dispatch]]
+ [stylefy.core :as stylefy :refer [use-style]]))
-(defn active-db
+(def db-list-item-style
+ {:display "flex"
+ ::stylefy/manual [[:.icon {:flex "0 0 1.75em"
+ :font-size "inherit"
+ :margin "0 0.5em 0 0"}]
+ [:.body {:display "flex"
+ :text-align "start"
+ :flex "1 1 100%"
+ :padding "0.5rem 0.25rem 0.5rem 0.5rem"
+ :border-radius "0.25rem"
+ :font-weight "normal"
+ :background "inherit"
+ :color "inherit"
+ :appearance "none"
+ :border "none"
+ :line-height "1.1"}
+ [:.MuiSvgIcon-root {:opacity "50%"}]
+ ["&:hover" {:filter "brightness(110%)"}]]
+ [:.is-current]
+ [:.label {:display "block"
+ :overflow "hidden"
+ :flex "1 1 100%"}]
+ [:span {:display "block"}]
+ [:.name {:font-weight "600"
+ :color "inherit"}]
+ [:.path {:color (color :body-text-color :opacity-med)
+ :max-width "100%"
+ :overflow "hidden"
+ :text-overflow "ellipsis"
+ :font-size "12px"
+ :white-space "nowrap"}
+ [:svg {:display "inline-block"
+ :font-size "inherit"
+ :position "relative"
+ :top "0.2em"
+ :margin "auto 0.25em auto 0"}]]]})
+
+
+(defn db-list-item-content
[{:keys [db]}]
- [:> Flex {:gap 2
- :p 2
- :borderRadius "none"
- :whiteSpace "nowrap"
- :height "auto"
- :align "stretch"
- :background "transparent"
- :justifyContent "stretch"
- :textAlign "left"}
+ [:<>
[db-icon {:db db}]
- [:> VStack {:align "stretch"
- :flex "1 1 100%"
- :overflow "hidden"
- :spacing 0
- :textOverflow "ellipsis"}
- [:> Text {:textOverflow "ellipsis"
- :overflow "hidden"
- :fontWeight "bold"}
- (:name db)]
- [:> Text {:textOverflow "ellipsis"
- :fontSize "sm"
- :color "foreground.secondary"
- :overflow "hidden"
- :title (:id db)}
+ [:div.label
+ [:span.name (:name db)]
+ [:span.path
+ {:title (:id db)}
(when (:is-remote db)
[:> Link])
(:id db)]]])
-(defn db-item
- [{:keys [db on-click on-remove]}]
- [:> Box {:display "grid"
- :borderTopWidth "1px"
- :borderTopStyle "solid"
- :borderTopColor "separator.divider"
- :gridTemplateAreas "'main'"}
- [:> Button {:onClick (when on-click on-click)
- :gridArea "main"
- :whiteSpace "nowrap"
- :bg "transparent"
- :isDisabled (not on-click)
- :display "flex"
- :gap 2
- :py 2
- :pr 10
- :borderRadius "none"
- :height "auto"
- :align "stretch"
- :justifyContent "stretch"
- :_focusVisible {:boxShadow "focusInset"}
- :textAlign "left"}
- [db-icon {:db db}]
- [:> VStack {:align "stretch"
- :flex "1 1 100%"
- :spacing 1
- :overflow "hidden"
- :textOverflow "ellipsis"}
- [:> Text {:textOverflow "ellipsis"
- :fontWeight "bold"
- :overflow "hidden"} (:name db)]
- [:> Text {:textOverflow "ellipsis"
- :size "sm"
- :color "foreground.secondary"
- :overflow "hidden"
- :title (:id db)}
- (when (:is-remote db)
- [:> Link])
- (:id db)]]]
- (when on-remove
- [:> IconButton
- {:onClick on-remove
- :gridArea "main"
- :alignSelf "center"
- :justifySelf "flex-end"
- :size "sm"
- :mr 2
- :bg "transparent"}
- [:> Clear]])])
-
-
(defn db-list-item
[{:keys [db is-current]}]
(let [remove-db-click-handler (fn [e]
(dialogs/delete-dialog! db)
(.. e stopPropagation))]
- (if is-current
- [active-db {:db db}]
- [db-item {:db db
- :on-click #(dispatch [:db-picker/select-db db])
- :on-remove remove-db-click-handler}])))
+ [:div (use-style db-list-item-style)
+ (if is-current
+ [:div.body.is-current
+ [db-list-item-content {:db db}]]
+ [:button.body.button {:onClick #(dispatch [:db-picker/select-db db])}
+ [db-list-item-content {:db db}]
+ [:> Clear {:on-click remove-db-click-handler}]])]))
diff --git a/src/cljs/athens/electron/db_menu/status_indicator.cljs b/src/cljs/athens/electron/db_menu/status_indicator.cljs
index cab8e5dc60..3aadd72188 100644
--- a/src/cljs/athens/electron/db_menu/status_indicator.cljs
+++ b/src/cljs/athens/electron/db_menu/status_indicator.cljs
@@ -1,37 +1,37 @@
(ns athens.electron.db-menu.status-indicator
(:require
- ["@chakra-ui/react" :refer [Box Tooltip]]
["@material-ui/icons/CheckCircle" :default CheckCircle]
["@material-ui/icons/Error" :default Error]
- ["@material-ui/icons/Sync" :default Sync]))
+ ["@material-ui/icons/Sync" :default Sync]
+ [athens.style :refer [color]]
+ [stylefy.core :as stylefy :refer [use-style]]))
+
+
+(def status-icon-style
+ {:background (color :background-minus-2)
+ :border-radius "100%"
+ :padding 0
+ :margin "0 !important"
+ :height "12px !important"
+ :width "12px !important"
+ :position "absolute"
+ :bottom "0"
+ :right "0"
+ ::stylefy/manual [[".:running"]]})
(defn status-indicator
[{:keys [status]}]
- [:> Box {:p 0
- :m 0
- :color (cond
- (:closed status) "error"
- (:running status) "foreground.primary"
- :else "foreground.secondary")
- :fontSize "1em"
- :height "1em"
- :width "1em"
- :transform "translate(25%, 25%)"
- :position "absolute"
- :bottom 0
- :right 0
- :borderRadius "full"
- :sx {"svg" {:fontSize "1em"
- :background "background.floor"
- :borderRadius "full"}}}
+ [:div.status-indicator (use-style status-icon-style
+ {:class (str status)})
(cond
- (= status :closed) [:> Tooltip
- {:label "Disconnected"}
- [:> Error]]
- (= status :running) [:> Tooltip
- {:label "Synced"}
- [:> CheckCircle]]
- :else [:> Tooltip
- {:label "Synchronizing..."}
- [:> Sync]])])
+ (= status :closed)
+ [:> Error (merge {:style {:color (color :error-color)}
+ :title "Disconnected"})]
+ (= status :running)
+ [:> CheckCircle (merge (use-style status-icon-style)
+ {:style {:color (color :confirmation-color)}
+ :title "Synced"})]
+ :else [:> Sync (merge (use-style status-icon-style)
+ {:style {:color (color :highlight-color)}
+ :title "Synchronizing..."})])])
diff --git a/src/cljs/athens/electron/db_modal.cljs b/src/cljs/athens/electron/db_modal.cljs
index 725bf6dc6c..237470f197 100644
--- a/src/cljs/athens/electron/db_modal.cljs
+++ b/src/cljs/athens/electron/db_modal.cljs
@@ -1,15 +1,79 @@
(ns athens.electron.db-modal
(:require
- ["@chakra-ui/react" :refer [HStack VStack FormControl FormLabel Input Button Box Tabs Tab TabList TabPanel TabPanels Text Modal ModalOverlay Divider VStack Heading ModalContent ModalHeader ModalFooter ModalBody ModalCloseButton ButtonGroup]]
+ ["/components/Button/Button" :refer [Button]]
+ ["@material-ui/icons/AddBox" :default AddBox]
+ ["@material-ui/icons/Close" :default Close]
+ ["@material-ui/icons/Folder" :default Folder]
+ ["@material-ui/icons/Group" :default Group]
+ ["@material-ui/icons/MergeType" :default MergeType]
+ ["@material-ui/icons/Storage" :default Storage]
+ ["react-dom" :as react-dom]
[athens.electron.dialogs :as dialogs]
[athens.electron.utils :as utils]
[athens.events :as events]
+ [athens.style :refer [color]]
[athens.subs]
[athens.util :refer [js-event->val]]
+ [athens.views.modal :refer [modal-style]]
+ [athens.views.textinput :as textinput]
[clojure.edn :as edn]
[datascript.core :as d]
+ [komponentit.modal :as modal]
[re-frame.core :refer [subscribe dispatch] :as rf]
- [reagent.core :as r]))
+ [reagent.core :as r]
+ [stylefy.core :as stylefy :refer [use-style]]))
+
+
+(def modal-contents-style
+ {:display "flex"
+ :padding "0 1rem 1.5rem 1rem"
+ :flex-direction "column"
+ :align-items "center"
+ :justify-content "flex-start"
+ :width "500px"
+ :height "17em"
+ ::stylefy/manual [[:p {:max-width "24rem"
+ :text-align "center"}]
+ [:button.toggle-button {:font-size "18px"
+ :align-self "flex-start"
+ :padding-left "0"
+ :margin-bottom "1rem"}]
+ [:code {:word-break "break-all"}]
+ [:.MuiTabs-indicator {:background-color "var(--link-color)"}]]})
+
+
+(def picker-style
+ {:display "grid"
+ :grid-auto-flow "column"
+ :grid-auto-columns "1fr"
+ :border-radius "0.5rem"
+ :flex "0 0 auto"
+ :font-size "1em"
+ :margin "0.25rem 0"
+ :align-self "stretch"
+ :overflow "hidden"
+ :transition "box-shadow 0.2s ease, filter 0.2s ease"
+ :background (color :background-color)
+ :padding "1px"
+ ::stylefy/manual [[:&:hover {}]
+ [:button {:text-align "center"
+ :appearance "none"
+ :border "0"
+ :border-radius "calc(0.5rem - 1px)"
+ :padding "0.5rem 0.5rem"
+ :color "inherit"
+ :display "flex"
+ :justify-content "center"
+ :align-items "center"
+ :position "relative"
+ :z-index "0"
+ :background "inherit"}
+ [:svg {:margin-inline-end "0.25em" :font-size "1.25em"}]
+ [:&:hover {:filter "contrast(105%)"}]
+ [:&:active {:filter "contrast(110%)"}]
+ [:&.active {:background (color :background-plus-2)
+ :z-index "5"
+ :box-shadow [["0 1px 5px" (color :shadow-color)]]}]]]})
(defn file-cb
@@ -42,150 +106,146 @@
transformed-roam-db (r/atom nil)
roam-db-filename (r/atom "")]
(fn []
- [:> Modal {:isOpen @open?
- :onClose close-modal
- :closeOnOverlayClick false
- :size "lg"}
- [:> ModalOverlay]
- [:> ModalContent
- [:> ModalHeader
- "Merge from Roam"]
- [:> ModalCloseButton]
- (if (nil? @transformed-roam-db)
- (let [inputRef (atom nil)]
- [:> ModalBody
- [:input {:ref #(reset! inputRef %)
- :style {:display "none"}
- :type "file"
- :accept ".edn"
- :on-change #(file-cb % transformed-roam-db roam-db-filename)}]
- [:> Heading {:size "md" :as "h2"} "How to merge from Roam"]
- [:> Box {:position "relative"
- :padding-bottom "56.25%"
- :margin "1rem 0 0"
- :borderRadius "8px"
- :overflow "hidden"
- :flex "1 1 100%"
- :width "100%"}
- [:iframe {:src "https://www.loom.com/embed/787ed48da52c4149b031efb8e17c0939?hide_owner=true&hide_share=true&hide_title=true&hideEmbedTopBar=true"
- :frameBorder "0"
- :webkitallowfullscreen "true"
- :mozallowfullscreen "true"
- :allowFullScreen true
- :style {:position "absolute"
- :top 0
- :left 0
- :width "100%"
- :height "100%"}}]]
- [:> ModalFooter
- [:> ButtonGroup
- [:> Button
- {:onClick #(.click @inputRef)}
- "Upload database"]]]])
- (let [roam-pages (roam-pages @transformed-roam-db)
- shared-pages (events/get-shared-pages @transformed-roam-db)]
- [:> ModalBody
- [:> Text {:size "md"} (str "Your Roam DB had " (count roam-pages)) " pages. " (count shared-pages) " of these pages were also found in your Athens DB. Press Merge to continue merging your DB."]
- [:> Divider {:my 4}]
- [:> Heading {:size "md" :as "h3"} "Shared Pages"]
- [:> VStack {:as "ol"
- :align "stretch"
- :maxHeight "400px"
- :overflowY "auto"}
- (for [x shared-pages]
- ^{:key x}
- [:li [:> Text (str "[[" x "]]")]])]
- [:> ModalFooter
- [:> ButtonGroup
- [:> Button {:onClick (fn []
- (dispatch [:upload/roam-edn @transformed-roam-db @roam-db-filename])
- (close-modal))}
-
- "Merge"]]]]))]])))
-
-
-(defn form-container
- [content footer]
- [:> Box {:as "form"
- :display "contents"}
- [:> Box {:p 5 :pt 4}
- content]
- [:> ModalFooter {:borderTop "1px solid"
- :borderColor "separator.divider"
- :p 2
- :pr 5} footer]])
+ [:div (use-style modal-style)
+ [modal/modal
+
+ {:title [:div.modal__title
+ [:> MergeType]
+ [:h4 "Merge Roam DB"]
+ [:> Button {:on-click close-modal}
+ [:> Close]]]
+
+ :content [:div (use-style (merge modal-contents-style))
+ (if (nil? @transformed-roam-db)
+ [:<>
+ [:input {:style {:flex "0 0 auto"} :type "file" :accept ".edn" :on-change #(file-cb % transformed-roam-db roam-db-filename)}]
+ [:div {:style {:position "relative"
+ :padding-bottom "56.25%"
+ :margin "1em 0 0"
+ :flex "1 1 100%"
+ :width "100%"}}
+ [:iframe {:src "https://www.loom.com/embed/787ed48da52c4149b031efb8e17c0939"
+ :frameBorder "0"
+ :webkitallowfullscreen "true"
+ :mozallowfullscreen "true"
+ :allowFullScreen true
+ :style {:position "absolute"
+ :top 0
+ :left 0
+ :width "100%"
+ :height "100%"}}]]]
+ (let [roam-pages (roam-pages @transformed-roam-db)
+ shared-pages (events/get-shared-pages @transformed-roam-db)]
+ [:div {:style {:display "flex" :flex-direction "column"}}
+ [:h6 (str "Your Roam DB had " (count roam-pages)) " pages. " (count shared-pages) " of these pages were also found in your Athens DB. Press Merge to continue merging your DB."]
+ [:p {:style {:margin "10px 0 0 0"}} "Shared Pages"]
+ [:ol {:style {:max-height "400px"
+ :width "100%"
+ :overflow-y "auto"}}
+ (for [x shared-pages]
+ ^{:key x}
+ [:li (str "[[" x "]]")])]
+ [:> Button {:style {:align-self "center"}
+ :is-primary true
+ :on-click (fn []
+ (dispatch [:upload/roam-edn @transformed-roam-db @roam-db-filename])
+ (close-modal))}
+ "Merge"]]))]
+
+ :on-close close-modal}]])))
(defn open-local-comp
[loading db]
- [form-container
- [:> FormControl {:isReadOnly true}
- [:> FormLabel (if @loading
- "No DB Found At"
- "Current database location")]
- [:> HStack
- [:> Text {:as "output"
- :borderRadius "md"
- :cursor "default"
- :bg "background.floor"
- :color "foreground.secondary"
- :flex "1 1 100%"
- :py 1.5
- :px 2.5
- :display "flex"}
- (:id db)]
- [:> Button {:isDisabled @loading
- :size "sm"
- :onClick #(dialogs/move-dialog!)}
- "Move"]]]
- [:> ButtonGroup
- [:> Button {:onClick #(dialogs/open-dialog!)}
- "Open from file"]]])
+ [:<>
+ [:h5 {:style {:align-self "flex-start"
+ :margin-top "2em"}}
+ (if @loading
+ "No DB Found At"
+ "Current Location")]
+ [:code {:style {:margin "1rem 0 2rem 0"}} (:id db)]
+ [:div (use-style {:display "flex"
+ :justify-content "space-between"
+ :align-items "center"
+ :width "80%"})
+ [:> Button {:is-primary true
+ :on-click #(dialogs/open-dialog!)}
+ "Open"]
+ [:> Button {:disabled @loading
+ :is-primary true
+ :on-click #(dialogs/move-dialog!)}
+ "Move"]]])
(defn create-new-local
[state]
- [form-container
- [:> FormControl
- [:> FormLabel "Name"]
- [:> Input {:value (:input @state)
- :onChange #(swap! state assoc :input (js-event->val %))}]]
- [:> ButtonGroup
- [:> Button {:value (:input @state)
- :isDisabled (clojure.string/blank? (:input @state))
- :onClick #(dialogs/create-dialog! (:input @state))}
- "Choose folder"]]])
+ [:<>
+ [:div {:style {:display "flex"
+ :justify-content "space-between"
+ :width "100%"
+ :margin-top "2em"
+ :margin-bottom "1em"}}
+ [:h5 "Database Name"]
+ [textinput/textinput {:value (:input @state)
+ :placeholder "DB Name"
+ :on-change #(swap! state assoc :input (js-event->val %))}]]
+ [:div {:style {:display "flex"
+ :justify-content "space-between"
+ :width "100%"}}
+ [:h5 "New Location"]
+ [:> Button {:is-primary true
+ :disabled (clojure.string/blank? (:input @state))
+ :on-click #(dialogs/create-dialog! (:input @state))}
+ "Browse"]]])
(defn join-remote-comp
[]
- (let [name (r/atom "")
- address (r/atom "")
+ (let [name (r/atom "RTC")
+ address (r/atom "localhost:3010")
password (r/atom "")]
(fn []
- [form-container
+ [:<>
(->>
- [:> VStack {:spacing 4}
- [:> FormControl
- [:> FormLabel "Database name"]
- [:> Input {:value @name
- :onChange #(reset! name (js-event->val %))}]]
- [:> FormControl
- [:> FormLabel "Remote address"]
- [:> Input {:value @address
- :onChange #(reset! address (js-event->val %))}]]
- [:> FormControl {:flexDirection "row"}
- [:> FormLabel "Password"]
- [:> Input {:value @password
- :type "password"
- :onChange #(reset! password (js-event->val %))}]]]
+ [:div {:style {:width "100%" :margin-top "10px"}}
+ [:h5 "Database Name"]
+ [:div {:style {:margin "5px 0"
+ :display "flex"
+ :justify-content "space-between"}}
+ [textinput/textinput {:style {:flex-grow 1
+ :padding "5px"}
+ :type "text"
+ :value @name
+ :placeholder "DB name"
+ :on-change #(reset! name (js-event->val %))}]]
+ [:h5 "Remote Address"]
+ [:div {:style {:margin "5px 0"
+ :display "flex"
+ :justify-content "space-between"}}
+ [textinput/textinput {:style {:flex-grow 1
+ :padding "5px"}
+ :type "text"
+ :value @address
+ :placeholder "Remote server address"
+ :on-change #(reset! address (js-event->val %))}]]
+ [:h5 "Password"]
+ [:div {:style {:margin "5px 0"
+ :display "flex"
+ :justify-content "space-between"}}
+ [textinput/textinput {:style {:flex-grow 1
+ :padding "5px"}
+ :type "password"
+ :value @password
+ :placeholder "Password"
+ :disabled false
+ :on-change #(reset! password (js-event->val %))}]]]
doall)
- [:> ButtonGroup
- [:> Button {:type "submit"
- :isDisabled (or (clojure.string/blank? @name)
- (clojure.string/blank? @address))
- :onClick #(rf/dispatch [:db-picker/add-and-select-db (utils/self-hosted-db @name @address @password)])}
- "Join"]]])))
+ [:> Button {:is-primary true
+ :style {:margin-top "0.5rem"}
+ :disabled (or (clojure.string/blank? @name)
+ (clojure.string/blank? @address))
+ :on-click #(rf/dispatch [:db-picker/add-and-select-db (utils/self-hosted-db @name @address @password)])}
+ "Join"]])))
(defn window
@@ -196,34 +256,46 @@
close-modal (fn []
(when-not @loading
(dispatch [:modal/toggle])))
+ el (.. js/document (querySelector "#app"))
selected-db @(subscribe [:db-picker/selected-db])
- state (r/atom {:input ""})]
+ state (r/atom {:input ""
+ :tab-value (if utils/electron? 0 2)})]
(fn []
- [:> Modal {:isOpen loading
- :motionPreset "scale"
- :onClose close-modal}
- [:> ModalOverlay]
- [:> ModalContent
- [:> ModalHeader
- "Add Database"]
- (when-not @loading
- [:> ModalCloseButton])
- [:> ModalBody {:display "contents"}
- ;; TODO: this is hacky, we're just hiding the picker and forcing
- ;; tab 2 for the web client. Instead we should use Stuart's
- ;; redesigned DB picker.
- [:> Tabs {:isFitted true
- :display "contents"
- :defaultIndex (if utils/electron? 0 2)}
- (when utils/electron?
- [:> TabList
- [:> Tab "Open Local"]
- [:> Tab "Join Remote"]
- [:> Tab "Create New"]])
- [:> TabPanels {:display "contents"}
- [:> TabPanel {:display "contents"}
- [open-local-comp loading selected-db]]
- [:> TabPanel {:display "contents"}
- [join-remote-comp]]
- [:> TabPanel {:display "contents"}
- [create-new-local state]]]]]]])))
+ (.createPortal
+ react-dom
+ (r/as-element [:div (use-style modal-style)
+ [modal/modal
+ {:title [:div.modal__title
+ [:> Storage]
+ [:h4 "Database"]
+ (when-not @loading
+ [:> Button {:on-click close-modal} [:> Close]])]
+ :content [:div (use-style modal-contents-style)
+ ;; TODO: this is hacky, we're just hiding the picker and forcing
+ ;; tab 2 for the web client. Instead we should use Stuart's
+ ;; redesigned DB picker.
+ (when utils/electron?
+ [:div (use-style picker-style)
+ [:button {:class (when (= 0 (:tab-value @state)) "active")
+ :on-click (fn [] (swap! state assoc :tab-value 0))}
+ [:> Folder]
+ [:span "Open"]]
+ [:button {:class (when (= 1 (:tab-value @state)) "active")
+ :on-click (fn [] (swap! state assoc :tab-value 1))}
+ [:> AddBox]
+ [:span "New"]]
+ [:button {:class (when (= 2 (:tab-value @state)) "active")
+ :on-click (fn [] (swap! state assoc :tab-value 2))}
+ [:> Group]
+ [:span "Join"]]])
+ (cond
+ (= 2 (:tab-value @state))
+ [join-remote-comp]
+
+ (= 1 (:tab-value @state))
+ [create-new-local state]
+
+ (= 0 (:tab-value @state))
+ [open-local-comp loading selected-db])]
+ :on-close close-modal}]])
+ el))))
diff --git a/src/cljs/athens/events.cljs b/src/cljs/athens/events.cljs
index 01a5f45305..2ff72d51cf 100644
--- a/src/cljs/athens/events.cljs
+++ b/src/cljs/athens/events.cljs
@@ -471,6 +471,27 @@
(assoc-in db [:selection :items] ordered-selection))))
+;; Alerts
+
+(reg-event-db
+ :alert/set
+ (fn-traced [db alert]
+ (assoc db :alert alert)))
+
+
+(reg-event-db
+ :alert/unset
+ (fn-traced [db]
+ (assoc db :alert nil)))
+
+
+;; Use native js/alert rather than custom UI alert
+(reg-event-fx
+ :alert/js
+ (fn [_ [_ message]]
+ {:alert/js! message}))
+
+
(reg-event-fx
:confirm/js
(fn [_ [_ message true-cb false-cb]]
@@ -860,9 +881,7 @@
:count (count new-uids)}]))]]})
{})
(catch :default _
- {:fx (util/toast (clj->js {:status "error"
- :title "Couldn't undo"
- :description "Undo for this operation not supported in Lan-Party, yet."}))}))))
+ {:fx [[:dispatch [:alert/js "Undo for this operation not supported in Lan-Party, yet."]]]}))))
(reg-event-fx
@@ -893,9 +912,7 @@
:count (count new-uids)}]))]]})
{})
(catch :default _
- {:fx (util/toast (clj->js {:status "error"
- :title "Couldn't redo"
- :description "Redo for this operation not supported in Lan-Party, yet."}))}))))
+ {:fx [[:dispatch [:alert/js "Redo for this operation not supported in Lan-Party, yet."]]]}))))
(reg-event-fx
@@ -1621,9 +1638,7 @@
(re-find #"text/html" datatype) (.getAsString item (fn [_] #_(prn "getAsString" _))))))
items)
{})
- {:fx (util/toast (clj->js {:status "error"
- :title "Couldn't paste"
- :description "Image paste is not supported in Lan-Party, yet."}))}))))
+ {:fx [[:dispatch [:alert/js "Image paste not supported in Lan-Party, yet."]]]}))))
(reg-event-fx
diff --git a/src/cljs/athens/main/core.cljs b/src/cljs/athens/main/core.cljs
index e007a782cc..c02e37e1ef 100644
--- a/src/cljs/athens/main/core.cljs
+++ b/src/cljs/athens/main/core.cljs
@@ -89,7 +89,7 @@
:y (.-y main-window-state)
:width (.-width main-window-state)
:height (.-height main-window-state)
- :minWidth 650 ; Minimum width before clipping in toolbar
+ :minWidth 800 ; Minimum width before clipping in toolbar
:minHeight 300
:backgroundColor "#1A1A1A"
:autoHideMenuBar true
diff --git a/src/cljs/athens/parse_renderer.cljs b/src/cljs/athens/parse_renderer.cljs
index 2aeb207a9b..42eece2c60 100644
--- a/src/cljs/athens/parse_renderer.cljs
+++ b/src/cljs/athens/parse_renderer.cljs
@@ -1,43 +1,68 @@
^:cljstyle/ignore
(ns athens.parse-renderer
(:require
- ["@chakra-ui/react" :refer [Link Button Text Box]]
- ["katex" :as katex]
- ["katex/dist/contrib/mhchem"]
- [athens.config :as config]
- [athens.parser.impl :as parser-impl]
- [athens.reactive :as reactive]
- [athens.router :as router]
- [clojure.string :as str]
- [instaparse.core :as insta]
- [re-frame.core :as rf]))
+ ["katex" :as katex]
+ ["katex/dist/contrib/mhchem"]
+ [athens.config :as config]
+ [athens.parser.impl :as parser-impl]
+ [athens.reactive :as reactive]
+ [athens.router :as router]
+ [athens.style :refer [color OPACITIES]]
+ [clojure.string :as str]
+ [instaparse.core :as insta]
+ [re-frame.core :as rf]
+ [stylefy.core :as stylefy :refer [use-style]]))
(declare parse-and-render)
-(def fm-props
- {:as "b"
- :class "formatting"
- :whiteSpace "nowrap"
- :fontWeight "normal"
- :opacity "0.3"})
+;; Styles
+(def page-link
+ {:cursor "pointer"
+ :text-decoration "none"
+ :color (color :link-color)
+ :display "inline"
+ :border-radius "0.25rem"
+ ::stylefy/manual [[:.formatting {:color (color :body-text-color)
+ :opacity (:opacity-low OPACITIES)}]
+ [:&:hover {:z-index 1
+ :background (color :link-color :opacity-lower)
+ :box-shadow (str "0px 0px 0px 1px " (color :link-color :opacity-lower))}]]})
-(def link-props
- {:color "link"
- :borderRadius "1px"
- :variant "link"
- :minWidth "0"
- :whiteSpace "inherit"
- :wordBreak "inherit"
- :alignItems "flex-start"
- :justifyContent "flex-start"
- :lineHeight "unset"
- :textAlign "inherit"
- :fontSize "inherit"
- :fontWeight "inherit"
- :textDecoration "none"})
+
+(def hashtag
+ {::stylefy/mode [[:hover {:text-decoration "underline" :cursor "pointer"}]]
+ ::stylefy/manual [[:.formatting {:opacity (:opacity-low OPACITIES)}]]})
+
+
+(def image {:border-radius "0.125rem"})
+
+
+(def url-link
+ {:cursor "pointer"
+ :text-decoration "none"
+ :color (color :link-color)
+ ::stylefy/manual [[:.formatting {:color (color :body-text-color :opacity-low)}]
+ [:&:hover {:text-decoration "underline"}]]})
+
+
+(def autolink
+ {:cursor "pointer"
+ :text-decoration "none"
+ ::stylefy/manual [[:.formatting {:color (color :body-text-color :opacity-low)}]
+ [:.contents {:color (color :link-color)
+ :text-decoration "none"}]
+ [:&:hover [:.contents {:text-decoration "underline"}]]]})
+
+
+(def block-ref
+ {:font-size "0.9em"
+ :transition "background 0.05s ease"
+ :border-bottom [["1px" "solid" (color :highlight-color)]]
+ ::stylefy/mode [[:hover {:background-color (color :highlight-color :opacity-lower)
+ :cursor "alias"}]]})
(defn parse-title
@@ -54,16 +79,12 @@
(defn render-page-link
"Renders a page link given the title of the page."
[{:keys [from title]} title-coll]
- [:<>
- [:> Text fm-props "[["]
+ [:span (assoc (use-style page-link {:class "page-link"})
+ :title from)
+ [:span {:class "formatting"} "[["]
(cond
(not (str/blank? title))
- [:> Button
- (merge link-props
- {:class "page-link"
- :fontWeight "normal"
- :title from
- :onClick (fn [e]
+ [:span {:on-click (fn [e]
(let [parsed-title (parse-title title-coll)
shift? (.-shiftKey e)]
(.. e stopPropagation) ; prevent bubbling up click handler for nested links
@@ -72,27 +93,22 @@
:pane (if shift?
:right-pane
:main-pane)}])
- (router/navigate-page parsed-title e)))})
+ (router/navigate-page parsed-title e)))}
title]
:else
- (into
- [:> Button
- (merge link-props
- {:class "page-link"
- :title from
- :onClick (fn [e]
- (let [parsed-title (parse-title title-coll)
- shift? (.-shiftKey e)]
- (.. e stopPropagation) ; prevent bubbling up click handler for nested links
- (rf/dispatch [:reporting/navigation {:source :pr-page-link
- :target :page
- :pane (if shift?
- :right-pane
- :main-pane)}])
- (router/navigate-page parsed-title e)))})]
- title-coll))
- [:> Text fm-props "]]"]])
+ (into [:span {:on-click (fn [e]
+ (let [parsed-title (parse-title title-coll)
+ shift? (.-shiftKey e)]
+ (.. e stopPropagation) ; prevent bubbling up click handler for nested links
+ (rf/dispatch [:reporting/navigation {:source :pr-page-link
+ :target :page
+ :pane (if shift?
+ :right-pane
+ :main-pane)}])
+ (router/navigate-page parsed-title e)))}]
+ title-coll))
+ [:span {:class "formatting"} "]]"]])
(defn- block-breadcrumb-string
@@ -109,50 +125,30 @@
parents (reactive/get-reactive-parents-recursively [:block/uid ref-uid])
bc-string (block-breadcrumb-string parents)]
(if block
- [:> Button {:variant "link"
- :as "a"
- :title (-> from
- (str/replace "]("
- "]\n---\n(")
- (str/replace (str "((" ref-uid "))")
- bc-string))
- :class "block-ref"
- :display "inline"
- :color "unset"
- :whiteSpace "unset"
- :textAlign "unset"
- :minWidth "0"
- :fontSize "inherit"
- :fontWeight "inherit"
- :lineHeight "inherit"
- :marginInline "-2px"
- :paddingInline "2px"
- :borderBottomWidth "1px"
- :borderBottomStyle "solid"
- :borderBottomColor "ref.foreground"
- :cursor "alias"
- :sx {"WebkitBoxDecorationBreak" "clone"}
- :_hover {:textDecoration "none"
- :borderBottomColor "transparent"
- :bg "ref.background"}
- :onClick (fn [e]
- (.. e stopPropagation)
- (let [shift? (.-shiftKey e)]
- (rf/dispatch [:reporting/navigation {:source :pr-block-ref
- :target :block
- :pane (if shift?
- :right-pane
- :main-pane)}])
- (router/navigate-uid ref-uid e)))}
- (cond
- (= uid ref-uid)
- [parse-and-render "{{SELF}}"]
-
- (not (str/blank? title))
- [parse-and-render title ref-uid]
-
- :else
- [parse-and-render (:block/string block) ref-uid])]
+ [:span (assoc (use-style block-ref {:class "block-ref"})
+ :title (-> from
+ (str/replace "]("
+ "]\n---\n(")
+ (str/replace (str "((" ref-uid "))")
+ bc-string)))
+ [:span {:class "contents"
+ :on-click (fn [e]
+ (let [shift? (.-shiftKey e)]
+ (rf/dispatch [:reporting/navigation {:source :pr-block-ref
+ :target :block
+ :pane (if shift?
+ :right-pane
+ :main-pane)}])
+ (router/navigate-uid ref-uid e)))}
+ (cond
+ (= uid ref-uid)
+ [parse-and-render "{{SELF}}"]
+
+ (not (str/blank? title))
+ [parse-and-render title ref-uid]
+
+ :else
+ [parse-and-render (:block/string block) ref-uid])]]
from)))
@@ -218,56 +214,44 @@
:page-link (fn [{_from :from :as attr} & title-coll]
(render-page-link attr title-coll))
:hashtag (fn [{_from :from} & title-coll]
- [:> Button (merge link-props
- {:variant "link"
- :class "hashtag"
- :color "inherit"
- :fontWeight "inherit"
- :_hover {:textDecoration "none"}
- :onClick (fn [e]
- (let [parsed-title (parse-title title-coll)
- shift? (.-shiftKey e)]
- (rf/dispatch [:reporting/navigation {:source :pr-hashtag
- :target :hashtag
- :pane (if shift?
- :right-pane
- :main-pane)}])
- (router/navigate-page parsed-title e)))})
- [:> Text fm-props "#"]
+ [:span (use-style hashtag {:class "hashtag"
+ :on-click (fn [e]
+ (let [parsed-title (parse-title title-coll)
+ shift? (.-shiftKey e)]
+ (rf/dispatch [:reporting/navigation {:source :pr-hashtag
+ :target :hashtag
+ :pane (if shift?
+ :right-pane
+ :main-pane)}])
+ (router/navigate-page parsed-title e)))})
+ [:span {:class "formatting"} "#"]
[:span {:class "contents"} title-coll]])
:block-ref (fn [{_from :from :as attr} ref-uid]
(render-block-ref attr ref-uid uid))
:url-image (fn [{url :src alt :alt}]
- [:> Box {:class "url-image"
- :as "img"
- :borderRadius "md"
- :alt alt
- :src url}])
+ [:img (use-style image {:class "url-image"
+ :alt alt
+ :src url})])
:url-link (fn [{url :url} text]
- [:> Button
- (merge link-props {:class "url-link"
- :href url
- :target "_blank"})
+ [:a (use-style url-link {:class "url-link"
+ :href url
+ :target "_blank"})
text])
:link (fn [{:keys [text target title]}]
- [:> Button (cond-> (merge link-props
- {:class "url-link contents"
- :as "a"
- :href target
- :target "_blank"})
- (string? title)
- (assoc :title title))
+ [:a (cond-> (use-style url-link {:class "url-link contents"
+ :href target
+ :target "_blank"})
+ (string? title)
+ (assoc :title title))
text])
:autolink (fn [{:keys [text target]}]
- [:<>
- [:> Text fm-props "<"]
- [:> Link (merge
- link-props
- {:class "autolink contents"
- :href target
- :target "_blank"})
+ [:span (use-style autolink)
+ [:span {:class "formatting"} "<"]
+ [:a {:class "autolink contents"
+ :href target
+ :target "_blank"}
text]
- [:> Text fm-props ">"]])
+ [:span {:class "formatting"} ">"]])
:text-run (fn [& contents]
(apply conj [:span {:class "text-run"}] contents))
:paragraph (fn [& contents]
diff --git a/src/cljs/athens/self_hosted/presence/views.cljs b/src/cljs/athens/self_hosted/presence/views.cljs
index 43477a0f5c..788b06b1ed 100644
--- a/src/cljs/athens/self_hosted/presence/views.cljs
+++ b/src/cljs/athens/self_hosted/presence/views.cljs
@@ -1,7 +1,7 @@
(ns athens.self-hosted.presence.views
(:require
+ ["/components/Avatar/Avatar" :refer [Avatar]]
["/components/PresenceDetails/PresenceDetails" :refer [PresenceDetails]]
- ["@chakra-ui/react" :refer [Avatar AvatarGroup]]
[athens.self-hosted.presence.events]
[athens.self-hosted.presence.fx]
[athens.self-hosted.presence.subs]
@@ -10,6 +10,8 @@
[reagent.core :as r]))
+;; Avatar
+
(defn user->person
[{:keys [session-id username color]
:page/keys [title]}]
@@ -23,8 +25,8 @@
(defn copy-host-address-to-clipboard
[host-address]
(.. js/navigator -clipboard (writeText host-address))
- (util/toast (clj->js {:status "info"
- :title "Host address copied to clipboard"})))
+ (rf/dispatch [:show-snack-msg {:msg "Host address copied to clipboard"
+ :type :success}]))
(defn go-to-user-block
@@ -34,11 +36,11 @@
(->> (js->clj js-person :keywordize-keys true)
:personId
(get all-users))]
- (if page-uid
- ;; TODO: if we support navigating to a block, it should be added here.
- (rf/dispatch [:navigate :page {:id page-uid}])
- (util/toast (clj->js {:title "User is not on any page"
- :status "warning"})))))
+ (rf/dispatch (if page-uid
+ ;; TODO: if we support navigating to a block, it should be added here.
+ [:navigate :page {:id page-uid}]
+ [:show-snack-msg {:msg "User is not on any page"
+ :type :success}]))))
(defn edit-current-user
@@ -85,17 +87,20 @@
(let [users (rf/subscribe [:presence/has-presence (util/embed-uid->original-uid uid)])]
(when (seq @users)
(into
- [:> AvatarGroup {:max 3
- :zIndex 2
- :size "xs"
- :position "absolute"
- :right "-1.5rem"
- :top "0.25rem"}
- (->> @users
- (map user->person)
- (remove nil?)
- (map (fn [{:keys [personId] :as person}]
- [:> Avatar {:key personId
- :bg (:color person)
- :name (:username person)}])))]))))
+ [:> (.-Stack Avatar)
+ {:size "1.25rem"
+ :maskSize "1.5px"
+ :stackOrder "from-left"
+ :limit 3
+ :style {:zIndex 100
+ :position "absolute"
+ :right "-1.5rem"
+ :top "0.25rem"
+ :padding "0.125rem"
+ :background "var(--background-color)"}}]
+ (->> @users
+ (map user->person)
+ (remove nil?)
+ (map (fn [{:keys [personId] :as person}]
+ [:> Avatar (merge {:showTooltip false :key personId} person)])))))))
diff --git a/src/cljs/athens/style.cljs b/src/cljs/athens/style.cljs
index a1bf274ee1..a00e2f3969 100644
--- a/src/cljs/athens/style.cljs
+++ b/src/cljs/athens/style.cljs
@@ -7,6 +7,97 @@
[stylefy.reagent :as stylefy-reagent]))
+(def THEME-DARK
+ {:link-color "#2399E7"
+ :highlight-color "#FBBE63"
+ :text-highlight-color "#FBBE63"
+ :warning-color "#DE3C21"
+ :confirmation-color "#189E36"
+ :header-text-color "#BABABA"
+ :body-text-color "#AAA"
+ :border-color "hsla(32, 81%, 90%, 0.08)"
+ :background-minus-1 "#151515"
+ :background-minus-2 "#111"
+ :background-color "#1A1A1A"
+ :background-plus-1 "#222"
+ :background-plus-2 "#333"
+
+ :graph-control-bg "#272727"
+ :graph-control-color "white"
+ :graph-node-normal "#909090"
+ :graph-node-hlt "#FBBE63"
+ :graph-link-normal "#323232"
+
+ :error-color "#fd5243"})
+
+
+(def THEME-LIGHT
+ {:link-color "#0075E1"
+ :highlight-color "#F9A132"
+ :text-highlight-color "#ffdb8a"
+ :warning-color "#D20000"
+ :confirmation-color "#009E23"
+ :header-text-color "#322F38"
+ :body-text-color "#433F38"
+ :border-color "hsla(32, 81%, 10%, 0.08)"
+ :background-plus-2 "#fff"
+ :background-plus-1 "#fbfbfb"
+ :background-color "#F6F6F6"
+ :background-minus-1 "#FAF8F6"
+ :background-minus-2 "#EFEDEB"
+ :graph-control-bg "#f9f9f9"
+ :graph-control-color "black"
+ :graph-node-normal "#909090"
+ :graph-node-hlt "#0075E1"
+ :graph-link-normal "#cfcfcf"
+
+ :error-color "#fd5243"})
+
+
+(def DEPTH-SHADOWS
+ {:4 "0 2px 4px rgba(0, 0, 0, 0.2)"
+ :8 "0 4px 8px rgba(0, 0, 0, 0.2)"
+ :16 "0 4px 16px rgba(0, 0, 0, 0.2)"
+ :64 "0 24px 60px rgba(0, 0, 0, 0.2)"})
+
+
+(def OPACITIES
+ {:opacity-lower 0.10
+ :opacity-low 0.25
+ :opacity-med 0.50
+ :opacity-high 0.75
+ :opacity-higher 0.85})
+
+
+;; Based on Bootstrap's excellent Z-index set
+(def ZINDICES
+ {:zindex-dropdown 1000
+ :zindex-sticky 1020
+ :zindex-fixed 1030
+ :zindex-modal-backdrop 1040
+ :zindex-modal 1050
+ :zindex-popover 1060
+ :zindex-tooltip 1070})
+
+
+;; Color
+(defn color
+ "Turns a color and optional opacity into a CSS variable.
+ Only accepts keywords."
+ ([variable]
+ (when (keyword? variable)
+ (str "var(--"
+ (symbol variable)
+ ")")))
+ ([variable alpha]
+ (when (and (keyword? variable) (keyword? alpha))
+ (str "var(--"
+ (symbol variable)
+ "---"
+ (symbol alpha)
+ ")"))))
+
+
(reg-sub
:zoom-level
(fn [db _]
diff --git a/src/cljs/athens/util.cljs b/src/cljs/athens/util.cljs
index 67ef5f4857..89ab41b727 100644
--- a/src/cljs/athens/util.cljs
+++ b/src/cljs/athens/util.cljs
@@ -1,8 +1,6 @@
(ns athens.util
(:require
["/textarea" :as getCaretCoordinates]
- ["/theme/theme" :refer [theme]]
- ["@chakra-ui/react" :refer [createStandaloneToast]]
[athens.config :as config]
[athens.electron.utils :as electron.utils]
[clojure.string :as string]
@@ -16,9 +14,6 @@
KeyCodes)))
-(def toast (createStandaloneToast (clj->js {:theme theme})))
-
-
;; Electron ipcMain Channels
(def ipcMainChannels
@@ -222,8 +217,8 @@
(let [os (.. js/window -navigator -appVersion)]
(cond
(re-find #"Windows" os) :windows
- (re-find #"Mac" os) :mac
- :else :linux)))
+ (re-find #"Linux" os) :linux
+ (re-find #"Mac" os) :mac)))
(defn is-mac?
diff --git a/src/cljs/athens/views.cljs b/src/cljs/athens/views.cljs
index 55fdc4b32d..ec86fd1220 100644
--- a/src/cljs/athens/views.cljs
+++ b/src/cljs/athens/views.cljs
@@ -1,11 +1,15 @@
(ns athens.views
(:require
- ["/theme/theme" :refer [theme]]
- ["@chakra-ui/react" :refer [ChakraProvider Flex Grid Spinner Center]]
+ ["/components/Spinner/Spinner" :refer [Spinner]]
+ ["/components/utils/style/style" :refer [GlobalStyles]]
+ ["@material-ui/core/Snackbar" :as Snackbar]
+ ["@react-aria/overlays" :refer [OverlayProvider]]
[athens.config]
[athens.electron.db-modal :as db-modal]
+ [athens.electron.utils :as electron.utils]
[athens.style :refer [zoom]]
[athens.subs]
+ [athens.util :refer [get-os]]
[athens.views.app-toolbar :as app-toolbar]
[athens.views.athena :refer [athena-component]]
[athens.views.devtool :refer [devtool-component]]
@@ -13,11 +17,28 @@
[athens.views.left-sidebar :as left-sidebar]
[athens.views.pages.core :as pages]
[athens.views.right-sidebar :as right-sidebar]
- [re-frame.core :as rf]))
+ [re-frame.core :as rf]
+ [reagent.core :as r]
+ [stylefy.core :as stylefy :refer [use-style]]))
+
+
+;; Styles
+
+
+(def app-wrapper-style
+ {:display "grid"
+ :grid-template-areas
+ "'app-header app-header app-header'
+ 'left-sidebar main-content secondary-content'
+ 'devtool devtool devtool'"
+ :grid-template-columns "auto 1fr auto"
+ :grid-template-rows "auto 1fr auto"
+ :height "100vh"})
;; Components
+
(defn alert
[]
(let [alert- (rf/subscribe [:alert])]
@@ -26,49 +47,62 @@
(rf/dispatch [:alert/unset]))))
+;; Snackbar
+
+(def m-snackbar (r/adapt-react-class (.-default Snackbar)))
+
+
+(rf/reg-sub
+ :db/snack-msg
+ (fn [db]
+ (:db/snack-msg db)))
+
+
+(rf/reg-event-db
+ :show-snack-msg
+ (fn [db [_ msg-opts]]
+ (js/setTimeout #(rf/dispatch [:show-snack-msg {}]) 4000)
+ (assoc db :db/snack-msg msg-opts)))
+
+
(defn main
[]
(let [loading (rf/subscribe [:loading?])
+ os (get-os)
+ electron? electron.utils/electron?
modal (rf/subscribe [:modal])]
(fn []
- [:div (merge {:style {:display "contents"}}
- (zoom))
- [:> ChakraProvider {:theme theme,
- :bg "background.basement"}
+ [:> OverlayProvider
+ [:div (merge {:style {:display "contents"}}
+ (zoom))
+ [:> GlobalStyles]
[help-popup]
[alert]
+ (let [{:keys [msg type]} @(rf/subscribe [:db/snack-msg])]
+ [m-snackbar
+ {:message msg
+ :open (boolean msg)}
+ [:span
+ {:style {:background-color (case type
+ :success "green"
+ "red")
+ :padding "10px 20px"
+ :color "white"}}
+ msg]])
[athena-component]
(cond
(and @loading @modal) [db-modal/window]
- @loading
- [:> Center {:height "100vh"}
- [:> Flex {:width 28
- :flexDirection "column"
- :gap 2
- :color "foreground.secondary"
- :borderRadius "lg"
- :placeItems "center"
- :placeContent "center"
- :height 28}
- [:> Spinner {:size "xl"}]]]
+ @loading [:> Spinner]
:else [:<>
(when @modal [db-modal/window])
- [:> Grid
- {:gridTemplateColumns "auto 1fr auto"
- :gridTemplateRows "auto 1fr auto"
- :grid-template-areas
- "'app-header app-header app-header'
- 'left-sidebar main-content secondary-content'
- 'devtool devtool devtool'"
- :height "100vh"
- :overflow "hidden"
- :sx {"WebkitAppRegion" "drag"
- "--app-toolbar-height" "3.25rem"
- ".os-mac &" {"--app-header-height" "52px"}
- ".os-windows &" {"--toolbar-height" "44px"}
- ".os-linux &" {"--toolbar-height" "44px"}}}
+ [:div (use-style app-wrapper-style
+ {:class [(case os
+ :windows "os-windows"
+ :mac "os-mac"
+ :linux "os-linux")
+ (when electron? "is-electron")]})
[app-toolbar/app-toolbar]
[left-sidebar/left-sidebar]
[pages/view]
diff --git a/src/cljs/athens/views/app_toolbar.cljs b/src/cljs/athens/views/app_toolbar.cljs
index cd76c34805..4aca0f1b1a 100644
--- a/src/cljs/athens/views/app_toolbar.cljs
+++ b/src/cljs/athens/views/app_toolbar.cljs
@@ -2,6 +2,7 @@
(:require
["/components/AppToolbar/AppToolbar" :refer [AppToolbar]]
[athens.electron.db-menu.core :refer [db-menu]]
+ [athens.electron.db-modal :as db-modal]
[athens.electron.utils :as electron.utils]
[athens.router :as router]
[athens.self-hosted.presence.views :refer [toolbar-presence-el]]
@@ -31,6 +32,7 @@
win-fullscreen? (if electron?
(rf/subscribe [:win-fullscreen?])
(r/atom false))
+ merge-open? (r/atom false)
os (util/get-os)
on-left-sidebar-toggle #(rf/dispatch [:left-sidebar/toggle])
on-back #(.back js/window.history)
@@ -58,36 +60,42 @@
on-athena #(rf/dispatch [:athena/toggle])
on-help #(rf/dispatch [:help/toggle])
on-theme #(rf/dispatch [:theme/toggle])
+ on-merge #(swap! merge-open? not)
on-right-sidebar #(rf/dispatch [:right-sidebar/toggle])
on-maximize #(rf/dispatch [:toggle-max-min-win])
on-minimize #(rf/dispatch [:minimize-win])
on-close #(rf/dispatch [:close-win])]
- [:> AppToolbar {:style (unzoom)
- :os os
- :isElectron electron?
- :route @route-name
- :isWinFullscreen @win-fullscreen?
- :isWinMaximized @win-maximized?
- :isWinFocused @win-focused?
- :isHelpOpen @help-open?
- :isThemeDark @theme-dark
- :isLeftSidebarOpen @left-open?
- :isRightSidebarOpen @right-open?
- :isCommandBarOpen @athena-open?
- :onPressLeftSidebarToggle on-left-sidebar-toggle
- :onPressHistoryBack on-back
- :onPressHistoryForward on-forward
- :onPressDailyNotes on-daily-pages
- :onPressAllPages on-all-pages
- :onPressGraph on-graph
- :onPressCommandBar on-athena
- :onPressHelp on-help
- :onPressThemeToggle on-theme
- :onPressSettings on-settings
- :onPressRightSidebarToggle on-right-sidebar
- :onPressMaximizeRestore on-maximize
- :onPressMinimize on-minimize
- :onPressClose on-close
- :databaseMenu (r/as-element [db-menu])
- :presenceDetails (when (electron.utils/remote-db? @selected-db)
- (r/as-element [toolbar-presence-el]))}]))
+ (fn []
+ [:<>
+ (when @merge-open?
+ [db-modal/merge-modal merge-open?])
+ [:> AppToolbar {:style (unzoom)
+ :os os
+ :isElectron electron?
+ :route @route-name
+ :isWinFullscreen @win-fullscreen?
+ :isWinMaximized @win-maximized?
+ :isWinFocused @win-focused?
+ :isHelpOpen @help-open?
+ :isThemeDark @theme-dark
+ :isLeftSidebarOpen @left-open?
+ :isRightSidebarOpen @right-open?
+ :isCommandBarOpen @athena-open?
+ :onPressLeftSidebarToggle on-left-sidebar-toggle
+ :onPressHistoryBack on-back
+ :onPressHistoryForward on-forward
+ :onPressDailyNotes on-daily-pages
+ :onPressAllPages on-all-pages
+ :onPressGraph on-graph
+ :onPressCommandBar on-athena
+ :onPressHelp on-help
+ :onPressThemeToggle on-theme
+ :onPressSettings on-settings
+ :onPressMerge on-merge
+ :onPressRightSidebarToggle on-right-sidebar
+ :onPressMaximizeRestore on-maximize
+ :onPressMinimize on-minimize
+ :onPressClose on-close
+ :databaseMenu (r/as-element [db-menu])
+ :presenceDetails (when (electron.utils/remote-db? @selected-db)
+ (r/as-element [toolbar-presence-el]))}]])))
diff --git a/src/cljs/athens/views/athena.cljs b/src/cljs/athens/views/athena.cljs
index d3b60b2a68..29b9f1193f 100644
--- a/src/cljs/athens/views/athena.cljs
+++ b/src/cljs/athens/views/athena.cljs
@@ -1,22 +1,150 @@
(ns athens.views.athena
(:require
- ["/components/Icons/Icons" :refer [XmarkIcon]]
- ["@chakra-ui/react" :refer [Modal ModalContent ModalOverlay VStack Button IconButton Input HStack Heading Text]]
+ ["@material-ui/icons/ArrowForward" :default ArrowForward]
+ ["@material-ui/icons/Close" :default Close]
+ ["@material-ui/icons/Create" :default Create]
[athens.common.utils :as utils]
[athens.db :as db :refer [search-in-block-content search-exact-node-title search-in-node-title re-case-insensitive]]
[athens.router :as router]
+ [athens.style :refer [color DEPTH-SHADOWS OPACITIES ZINDICES]]
[athens.subs]
[athens.util :refer [scroll-into-view]]
[clojure.string :as str]
+ [garden.selectors :as selectors]
[goog.dom :refer [getElement]]
[goog.events :as events]
[re-frame.core :as rf :refer [subscribe dispatch]]
- [reagent.core :as r])
+ [reagent.core :as r]
+ [stylefy.core :as stylefy :refer [use-style use-sub-style]])
(:import
(goog.events
KeyCodes)))
+;; Styles
+
+
+(def container-style
+ {:width "49rem"
+ :max-width "calc(100vw - 1rem)"
+ :border-radius "0.25rem"
+ :box-shadow [[(:64 DEPTH-SHADOWS) ", 0 0 0 1px " (color :body-text-color :opacity-lower)]]
+ :display "flex"
+ :flex-direction "column"
+ :background (color :background-plus-1)
+ :position "fixed"
+ :overflow "hidden"
+ :max-height "60vh"
+ :z-index (:zindex-modal ZINDICES)
+ :top "40%"
+ :left "50%"
+ :transform "translate(-50%, -50%)"
+ ;; Styling for the states of the custom search-cancel button, which depend on the input contents
+ ::stylefy/manual [[(selectors/+ :input :button) {:opacity 0}]
+ ;; Using ':valid' here as a proxy for "has contents", i.e. "button should appear"
+ [(selectors/+ :input:valid :button) {:opacity 1}]]})
+
+
+(def athena-input-style
+ {:width "100%"
+ :border 0
+ :font-size "2.375rem"
+ :font-weight "300"
+ :line-height "1.3"
+ :letter-spacing "-0.03em"
+ :border-radius "0.25rem 0.25rem 0 0"
+ :background (color :background-plus-2)
+ :color (color :body-text-color)
+ :caret-color (color :link-color)
+ :padding "1.5rem 4rem 1.5rem 1.5rem"
+ :cursor "text"
+ ::stylefy/mode {:focus {:outline "none"}
+ "::placeholder" {:color (color :body-text-color :opacity-low)}
+ "::-webkit-search-cancel-button" {:display "none"}}}) ; We replace the button elsewhere
+
+
+
+(def search-cancel-button-style
+ {:background "none"
+ :color "inherit"
+ :position "absolute"
+ :transition "opacity 0.1s ease, background 0.1s ease"
+ :cursor "pointer"
+ :border 0
+ :right "2rem"
+ :place-items "center"
+ :place-content "center"
+ :height "2.5rem"
+ :width "2.5rem"
+ :border-radius "1000px"
+ :display "flex"
+ :transform "translate(0%, -50%)"
+ :top "50%"
+ ::stylefy/manual [[:&:hover :&:focus {:background (color :background-plus-1)}]]})
+
+
+(def results-list-style
+ {:background (color :background-color)
+ :overflow-y "auto"
+ :max-height "100%"})
+
+
+(def results-heading-style
+ {:padding "0.25rem 1.125rem"
+ :background (color :background-plus-2)
+ :display "flex"
+ :position "sticky"
+ :flex-wrap "wrap"
+ :gap "0.5rem"
+ :align-items "center"
+ :top "0"
+ :justify-content "space-between"
+ :box-shadow [["0 1px 0 0 " (color :border-color)]]
+ :border-top [["1px solid" (color :border-color)]]})
+
+
+(def result-style
+ {:display "flex"
+ :padding "0.75rem 2rem"
+ :background (color :background-plus-1)
+ :color (color :body-text-color)
+ :transition "all .05s ease"
+ :border-top [["1px solid " (color :border-color)]]
+ ::stylefy/sub-styles {:title {:font-size "1rem"
+ :margin "0"
+ :color (color :header-text-color)
+ :font-weight "500"}
+ :preview {:white-space "wrap"
+ :word-break "break-word"
+ :color (color :body-text-color :opacity-med)}
+ :link-leader {:color "transparent"
+ :margin "auto auto"}}
+ ::stylefy/manual [[:b {:font-weight "500"
+ :opacity (:opacity-high OPACITIES)}]
+ [:&.selected :&:hover {:background (color :link-color)
+ :color "#fff"} ; Intentionally not a theme value, because we don't have a semantic way to contrast with :link-color
+ [:.title :.preview :.link-leader :.result-highlight {:color "inherit"}]]]})
+
+
+(def result-body-style
+ {:flex "1 1 100%"
+ :display "flex"
+ :flex-direction "column"
+ :justify-content "center"
+ :align-items "flex-start"})
+
+
+(def result-highlight-style
+ {:color (color :body-text-color)
+ :font-weight "500"})
+
+
+(def hint-style
+ {:color "inherit"
+ :opacity (:opacity-med OPACITIES)
+ :font-size "14px"})
+
+
;; Utilities
@@ -26,8 +154,7 @@
(doall
(map-indexed (fn [i part]
(if (re-find query-pattern part)
- [:> Text {:class "result-highlight"
- :key i} part]
+ [:span.result-highlight (use-style result-highlight-style {:key i}) part]
part))
(str/split txt query-pattern)))))
@@ -55,6 +182,9 @@
{:keys [index query results]} @state
item (get results index)]
(cond
+ (= key KeyCodes.ESC)
+ (dispatch [:athena/toggle])
+
(= KeyCodes.ENTER key) (cond
;; if page doesn't exist, create and open
(and (zero? index) (nil? item))
@@ -102,7 +232,7 @@
input-el (.. e -target)
;; Get the result list container which is the last element child
;; of the whole athena component
- result-el (.. input-el (closest "section.athena-modal") -lastElementChild)
+ result-el (.. input-el (closest "div.athena") -lastElementChild)
;; Get next element in the result list
next-el (nth (array-seq (.. result-el -children)) cur-index)]
;; Check if next el is beyond the bounds of the result list and scroll if so
@@ -114,7 +244,7 @@
(swap! state update :index #(if (= % (dec (count results))) 0 (inc %)))
(let [cur-index (:index @state)
input-el (.. e -target)
- result-el (.. input-el (closest "section.athena-modal") -lastElementChild)
+ result-el (.. input-el (closest "div.athena") -lastElementChild)
next-el (nth (array-seq (.. result-el -children)) cur-index)]
(scroll-into-view next-el result-el (zero? cur-index))))
@@ -123,86 +253,38 @@
;; Components
-(defn result-el
- [{:keys [title preview prefix icon query on-click active?]}]
- [:> Button {:justifyContent "flex-start"
- :fontWeight "normal"
- :display "flex"
- :height "auto"
- :textAlign "start"
- :flexDirection "row"
- :bg "transparent"
- :px 3
- :py 3
- :isActive active?
- :onClick on-click}
- [:> VStack {:align "stretch"
- :spacing 1
- :overflow "hidden"}
- [:> Heading {:as "h4"
- :size "sm"} prefix (highlight-match query title)]
- (when preview
- [:> Text {:color "foreground.secondary"
- :textOverflow "ellipsis"
- :overflow "hidden"} (highlight-match query preview)])]
- icon])
-
(defn results-el
[state]
(let [no-query? (str/blank? (:query @state))
recent-items @(subscribe [:athena/get-recent])]
- [:<> [:> HStack {:fontSize "sm"
- :px 6
- :py 2
- :color "foreground.secondary"
- :borderTop "1px solid"
- :borderColor "separator.divider"
- :justifyContent "space-between"}
- [:> Heading {:size "xs"}
- (if no-query? "Recent" "Results")]
- [:> Text
+ [:<> [:div (use-style results-heading-style)
+ [:h5 (if no-query? "Recent" "Results")]
+ [:span (use-style hint-style)
"Press "
[:kbd "shift + enter"]
" to open in right sidebar."]]
(when no-query?
- [:> VStack {:align "stretch"
- :spacing 1
- :borderTopWidth "1px"
- :borderTopStyle "solid"
- :borderColor "separator.divider"
- :pt 4
- :mb 4
- :px 4
- :overflowY "overlay"
- :_empty {:display "none"}}
+ [:div (use-style results-list-style)
(doall
(for [[i x] (map-indexed list recent-items)]
(when x
(let [{:keys [query :node/title :block/string]} x]
- [result-el {:key i
- :title title
- :query query
- :preview string
- :on-click (fn [e]
- (rf/dispatch [:reporting/navigation {:source :athena
- :target :page
- :pane :main-pane}])
- (router/navigate-page title e))}]))))])]))
+ [:div (use-style result-style {:key i
+ :on-click (fn [e]
+ (rf/dispatch [:reporting/navigation {:source :athena
+ :target :page
+ :pane :main-pane}])
+ (router/navigate-page title e))})
+ [:h4.title (use-sub-style result-style :title) (highlight-match query title)]
+ (when string
+ [:span.preview (use-sub-style result-style :preview) (highlight-match query string)])
+ [:span.link-leader (use-sub-style result-style :link-leader) [(r/adapt-react-class ArrowForward)]]]))))])]))
(defn search-results-el
[{:keys [results query index]}]
- [:> VStack {:align "stretch"
- :borderTopWidth "1px"
- :borderTopStyle "solid"
- :borderColor "separator.divider"
- :spacing 1
- :pt 4
- :mb 4
- :px 4
- :overflowY "overlay"
- :_empty {:display "none"}}
+ [:div (use-style results-list-style)
(doall
(for [[i x] (map-indexed list results)
:let [block-uid (:block/uid x)
@@ -212,113 +294,92 @@
string (:block/string x)]]
(if (nil? x)
^{:key i}
- [result-el {:key i
- :title query
- :prefix "Create page: "
- :preview nil
- :query query
- :active? (= i index)
- :on-click (fn [e]
- (let [block-uid (utils/gen-block-uid)
- shift? (.-shiftKey e)]
- (dispatch [:athena/toggle])
- (dispatch [:page/new {:title query
- :block-uid block-uid
- :source :athena}])
- (dispatch [:reporting/navigation {:source :athena
- :target (if parent
- (str "block/" block-uid)
- (str "page/" title))
- :pane (if shift?
- :right-pane
- :main-pane)}])))}]
- [result-el {:key i
- :title title
- :query query
- :preview string
- :active? (= i index)
- :on-click (fn [e]
- (let [selected-page {:node/title title
- :block/uid uid
- :block/string string
- :query query}
- shift? (.-shiftKey e)]
- (dispatch [:athena/toggle])
- (dispatch [:athena/update-recent-items selected-page])
- (dispatch [:reporting/navigation {:source :athena
- :target (if parent
- :block
- :page)
- :pane (if shift?
- :right-pane
- :main-pane)}])
- (if parent
- (router/navigate-uid block-uid)
- (router/navigate-page title e))))}])))])
+ [:div (use-style result-style
+ {:on-click (fn [e]
+ (let [block-uid (utils/gen-block-uid)
+ shift? (.-shiftKey e)]
+ (dispatch [:athena/toggle])
+ (dispatch [:page/new {:title query
+ :block-uid block-uid
+ :source :athena}])
+ (dispatch [:reporting/navigation {:source :athena
+ :target (if parent
+ (str "block/" block-uid)
+ (str "page/" title))
+ :pane (if shift?
+ :right-pane
+ :main-pane)}])))
+ :class (when (= i index) "selected")})
+
+ [:div (use-style result-body-style)
+ [:h4.title (use-sub-style result-style :title)
+ [:b "Create Page: "]
+ query]]
+ [:span.link-leader (use-sub-style result-style :link-leader) [(r/adapt-react-class Create)]]]
+
+ [:div (use-style result-style
+ {:key i
+ :on-click (fn [e]
+ (let [selected-page {:node/title title
+ :block/uid uid
+ :block/string string
+ :query query}
+ shift? (.-shiftKey e)]
+ (dispatch [:athena/toggle])
+ (dispatch [:athena/update-recent-items selected-page])
+ (dispatch [:reporting/navigation {:source :athena
+ :target (if parent
+ :block
+ :page)
+ :pane (if shift?
+ :right-pane
+ :main-pane)}])
+ (if parent
+ (router/navigate-uid block-uid)
+ (router/navigate-page title e))))
+
+ :class (when (= i index) "selected")})
+ [:div (use-style result-body-style)
+
+ [:h4.title (use-sub-style result-style :title) (highlight-match query title)]
+ (when string
+ [:span.preview (use-sub-style result-style :preview) (highlight-match query string)])]
+ [:span.link-leader (use-sub-style result-style :link-leader) [(r/adapt-react-class ArrowForward)]]])))])
(defn athena-component
[]
- (let [athena-open? (rf/subscribe [:athena/open])
+ (let [ref (atom nil)
+ athena-open? (rf/subscribe [:athena/open])
+ handle-click-outside (fn [e]
+ (when (and @athena-open?
+ (not (.. @ref (contains (.. e -target)))))
+ (dispatch [:athena/toggle])))
state (r/atom {:index 0
:query nil
:results []})
search-handler (create-search-handler state)]
- (fn []
- [:> Modal {:maxHeight "60vh"
- :display "flex"
- :scrollBehavior "inside"
- :outline "none"
- :motionPreset "none"
- :closeOnEsc true
- :isOpen @athena-open?
- :onClose #(dispatch [:athena/toggle])}
- [:> ModalOverlay]
- [:> ModalContent {:width "49rem"
- :class "athena-modal"
- :overflow "hidden"
- :backdropFilter "blur(20px)"
- :bg "background.vibrancy"
- :maxWidth "calc(100vw - 4rem)"}
- [:> Input
- {:type "search"
- :width "100%"
- :border 0
- :fontSize "2.375rem"
- :fontWeight "300"
- :lineHeight "1.3"
- :letterSpacing "-0.03em"
- :color "inherit"
- :background "none"
- :borderRadius 0
- :height "auto"
- :padding "1.5rem 4rem 1.5rem 1.5rem"
- :cursor "text"
- :id "athena-input"
- :auto-focus true
- :required true
- :_focus {:outline "none"}
- :sx {"::placeholder" {:color "foreground.secondary"}
- "::-webkit-search-cancel-button" {:display "none"}}
- :placeholder "Find or Create Page"
- :on-change (fn [e] (search-handler (.. e -target -value)))
- :on-key-down (fn [e] (key-down-handler e state))}]
- (when (:query @state)
- [:> IconButton {:background "none"
- :color "foreground.secondary"
- :position "absolute"
- :transition "opacity 0.1s ease, background 0.1s ease"
- :cursor "pointer"
- :border 0
- :right "2rem"
- :placeItems "center"
- :placeContent "center"
- :height "2.5rem"
- :width "2.5rem"
- :borderRadius "1000px"
- :display "flex"
- :top "2rem"
- :onClick #(set! (.-value (getElement "athena-input")) nil)}
- [:> XmarkIcon {:boxSize 6}]])
- [results-el state]
- [search-results-el @state]]])))
+ (r/create-class
+ {:display-name "athena"
+ ;; NOTE: this mouse listener stuff can go away with mechanism combining overlay and react portals
+ :component-did-mount (fn [_this] (events/listen js/document "mousedown" handle-click-outside))
+ :component-will-unmount (fn [_this] (events/unlisten js/document "mousedown" handle-click-outside))
+ :reagent-render
+ (fn []
+ (when @athena-open?
+ [:div.athena (use-style container-style
+ {:ref #(reset! ref %)})
+ [:header {:style {:position "relative"}}
+ [:input (use-style athena-input-style
+ {:type "search"
+ :id "athena-input"
+ :auto-focus true
+ :required true
+ :placeholder "Find or Create Page"
+ :on-change (fn [e] (search-handler (.. e -target -value)))
+ :on-key-down (fn [e] (key-down-handler e state))})]
+ [:button (use-style search-cancel-button-style
+ {:on-click #(set! (.-value (getElement "athena-input")) %)})
+ [:> Close]]]
+ [results-el state]
+ [search-results-el @state]]))})))
diff --git a/src/cljs/athens/views/blocks/autocomplete_search.cljs b/src/cljs/athens/views/blocks/autocomplete_search.cljs
index 279d98c48d..f9f1332d9e 100644
--- a/src/cljs/athens/views/blocks/autocomplete_search.cljs
+++ b/src/cljs/athens/views/blocks/autocomplete_search.cljs
@@ -1,8 +1,13 @@
(ns athens.views.blocks.autocomplete-search
(:require
- ["@chakra-ui/react" :refer [Portal Popover PopoverTrigger PopoverBody Button PopoverContent Text Box]]
+ ["/components/Button/Button" :refer [Button]]
+ [athens.style :as style]
[athens.views.blocks.textarea-keydown :as textarea-keydown]
- [clojure.string :as string]))
+ [athens.views.dropdown :as dropdown]
+ [clojure.string :as string]
+ [goog.events :as events]
+ [reagent.core :as r]
+ [stylefy.core :as stylefy]))
(defn inline-item-click
@@ -17,50 +22,42 @@
(defn inline-search-el
- [_block]
- (fn [block state]
- (let [{:search/keys [index results type query] caret-position :caret-position} @state
- can-open (some #(= % type) [:page :block :hashtag :template])
- {:keys [left top]} caret-position]
- [:> Popover {:isOpen can-open
- :placement "bottom-start"
- :isLazy true
- :returnFocusOnClose false
- :closeOnBlur true
- :closeOnEsc true
- :onClose #(swap! state assoc :search/type false)
- :autoFocus false
- :onMouseDown (fn [e] (.. e preventDefault))}
- [:> PopoverTrigger
- [:> Box {:position "fixed"
- :overflow "auto"
- :width "0"
- :height "0"
- :top (str (+ 24 top) "px")
- :left (str (+ 24 left) "px")}]]
- [:> Portal
- [:> PopoverContent
- [:> PopoverBody {:p 0
- :overflow "hidden"
- :borderRadius "inherit"}
- (when can-open
- (if (or (string/blank? query)
- (empty? results))
- [:> Text {:py "0.4rem"
- :px "0.8rem"
- :fontStyle "italics"}
- (str "Search for a " (symbol type))]
- (doall
- (for [[i {:keys [node/title block/string block/uid]}] (map-indexed list results)]
- [:> Button {:key (str "inline-search-item" uid)
- :id (str "dropdown-item-" i)
- :borderRadius "0"
- :justifyContent "flex-start"
- :width "100%"
- :_first {:borderTopRadius "inherit"}
- :_last {:borderBottomRadius "inherit"}
- :isActive (= index i)
- ;; if page link, expand to title. otherwise expand to uid for a block ref
- :onClick (fn [_] (inline-item-click state (:block/uid block) (or title uid)))}
- (or title string)]))))]]]])))
+ [_block state]
+ (let [ref (atom nil)
+ handle-click-outside (fn [e]
+ (let [{:search/keys [type]} @state]
+ (when (and (#{:page :block :hashtag :template} type)
+ (not (.. @ref (contains (.. e -target)))))
+ (swap! state assoc :search/type false))))]
+ (r/create-class
+ {:display-name "inline-search"
+ :component-did-mount (fn [_this] (events/listen js/document "mousedown" handle-click-outside))
+ :component-will-unmount (fn [_this] (events/unlisten js/document "mousedown" handle-click-outside))
+ :reagent-render (fn [block state]
+ (let [{:search/keys [query results index type] caret-position :caret-position} @state
+ {:keys [left top]} caret-position]
+ (when (some #(= % type) [:page :block :hashtag :template])
+ [:div (merge (stylefy/use-style dropdown/dropdown-style
+ {:ref #(reset! ref %)
+ ;; don't blur textarea when clicking to auto-complete
+ :on-mouse-down (fn [e] (.. e preventDefault))})
+ {:style {:position "absolute"
+ :max-height "20rem"
+ :z-index (:zindex-popover style/ZINDICES)
+ :top (+ 24 top)
+ :left (+ 24 left)}})
+ [:div#dropdown-menu (stylefy/use-style dropdown/menu-style)
+ (if (or (string/blank? query)
+ (empty? results))
+ ;; Just using button for styling
+ [:> Button (stylefy/use-style {:opacity (style/OPACITIES :opacity-low)}) (str "Search for a " (symbol type))]
+ (doall
+ (for [[i {:keys [node/title block/string block/uid]}] (map-indexed list results)]
+ [:> Button {:key (str "inline-search-item" uid)
+ :id (str "dropdown-item-" i)
+ :is-pressed (= index i)
+ ;; if page link, expand to title. otherwise expand to uid for a block ref
+ :on-click (fn [_] (inline-item-click state (:block/uid block) (or title uid)))
+ :style {:text-align "left"}}
+ (or title string)])))]])))})))
diff --git a/src/cljs/athens/views/blocks/autocomplete_slash.cljs b/src/cljs/athens/views/blocks/autocomplete_slash.cljs
index f89c470ea2..ce80f6be1d 100644
--- a/src/cljs/athens/views/blocks/autocomplete_slash.cljs
+++ b/src/cljs/athens/views/blocks/autocomplete_slash.cljs
@@ -1,8 +1,11 @@
(ns athens.views.blocks.autocomplete-slash
(:require
- ["@chakra-ui/react" :refer [Portal Menu MenuList MenuItem]]
+ ["/components/Button/Button" :refer [Button]]
[athens.views.blocks.textarea-keydown :as textarea-keydown]
- [reagent.core :as r]))
+ [athens.views.dropdown :as dropdown]
+ [goog.events :as events]
+ [reagent.core :as r]
+ [stylefy.core :as stylefy]))
(defn slash-item-click
@@ -13,26 +16,33 @@
(defn slash-menu-el
- [_block]
- (fn [block state]
- (let [{:search/keys [index results type] caret-position :caret-position} @state
- {:keys [left top]} caret-position]
- [:> Menu {:isOpen (= type :slash)
- :onClose #(swap! state assoc :search/type false)
- :isLazy true}
- [:> Portal
- (when (= type :slash)
- [:> MenuList {:position "absolute"
- :left (str left "px")
- :top (str (+ top 24) "px")}
- (doall
- (for [[i [text icon _expansion kbd _pos :as item]] (map-indexed list results)]
- [:> MenuItem {:key text
- :isFocusable false
- :id (str "dropdown-item-" i)
- :command kbd
- :class (when (= i index) "isActive")
- :onClick (fn [_] (slash-item-click state block item))}
- [:<>
- [(r/adapt-react-class icon)]
- text]]))])]])))
+ [_block state]
+ (let [ref (atom nil)
+ handle-click-outside (fn [e]
+ (let [{:search/keys [type]} @state]
+ (when (and (= type :slash)
+ (not (.. @ref (contains (.. e -target)))))
+ (swap! state assoc :search/type false))))]
+ (r/create-class
+ {:display-name "slash-menu"
+ :component-did-mount (fn [_this] (events/listen js/document "mousedown" handle-click-outside))
+ :component-will-unmount (fn [_this] (events/unlisten js/document "mousedown" handle-click-outside))
+ :reagent-render (fn [block state]
+ (let [{:search/keys [index results type] caret-position :caret-position} @state
+ {:keys [left top]} caret-position]
+ (when (= type :slash)
+ [:div (merge (stylefy/use-style dropdown/dropdown-style
+ {:ref #(reset! ref %)
+ ;; don't blur textarea when clicking to auto-complete
+ :on-mouse-down (fn [e] (.. e preventDefault))})
+ {:style {:position "absolute" :left (+ left 24) :top (+ top 24)}})
+ [:div#dropdown-menu (merge (stylefy/use-style dropdown/menu-style) {:style {:max-height "8em"}})
+ (doall
+ (for [[i [text icon _expansion kbd _pos :as item]] (map-indexed list results)]
+ [:> Button {:key text
+ :id (str "dropdown-item-" i)
+ :is-pressed (= i index)
+ :on-click (fn [_] (slash-item-click state block item))}
+ [:<> [(r/adapt-react-class icon)] [:span text] (when kbd [:kbd kbd])]]))]])))})))
+
+
diff --git a/src/cljs/athens/views/blocks/content.cljs b/src/cljs/athens/views/blocks/content.cljs
index 2b0a43a8d5..3d3a6977fb 100644
--- a/src/cljs/athens/views/blocks/content.cljs
+++ b/src/cljs/athens/views/blocks/content.cljs
@@ -1,10 +1,10 @@
(ns athens.views.blocks.content
(:require
- ["/components/Block/components/Content" :refer [Content]]
[athens.config :as config]
[athens.db :as db]
[athens.events.selection :as select-events]
[athens.parse-renderer :refer [parse-and-render]]
+ [athens.style :as style]
[athens.subs.selection :as select-subs]
[athens.util :as util]
[athens.views.blocks.internal-representation :as internal-representation]
@@ -12,14 +12,175 @@
[clojure.edn :as edn]
[clojure.set :as set]
[clojure.string :as str]
+ [garden.selectors :as selectors]
[goog.events :as goog-events]
[komponentit.autosize :as autosize]
- [re-frame.core :as rf])
+ [re-frame.core :as rf]
+ [stylefy.core :as stylefy])
(:import
(goog.events
EventType)))
+;; Styles
+
+(def block-content-style
+ {:display "grid"
+ :grid-template-areas "'main'"
+ :align-items "stretch"
+ :justify-content "stretch"
+ :position "relative"
+ :overflow "visible"
+ :z-index 2
+ :flex-grow "1"
+ :word-break "break-word"
+ ::stylefy/manual [[:textarea {:display "block"
+ :line-height 0
+ :-webkit-appearance "none"
+ :cursor "text"
+ :resize "none"
+ :transform "translate3d(0,0,0)"
+ :color "inherit"
+ :outline "none"
+ :overflow "hidden"
+ :padding "0"
+ :background (style/color :background-minus-1)
+ :grid-area "main"
+ :min-height "100%"
+ :caret-color (style/color :link-color)
+ :margin "0"
+ :font-size "inherit"
+ :border-radius "0.25rem"
+ :box-shadow (str "-0.25rem 0 0 0" (style/color :background-minus-1))
+ :border "0"
+ :opacity "0"
+ :font-family "inherit"}]
+ [:&:hover [:textarea [(selectors/& (selectors/not :.is-editing)) {:line-height 2}]]]
+ [:.is-editing {:z-index 3
+ :line-height "inherit"
+ :opacity "1"}]
+ [:span.text-run
+ {:pointer-events "None"}
+ [:>a {:position "relative"
+ :z-index 2
+ :pointer-events "all"}]]
+ [:span
+ {:grid-area "main"}
+ [:>span
+ :>a {:position "relative"
+ :z-index 2}]]
+ [:abbr
+ {:grid-area "main"
+ :z-index 4}
+ [:>span
+ :>a {:position "relative"
+ :z-index 2}]]
+ ;; May want to refactor specific component styles to somewhere else.
+ ;; Closer to the component perhaps?
+ ;; Code
+ [:code :pre {:font-family "IBM Plex Mono"}]
+ ;; Media Containers
+ ;; Using a CSS hack/convention here to create a responsive container
+ ;; of a specific aspect ratio.
+ ;; TODO: Replace this with the CSS aspect-ratio property once available.
+ [:.media-16-9 {:height 0
+ :width "calc(100% - 0.25rem)"
+ :z-index 1
+ :transform-origin "right center"
+ :transition "all 0.2s ease"
+ :padding-bottom (str (* (/ 9 16) 100) "%")
+ :margin-block "0.25rem"
+ :margin-inline-end "0.25rem"
+ :position "relative"}]
+ ;; Media (YouTube embeds, map embeds, etc.)
+ [:iframe {:border 0
+ :box-shadow [["inset 0 0 0 0.125rem" (style/color :background-minus-1)]]
+ :position "absolute"
+ :height "100%"
+ :width "100%"
+ :cursor "default"
+ :top 0
+ :right 0
+ :left 0
+ :bottom 0
+ :border-radius "0.25rem"}]
+ ;; Images
+ [:img {:border-radius "0.25rem"
+ :max-width "calc(100% - 0.25rem)"}]
+ ;; Checkboxes
+ ;; TODO: Refactor these complicated styles into clip paths or SVGs
+ ;; or something nicer than this
+ [:input [:& (selectors/attr= :type :checkbox) {:appearance "none"
+ :border-radius "0.25rem"
+ :cursor "pointer"
+ :color (style/color :link-color)
+ :margin-inline-end "0.25rem"
+ :position "relative"
+ :top "0.13em"
+ :width "1rem"
+ :height "1rem"
+ :transition "color 0.05s ease, transform 0.05s ease, box-shadow 0.05s ease"
+ :transform "scale(1)"
+ :box-shadow "inset 0 0 0 1px"}
+ [:&:after {:content "''"
+ :position "absolute"
+ :top "45%"
+ :left "20%"
+ :width "30%"
+ :height "50%"
+ :border-width "0 2px 2px 0"
+ :border-style "solid"
+ :opacity 0
+ :transform "rotate(45deg) translate(-40%, -50%)"}]
+ [:&:checked {:background (style/color :link-color)}
+ [:&:after {:opacity 1
+ :color (style/color :background-color)}]]
+ [:&:active {:transform "scale(0.9)"}]]]
+
+ [:h1 :h2 :h3 :h4 :h5 :h6 {:margin "0"
+ :color (style/color :body-text-color :opacity-higher)
+ :font-weight "500"}]
+ [:h1 {:padding "0"
+ :margin-block-start "-0.1em"}]
+ [:h2 {:padding "0"}]
+ [:h3 {:padding "0"}]
+ [:h4 {:padding "0.25em 0"}]
+ [:h5 {:padding "1em 0"}]
+ [:h6 {:text-transform "uppercase"
+ :letter-spacing "0.06em"
+ :padding "1em 0"}]
+ [:p {:margin "0"
+ :padding-bottom "1em"}]
+ [:blockquote {:margin-inline "0.5em"
+ :margin-block "0.125rem"
+ :padding-block "calc(0.5em - 0.125rem - 0.125rem)"
+ :padding-inline "1.5em"
+ :border-radius "0.25em"
+ :background (style/color :background-minus-1)
+ :border-inline-start [["0.25em solid" (style/color :body-text-color :opacity-lower)]]
+ :color (style/color :body-text-color :opacity-high)}
+ [:p {:padding-bottom "1em"}]
+ [:p:last-child {:padding-bottom "0"}]]
+ [:.CodeMirror {:background (style/color :background-minus-1)
+ :margin "0.125rem 0.5rem"
+ :border-radius "0.25rem"
+ :font-size "85%"
+ :color (style/color :body-text-color)
+ :font-family "IBM Plex Mono"}]
+ [:.CodeMirror-gutters {:border-right "1px solid transparent"
+ :background (style/color :background-minus-1)}]
+ [:.CodeMirror-cursor {:border-left-color (style/color :link-color)}]
+ [:.CodeMirror-lines {:padding 0}]
+ [:.CodeMirror-linenumber {:color (style/color :body-text-color :opacity-med)}]
+
+ [:mark.contents.highlight {:padding "0 0.2em"
+ :border-radius "0.125rem"
+ :background-color (style/color :text-highlight-color)}]]})
+
+
+(stylefy/class "block-content" block-content-style)
+
+
(defn find-selected-items
"Used by both shift-click and click-drag for multi-block-selection.
Given a mouse event, a source block, and a target block, highlight blocks.
@@ -238,14 +399,14 @@
2 "1.7em"
3 "1.3em"
"1em")]
- [:> Content {:fontSize font-size
- :on-click (fn [e] (.. e stopPropagation) (rf/dispatch [:editing/uid uid]))}
+ [:div {:class ["block-content"]
+ :style {:font-size font-size}
+ :on-click (fn [e] (.. e stopPropagation) (rf/dispatch [:editing/uid uid]))}
;; NOTE: komponentit forces reflow, likely a performance bottle neck
;; When block is in editing mode or the editing DOM elements are rendered
- ;; (when (or (:show-editable-dom @state) @editing?)
- (when true
+ (when (or (:show-editable-dom @state) @editing?)
[autosize/textarea {:value (:string/local @state)
- :class ["block-input-textarea" (when (and (empty? @selected-items) @editing?) "is-editing")]
+ :class ["textarea" (when (and (empty? @selected-items) @editing?) "is-editing")]
;; :auto-focus true
:id (str "editable-uid-" uid)
:on-change (fn [e] (textarea-change e uid state))
diff --git a/src/cljs/athens/views/blocks/context_menu.cljs b/src/cljs/athens/views/blocks/context_menu.cljs
index 8f8135f4cf..fbc3dfbae1 100644
--- a/src/cljs/athens/views/blocks/context_menu.cljs
+++ b/src/cljs/athens/views/blocks/context_menu.cljs
@@ -1,15 +1,19 @@
(ns athens.views.blocks.context-menu
(:require
+ ["/components/Button/Button" :refer [Button]]
[athens.db :as db]
[athens.listeners :as listeners]
[athens.subs.selection :as select-subs]
- [athens.util :refer [toast]]
+ [athens.views.dropdown :refer [menu-style dropdown-style]]
[clojure.string :as string]
- [re-frame.core :as rf]))
+ [goog.events :as events]
+ [re-frame.core :as rf]
+ [reagent.core :as r]
+ [stylefy.core :as stylefy]))
-(defn handle-copy-refs
- [_ uid]
+(defn copy-refs-mouse-down
+ [_ uid state]
(let [selected-items @(rf/subscribe [::select-subs/items])
;; use this when using datascript-transit
;; uids (map (fn [x] [:block/uid x]) selected-items)
@@ -19,12 +23,23 @@
(->> (map (fn [uid] (str "((" uid "))\n")) selected-items)
(string/join "")))]
(.. js/navigator -clipboard (writeText data))
- (toast (clj->js {:title "Copied ref to clipboard"}))))
+ (swap! state assoc :context-menu/show false)))
+
+
+(defn bullet-context-menu
+ "Handle right click. If no blocks are selected, just give option for copying current block's uid."
+ [e _uid state]
+ (.. e preventDefault)
+ (let [rect (.. e -target getBoundingClientRect)]
+ (swap! state assoc
+ :context-menu/x (.. rect -left)
+ :context-menu/y (.. rect -bottom)
+ :context-menu/show true)))
(defn handle-copy-unformatted
"If copying only a single block, dissoc children to not copy subtree."
- [^js uid]
+ [^js e uid state]
(let [uids @(rf/subscribe [::select-subs/items])]
(if (empty? uids)
(let [block (dissoc (db/get-block [:block/uid uid]) :block/children)
@@ -34,4 +49,36 @@
(map #(listeners/blocks-to-clipboard-data 0 % true))
(apply str))]
(.. js/navigator -clipboard (writeText data)))))
- (toast (clj->js {:title "Copied content to clipboard" :status "success"})))
+ (.. e preventDefault)
+ (swap! state assoc :context-menu/show false))
+
+
+(defn context-menu-el
+ "Only option in context menu right now is copy block ref(s)."
+ [_block state]
+ (let [ref (atom nil)
+ handle-click-outside (fn [e]
+ (when (and (:context-menu/show @state)
+ (not (.. @ref (contains (.. e -target)))))
+ (swap! state assoc :context-menu/show false)))]
+ (r/create-class
+ {:display-name "context-menu"
+ :component-did-mount (fn [_this] (events/listen js/document "mousedown" handle-click-outside))
+ :component-will-unmount (fn [_this] (events/unlisten js/document "mousedown" handle-click-outside))
+ :reagent-render (fn [block state]
+ (let [{:block/keys [uid]} block
+ {:context-menu/keys [x y show]} @state
+ selected-items @(rf/subscribe [::select-subs/items])]
+ (when show
+ [:div (merge (stylefy/use-style dropdown-style
+ {:ref #(reset! ref %)})
+ {:style {:position "fixed"
+ :left (str x "px")
+ :top (str y "px")}})
+ [:div (stylefy/use-style menu-style)
+ [:> Button {:on-mouse-down (fn [e] (copy-refs-mouse-down e uid state))}
+ (if (empty? selected-items)
+ "Copy block ref"
+ "Copy block refs")]
+ [:> Button {:on-mouse-down (fn [e] (handle-copy-unformatted e uid state))}
+ "Copy unformatted"]]])))})))
diff --git a/src/cljs/athens/views/blocks/core.cljs b/src/cljs/athens/views/blocks/core.cljs
index d32b8df721..ebe6ca4b45 100644
--- a/src/cljs/athens/views/blocks/core.cljs
+++ b/src/cljs/athens/views/blocks/core.cljs
@@ -1,9 +1,8 @@
(ns athens.views.blocks.core
(:require
["/components/Block/components/Anchor" :refer [Anchor]]
- ["/components/Block/components/Container" :refer [Container]]
["/components/Block/components/Toggle" :refer [Toggle]]
- ["@chakra-ui/react" :refer [VStack Button Breadcrumb BreadcrumbItem BreadcrumbLink HStack]]
+ ["/components/Button/Button" :refer [Button]]
[athens.common.logging :as log]
[athens.db :as db]
[athens.electron.images :as images]
@@ -13,19 +12,21 @@
[athens.reactive :as reactive]
[athens.router :as router]
[athens.self-hosted.presence.views :as presence]
+ [athens.style :as style]
[athens.subs.selection :as select-subs]
[athens.util :as util :refer [mouse-offset vertical-center specter-recursive-path]]
[athens.views.blocks.autocomplete-search :as autocomplete-search]
[athens.views.blocks.autocomplete-slash :as autocomplete-slash]
[athens.views.blocks.bullet :refer [bullet-drag-start bullet-drag-end]]
[athens.views.blocks.content :as content]
- [athens.views.blocks.context-menu :refer [handle-copy-unformatted handle-copy-refs]]
+ [athens.views.blocks.context-menu :as context-menu]
[athens.views.blocks.drop-area-indicator :as drop-area-indicator]
- [athens.views.references :refer [reference-group reference-block]]
+ [athens.views.breadcrumbs :as breadcrumbs]
[com.rpl.specter :as s]
[goog.functions :as gfns]
[re-frame.core :as rf]
- [reagent.core :as r]))
+ [reagent.core :as r]
+ [stylefy.core :as stylefy]))
;; Styles
@@ -35,63 +36,81 @@
;; smaller than main content blocks, for instance.
-(def block-container-inner-style
- {"&.show-tree-indicator:before" {:content "''"
- :position "absolute"
- :width "1px"
- :left "calc(1.375em + 1px)"
- :top "2em"
- :bottom "0"
- :opacity "0"
- :transform "translateX(50%)"
- :transition "background-color 0.2s ease-in-out, opacity 0.2s ease-in-out"
- :background "separator.divider"}
- "&:hover.show-tree-indicator:before, &:focus-within.show-tree-indicator:before" {:opacity 1}
- "&:after" {:content "''"
- :zIndex 0
- :position "absolute"
- :inset "1px 0"
- :opacity 0
- :pointerEvents "none"
- :borderRadius "sm"
- :transition "opacity 0.075s ease-in-out"
- :background "link"}
- "&.is-selected:after" {:opacity 0.2}
- "&.is-presence .block-content" {:padding-right "1rem"}
- ".user-avatar" {:position "absolute"
- :left "4px"
- :top "4px"}
- ".block-body" {:display "grid"
- :gridTemplateColumns "1em 1em 1fr auto"
- :gridTemplateRows "0 1fr 0"
- :gridTemplateAreas "
+(def block-container-style
+ {:display "flex"
+ :line-height "2em"
+ :position "relative"
+ :border-radius "0.125rem"
+ :justify-content "flex-start"
+ :flex-direction "column"
+ ::stylefy/manual [[:&.show-tree-indicator:before {:content "''"
+ :position "absolute"
+ :width "1px"
+ :left "calc(1.375em + 1px)"
+ :top "2em"
+ :bottom "0"
+ :opacity "0"
+ :transform "translateX(50%)"
+ :transition "background-color 0.2s ease-in-out, opacity 0.2s ease-in-out"
+ :background (style/color :border-color)}]
+ [:&:hover
+ :&:focus-within [:&.show-tree-indicator:before {:opacity "1"}]]
+ [:&:after {:content "''"
+ :z-index -1
+ :position "absolute"
+ :top "0.75px"
+ :right 0
+ :bottom "0.75px"
+ :left 0
+ :opacity 0
+ :pointer-events "none"
+ :border-radius "0.25rem"
+ :transition "opacity 0.075s ease"
+ :background (style/color :link-color :opacity-lower)}]
+ [:&.is-selected:after {:opacity 1}]
+ [:&.is-presence [:.block-content {:padding-right "1rem"}]]
+ [:.user-avatar {:position "absolute"
+ :left "4px"
+ :top "4px"}]
+ [:.block-body {:display "grid"
+ :grid-template-columns "1em 1em 1fr auto"
+ :grid-template-rows "0 1fr 0"
+ :grid-template-areas "
'above above above above'
'toggle bullet content refs'
'below below below below'"
- :borderRadius "0.5rem"
- :position "relative"}
- "&:hover > .block-toggle, &:focus-within > .block-toggle" {:opacity "1"}
- "button.block-edit-toggle" {:position "absolute"
- :appearance "none"
- :width "100%"
- :background "none"
- :border 0
- :cursor "text"
- :display "block"
- :z-index 1
- :top 0
- :right 0
- :bottom 0
- :left 0}
- ".block-embed" {:borderRadius "sm"
- :sx {"--block-surface-color" "background.basement"}
- :bg "background.basement"
- ".block-container" {:marginLeft 0.5}}
- ".block-content" {:gridArea "content"
- :minHeight "1.5em"}
- "&.is-linked-ref" {:bg "background-attic"}
- ".block-container" {:marginLeft "2rem"
- :gridArea "body"}})
+ :border-radius "0.5rem"
+ :position "relative"}
+ [:&:hover
+ :&:focus-within ["> .block-toggle" {:opacity "1"}]]
+ [:button.block-edit-toggle {:position "absolute"
+ :appearance "none"
+ :width "100%"
+ :background "none"
+ :border 0
+ :cursor "text"
+ :display "block"
+ :z-index 1
+ :top 0
+ :right 0
+ :bottom 0
+ :left 0}]]
+ [:.block-content {:grid-area "content"
+ :min-height "1.5em"}]
+ [:&.is-linked-ref {:background-color (style/color :background-plus-2)}]
+ ;; Inset child blocks
+ [:.block-container {:margin-left "2rem"
+ :grid-area "body"}]]})
+
+
+(stylefy/class "block-container" block-container-style)
+
+
+(def dragging-style
+ {:opacity "0.25"})
+
+
+(stylefy/class "dragging" dragging-style)
;; Inline refs
@@ -102,6 +121,21 @@
(declare block-el)
+(def reference-breadcrumbs-style
+ {:font-size "12px"
+ :padding "0 0.25em"})
+
+
+(def reference-breadcrumbs-container-style
+ {:padding-left "0.5em"
+ :display "grid"
+ :grid-template-columns "1em 1fr"
+ :grid-template-rows "1fr"
+ :grid-template-areas "'toggle breadcrumbs'"
+ :border-radius "0.5rem"
+ :position "relative"})
+
+
(defn ref-comp
[block parent-state]
(let [orig-uid (:block/uid block)
@@ -125,33 +159,33 @@
(let [{:keys [block parents embed-id]} @state
block (reactive/get-reactive-block-document (:db/id block))]
[:<>
- [:> HStack
+ [:div (stylefy/use-style reference-breadcrumbs-container-style)
[:> Toggle {:isOpen (:open? @state)
:on-click (fn [e]
(.. e stopPropagation)
(swap! state update :open? not))}]
- [:> Breadcrumb {:fontSize "0.7em"}
+ [breadcrumbs/breadcrumbs-list {:style reference-breadcrumbs-style}
(doall
(for [{:keys [node/title block/string block/uid] :as breadcrumb-block}
(if (or (:open? @state) (not (:focus? @state)))
parents
(conj parents block))]
- [:> BreadcrumbItem {:key (str "breadcrumb-" uid)}
- [:> BreadcrumbLink {:onClick #(let [new-B (db/get-block [:block/uid uid])
- new-P (concat
- (take-while (fn [b] (not= (:block/uid b) uid)) parents)
- [breadcrumb-block])]
- (.. % stopPropagation)
- (swap! state assoc :block new-B :parents new-P :focus? false))}
- [parse-renderer/parse-and-render (or title string) uid]]]))]]
+ [breadcrumbs/breadcrumb {:key (str "breadcrumb-" uid)
+ :on-click #(let [new-B (db/get-block [:block/uid uid])
+ new-P (concat
+ (take-while (fn [b] (not= (:block/uid b) uid)) parents)
+ [breadcrumb-block])]
+ (.. % stopPropagation)
+ (swap! state assoc :block new-B :parents new-P :focus? false))}
+ [parse-renderer/parse-and-render (or title string) uid]]))]]
(when (:open? @state)
(if (:focus? @state)
;; Display the single child block only when focusing.
;; This is the default behaviour for a ref without children, for brevity.
- [:div.block-embed {:fontSize "0.7em"}
+ [:div.block-embed
[block-el
(util/recursively-modify-block-for-embed block embed-id)
linked-ref-data
@@ -167,47 +201,55 @@
{:block-embed? true}]])))]))))
+(def references-style
+ {:padding-left "2em"})
+
+
+(def references-list-style
+ {:font-size "14px"})
+
+
+(def references-group-style
+ {:background (style/color :background-minus-2 :opacity-med)
+ :padding "0rem 0.5rem"
+ :border-radius "0.25rem"
+ :margin "0.5em 0"})
+
+
+(def references-group-block-style
+ {:width "100%"
+ ::stylefy/manual [[:&:first-of-type {:border-top "0"
+ :margin-block-start "0"}]]})
+
+
(defn inline-linked-refs-el
[state uid]
(let [refs (reactive/get-reactive-linked-references [:block/uid uid])]
(when (not-empty refs)
- [:> VStack {:as "aside"
- :align "stretch"
- :key "Inline Linked References"
- :zIndex 2
- :mt 2
- :mb 4
- :ml 6
- :py 2
- :px 4
- :borderRadius "sm"
- :background "background.basement"}
- (doall
- (for [[group-title group] refs]
- [reference-group {:title group-title
- :key (str "group-" group-title)}
- (doall
- (for [block' group]
- [reference-block {:key (str "ref-" (:block/uid block'))}
- [ref-comp block' state]]))]))])))
+ [:div (stylefy/use-style references-style {:key "Inline Linked References"})
+ [:section
+ [:div (stylefy/use-style references-list-style)
+ (doall
+ (for [[group-title group] refs]
+ [:div (stylefy/use-style references-group-style {:key (str "group-" group-title)})
+ (doall
+ (for [block' group]
+ [:div (stylefy/use-style references-group-block-style {:key (str "ref-" (:block/uid block'))})
+ [ref-comp block' state]]))]))]]])))
;; Components
(defn block-refs-count-el
- [count click-fn active?]
- [:> Button {:gridArea "refs"
- :size "xs"
- :ml "1em"
- :mt 1
- :mr 1
- :zIndex 10
- :visibility (if (pos? count) "visible" "hidden")
- :isActive active?
- :onClick (fn [e]
- (.. e stopPropagation)
- (click-fn e))}
- count])
+ [count click-fn]
+ [:div (stylefy/use-style {:margin-left "1em"
+ :grid-area "refs"
+ :z-index (:zindex-dropdown style/ZINDICES)
+ :visibility (when-not (pos? count) "hidden")})
+ [:> Button {:on-click (fn [e]
+ (.. e stopPropagation)
+ (click-fn e))}
+ count]])
(defn block-drag-over
@@ -387,6 +429,7 @@
(assoc block :block/uid (or original-uid uid)))
block)
{:keys [dragging]} @state
+ is-editing @(rf/subscribe [:editing/is-editing uid])
is-selected @(rf/subscribe [::select-subs/selected? uid])
present-user @(rf/subscribe [:presence/has-presence uid])
is-presence (seq present-user)]
@@ -399,29 +442,28 @@
(when (not= string (:string/previous @state))
(swap! state assoc :string/previous string :string/local string))
- [:> Container {:sx (merge block-container-inner-style
- {"--block-surface-color" "background.floor"})
- :isDragging (and dragging (not is-selected))
- ;; :isEditing is-editing
- :isSelected is-selected
- :hasChildren (seq children)
- :isOpen open
- :isLinkedRef (and (false? initial-open) (= uid linked-ref-uid))
- :hasPresence is-presence
- :uid uid
- ;; need to know children for selection resolution
- :childrenUids children-uids
- ;; :show-editable-dom allows us to render the editing elements (like the textarea)
- ;; even when not editing this block. When true, clicking the block content will pass
- ;; the clicks down to the underlying textarea. The textarea is expensive to render,
- ;; so we avoid rendering it when it's not needed.
- :onMouseEnter #(swap! state assoc :show-editable-dom true)
- :onMouseLeave #(swap! state assoc :show-editable-dom false)
- :onDragOver (fn [e] (block-drag-over e block state))
- :onDragLeave (fn [e] (block-drag-leave e block state))
- :onDrop (fn [e] (block-drop e block state))}
-
- (when (= (:drag-target @state) :before) [drop-area-indicator/drop-area-indicator {:placement "above"}])
+ [:div
+ {:class ["block-container"
+ (when (and dragging (not is-selected)) "dragging")
+ (when is-editing "is-editing")
+ (when is-selected "is-selected")
+ (when (and (seq children) open) "show-tree-indicator")
+ (when (and (false? initial-open) (= uid linked-ref-uid)) "is-linked-ref")
+ (when is-presence "is-presence")]
+ :data-uid uid
+ ;; need to know children for selection resolution
+ :data-childrenuids children-uids
+ ;; :show-editable-dom allows us to render the editing elements (like the textarea)
+ ;; even when not editing this block. When true, clicking the block content will pass
+ ;; the clicks down to the underlying textarea. The textarea is expensive to render,
+ ;; so we avoid rendering it when it's not needed.
+ :on-mouse-enter #(swap! state assoc :show-editable-dom true)
+ :on-mouse-leave #(swap! state assoc :show-editable-dom false)
+ :on-drag-over (fn [e] (block-drag-over e block state))
+ :on-drag-leave (fn [e] (block-drag-leave e block state))
+ :on-drop (fn [e] (block-drop e block state))}
+
+ (when (= (:drag-target @state) :before) [drop-area-indicator/drop-area-indicator {:grid-area "above"}])
[:div.block-body
(when (seq children)
@@ -429,28 +471,28 @@
(and (false? linked-ref) open))
true
false)
- :onClick (fn [e]
- (.. e stopPropagation)
- (if (true? linked-ref)
- (swap! state update :linked-ref/open not)
- (toggle uid (not open))))}])
+ :on-click (fn [e]
+ (.. e stopPropagation)
+ (if (true? linked-ref)
+ (swap! state update :linked-ref/open not)
+ (toggle uid (not open))))}])
+ (when (:context-menu/show @state)
+ [context-menu/context-menu-el uid-sanitized-block state])
[:> Anchor {:isClosedWithChildren (when (and (seq children)
(or (and (true? linked-ref) (not (:linked-ref/open @state)))
(and (false? linked-ref) (not open))))
"closed-with-children")
:block block
- :uidSanitizedBlock uid-sanitized-block
:shouldShowDebugDetails (util/re-frame-10x-open?)
- :onCopyRef #(handle-copy-refs nil uid)
- :onCopyUnformatted #(handle-copy-unformatted uid)
- :onClick (fn [e]
- (let [shift? (.-shiftKey e)]
- (rf/dispatch [:reporting/navigation {:source :block-bullet
- :target :block
- :pane (if shift?
- :right-pane
- :main-pane)}])
- (router/navigate-uid uid e)))
+ :on-click (fn [e]
+ (let [shift? (.-shiftKey e)]
+ (rf/dispatch [:reporting/navigation {:source :block-bullet
+ :target :block
+ :pane (if shift?
+ :right-pane
+ :main-pane)}])
+ (router/navigate-uid uid e)))
+ :on-context-menu (fn [e] (context-menu/bullet-context-menu e uid state))
:on-drag-start (fn [e] (bullet-drag-start e uid state))
:on-drag-end (fn [e] (bullet-drag-end e uid state))}]
[content/block-content-el block state]
@@ -458,13 +500,10 @@
[presence/inline-presence-el uid]
(when (and (> (count _refs) 0) (not= :block-embed? opts))
- [block-refs-count-el
- (count _refs)
- (fn [e]
- (if (.. e -shiftKey)
- (rf/dispatch [:right-sidebar/open-item uid])
- (swap! state update :inline-refs/open not)))
- (:inline-refs/open @state)])]
+ [block-refs-count-el (count _refs) (fn [e]
+ (if (.. e -shiftKey)
+ (rf/dispatch [:right-sidebar/open-item uid])
+ (swap! state update :inline-refs/open not)))])]
[autocomplete-search/inline-search-el block state]
[autocomplete-slash/slash-menu-el block state]
@@ -485,6 +524,6 @@
(assoc linked-ref-data :initial-open (contains? parent-uids (:block/uid child)))
opts]]))
- (when (= (:drag-target @state) :first) [drop-area-indicator/drop-area-indicator {:placement "below" :child? true}])
- (when (= (:drag-target @state) :after) [drop-area-indicator/drop-area-indicator {:placement "below"}])])))))
+ (when (= (:drag-target @state) :first) [drop-area-indicator/drop-area-indicator {:style {:grid-area "below"} :child true}])
+ (when (= (:drag-target @state) :after) [drop-area-indicator/drop-area-indicator {:style {:grid-area "below"}}])])))))
diff --git a/src/cljs/athens/views/blocks/drop_area_indicator.cljs b/src/cljs/athens/views/blocks/drop_area_indicator.cljs
index 2d9c532a96..93705f7264 100644
--- a/src/cljs/athens/views/blocks/drop_area_indicator.cljs
+++ b/src/cljs/athens/views/blocks/drop_area_indicator.cljs
@@ -1,17 +1,47 @@
(ns athens.views.blocks.drop-area-indicator
(:require
- ["@chakra-ui/react" :refer [Box]]))
+ [athens.style :as style]
+ [stylefy.core :as stylefy]))
+
+
+(def drop-area-indicator-style
+ {:display "block"
+ :height "1px"
+ :pointer-events "none"
+ :margin-bottom "-1px"
+ :opacity (:opacity-high style/OPACITIES)
+ :color (style/color :link-color)
+ :position "relative"
+ :transform-origin "left"
+ :z-index 3
+ :width "100%"
+ ::stylefy/manual [["&:after" {:position "absolute"
+ :content "''"
+ :top "-0.5px"
+ :right "0"
+ :bottom "-0.5px"
+ :left "calc(2em - 4px)"
+ :border-radius "100px"
+ :background "currentColor"}]
+ ["&.child" {:--indent "1.95em"
+ :width "calc(100% - var(--indent))"
+ :margin-left "var(--indent)"}]
+ ["&.child:after" {:border-top-left-radius 0
+ :border-bottom-left-radius 0}]
+ ["&.child:before" {:position "absolute"
+ :content "''"
+ :border-radius "10em"
+ :border "2px solid "
+ :--size "4px"
+ :width "var(--size)"
+ :height "var(--size)"
+ :left "var(--indent)"
+ :top "50%"
+ :transform "translateY(-50%) translateX(-100%) translateX(-2px)"}]]})
(defn drop-area-indicator
- ([{:keys [placement child?]}]
- [:> Box {:display "block"
- :height "1px"
- :pointerEvents "none"
- :background "link"
- :gridArea (if (= placement "above") "above" "below")
- :marginLeft (if child? "4rem" "2rem")
- :marginBottom "-1px"
- :position "relative"
- :transformOrigin "left"
- :zIndex 3}]))
+ ([{:keys [style child]}]
+ [:div (stylefy/use-style
+ (merge drop-area-indicator-style style)
+ {:class (when child "child")})]))
diff --git a/src/cljs/athens/views/blocks/textarea_keydown.cljs b/src/cljs/athens/views/blocks/textarea_keydown.cljs
index 8ef494790a..308252f8f5 100644
--- a/src/cljs/athens/views/blocks/textarea_keydown.cljs
+++ b/src/cljs/athens/views/blocks/textarea_keydown.cljs
@@ -21,7 +21,6 @@
[goog.dom.selection :refer [setStart setEnd getText setCursorPosition getEndPoints]]
[goog.events.KeyCodes :refer [isCharacterKey]]
[goog.functions :refer [throttle #_debounce]]
- [goog.style :refer [getClientPosition]]
[re-frame.core :as rf :refer [dispatch dispatch-sync subscribe]])
(:import
(goog.events
@@ -818,12 +817,8 @@
;; update caret position for search dropdowns and for up/down
(when (nil? (:search/type @state))
-
- (let [caret-position (get-caret-position (.. e -target))
- textarea-position (js->clj (getClientPosition (.. e -target)) :keywordize-keys true)
- position {:left (+ (:left caret-position) (.. textarea-position -x))
- :top (+ (:top caret-position) (.. textarea-position -y))}]
- (swap! state assoc :caret-position position)))
+ (let [caret-position (get-caret-position (.. e -target))]
+ (swap! state assoc :caret-position caret-position)))
;; dispatch center
;; only when nothing is selected or duplicate/events dispatched
diff --git a/src/cljs/athens/views/breadcrumbs.cljs b/src/cljs/athens/views/breadcrumbs.cljs
new file mode 100644
index 0000000000..ddd1237b96
--- /dev/null
+++ b/src/cljs/athens/views/breadcrumbs.cljs
@@ -0,0 +1,68 @@
+(ns athens.views.breadcrumbs
+ (:require
+ [athens.db]
+ [athens.style :refer [color OPACITIES]]
+ [stylefy.core :as stylefy :refer [use-style]]))
+
+
+;; Styles
+
+
+(def breadcrumbs-list-style
+ {:list-style "none"
+ :display "flex"
+ :flex "1 1 auto"
+ :margin "0"
+ :padding "0"
+ :flex-direction "row"
+ :overflow "hidden"
+ :height "inherit"
+ :align-items "stretch"
+ :flex-wrap "nowrap"
+ :color (color :body-text-color :opacity-high)
+ ::stylefy/manual [[:svg {:font-size "inherit"
+ :color "inherit"
+ :margin "auto 0"}]]})
+
+
+(def breadcrumb-style
+ {:flex "0 1 auto"
+ :overflow "hidden"
+ :max-width "100%"
+ :min-width "2.5em"
+ :white-space "nowrap"
+ :text-overflow "ellipsis"
+ :transition "color 0.3s ease"
+ ::stylefy/manual [[:a {:text-decoration "none"
+ :cursor "pointer"
+ :position "relative"
+ :color "inherit"}]
+ [:* {:display "inline"
+ :margin 0
+ :padding 0
+ :font-size "inherit"}]
+ [:&:last-child {:color (color :body-text-color)}]
+ [:&:hover {:flex-shrink "0"
+ :color (color :link-color)}]
+ [:&:before {:display "inline-block"
+ :padding "0 0.15em"
+ :content "'>'"
+ :opacity (:opacity-low OPACITIES)
+ :transform "scaleX(0.5)"}]
+ [:&:first-child:before {:content "none"}]]})
+
+
+;; Components
+
+
+(defn breadcrumbs-list
+ [{:keys [style]} & children]
+ (into [:ol (use-style (merge breadcrumbs-list-style style))] children))
+
+
+(defn breadcrumb
+ ([children] [breadcrumb {} children])
+ ([{:keys [style] :as props} children]
+ [:li (use-style (merge breadcrumb-style style))
+ [:a (merge props)
+ children]]))
diff --git a/src/cljs/athens/views/devtool.cljs b/src/cljs/athens/views/devtool.cljs
index 41ee5291b3..51011924ea 100644
--- a/src/cljs/athens/views/devtool.cljs
+++ b/src/cljs/athens/views/devtool.cljs
@@ -1,25 +1,126 @@
(ns athens.views.devtool
(:require
- ["@chakra-ui/react" :refer [Box Button Table Thead Tbody Th Tr Td Input ButtonGroup]]
+ ["/components/Button/Button" :refer [Button]]
["@material-ui/icons/ChevronLeft" :default ChevronLeft]
["@material-ui/icons/Clear" :default Clear]
+ ["@material-ui/icons/History" :default History]
+ ["@material-ui/icons/ShortText" :default ShortText]
[athens.config :as config]
[athens.db :as db :refer [dsdb]]
+ [athens.style :refer [color]]
+ [athens.views.textinput :refer [textinput-style]]
[cljs.pprint :as pp]
[clojure.core.protocols :as core-p]
[clojure.datafy :refer [nav datafy]]
[datascript.core :as d]
[datascript.db]
+ [komponentit.autosize :as autosize]
[me.tonsky.persistent-sorted-set]
[re-frame.core :refer [subscribe dispatch]]
[reagent.core :as r]
[reagent.ratom]
- [sci.core :as sci])
+ [sci.core :as sci]
+ [stylefy.core :as stylefy :refer [use-style]])
(:import
(goog.events
KeyCodes)))
+;; Styles
+
+
+(def container-style
+ {:grid-area "devtool"
+ :flex-direction "column"
+ :background (color :background-minus-1)
+ :position "relative"
+ :width "100vw"
+ :height "33vh"
+ :display "flex"
+ :overflow-y "auto"
+ :right 0
+ :z-index 2})
+
+
+(def tabs-style
+ {:padding "0 0.5rem"
+ :flex "0 0 auto"
+ :background (color :background-minus-1)
+ :display "flex"
+ :align-items "stretch"
+ :justify-content "space-between"
+ ::stylefy/manual [[:button {:border-radius "0"}]]})
+
+
+(def tabs-section-style
+ {:display "flex"
+ :align-items "stretch"})
+
+
+(def panels-style
+ {:overflow-y "auto"
+ :padding "0.5rem"})
+
+
+(def current-location-style
+ {:display "flex"
+ :align-items "center"
+ :flex "1 1 100%"
+ :font-size "14px"
+ :border-bottom [["1px solid" (color :background-minus-1) 10]]})
+
+
+(def current-location-name-style
+ {:font-weight "bold"
+ :font-size "inherit"
+ :margin-block "0"
+ :margin-inline-start "1em"
+ :margin-inline-end "1em"})
+
+
+(def current-location-controls-style {:margin-inline-start "1em"})
+
+
+(def devtool-table-style
+ {:border-collapse "collapse"
+ :font-size "12px"
+ :font-family "IBM Plex Sans Condensed"
+ :letter-spacing "-0.01em"
+ :margin "0.5rem 0 0"
+ :border-spacing "0"
+ :min-width "100%"
+ ::stylefy/manual [[:td {:border-top [["1px solid " (color :border-color)]]
+ :padding "0.125rem"}]
+ [:tbody {:vertical-align "top"}]
+ [:th {:text-align "left" :padding "0.125rem 0.125rem" :white-space "nowrap"}]
+ [:tr {:transition "all 0.05s ease"}]
+ [:td:first-child :th:first-child {:padding-left "0.5rem"}]
+ [:td:last-child :th-last-child {:padding-right "0.5rem"}]
+ [:tbody [:tr:hover {:cursor "pointer"
+ :background (color :background-minus-1)
+ :color (color :header-text-color)}]]
+ [:td>ul {:padding "0"
+ :margin "0"
+ :list-style "none"}]
+ [:td [:li {:margin "0 0 0.25rem"
+ :padding-top "0.25rem" ;
+ :border-top (str "1px solid " (color :border-color))}]]
+ [:td [:li:first-child {:border-top "none" :margin-top "0" :padding-top "0"}]]
+ [:a {:color (color :link-color)}]
+ [:a:hover {:text-decoration "underline"}]]})
+
+
+(def edn-viewer-style {:font-size "12px"})
+
+
+(def query-input-style
+ (merge textinput-style {:width "100%"
+ :min-height "2.5rem"
+ :font-size "12px"
+ :background (color :background-color)
+ :font-family "IBM Plex Mono"}))
+
+
;; Components
@@ -72,34 +173,36 @@
[_ _ _]
(let [limit (r/atom 20)]
(fn [headers rows add-nav!]
- [:> Box {:width "100%"}
- [:> Table {:width "100%"}
- [:> Thead
- [:> Tr (for [h headers]
- ^{:key h} [:> Th h])]]
- [:> Tbody
+ [:div
+ [:table (use-style devtool-table-style)
+ [:thead
+ [:tr (for [h headers]
+ ^{:key h} [:th h])]]
+ [:tbody
(doall
(for [row (take @limit rows)]
^{:key row}
- [:> Tr {:on-click #(add-nav! [(first row)
- (-> row meta :row-value)])}
+ [:tr {:on-click #(add-nav! [(first row)
+ (-> row meta :row-value)])}
(for [i (range (count row))]
(let [cell (get row i)]
^{:key (str row i cell)}
- [:> Td (if (nil? cell)
- ""
- (pr-str cell))]))]))]] ; use the edn-viewer here as well?
+ [:td (if (nil? cell)
+ ""
+ (pr-str cell))]))]))]] ; use the edn-viewer here as well?
(when (< @limit (count rows))
- [:> Button {:onClick #(swap! limit + 10)
- :width "100%"}
+ [:> Button {:on-click #(swap! limit + 10)
+ :style {:width "100%"
+ :justify-content "center"
+ :margin "0.25rem 0"}}
"Load More"])])))
;; TODO add truncation of long strings here
(defn edn-viewer
[data _]
- [:> Box {:as "pre" :fontSize "12px"} [:code (with-out-str (pp/pprint data))]])
+ [:pre (use-style edn-viewer-style) [:code (with-out-str (pp/pprint data))]])
(defn coll-viewer
@@ -215,13 +318,13 @@
applicable-vs (applicable-viewers datafied-data)
viewer-name (or (:viewer @state) (first applicable-vs))
viewer (get-in indexed-viewers [viewer-name :athens.viewer/fn])]
- [:> Box
- [:> Box {:display "flex"
- :flexDirection "row"
- :flexWrap "no-wrap"
- :alignItems "stretch"
- :justifyContent "space-between"}
- [:> Box
+ [:div
+ [:div {:style {:display "flex"
+ :flex-direction "row"
+ :flex-wrap "no-wrap"
+ :align-items "stretch"
+ :justify-content "space-between"}}
+ [:div (use-style current-location-style)
(doall
(for [i (-> navs count range)]
(let [nav (get navs i)]
@@ -232,8 +335,8 @@
(update :navs subvec 0 i)
(dissoc :viewer))))}
[:<> [:> ChevronLeft] [:span (first nav)]]])))
- [:h3 (pr-str (type navved-data))]
- [:div
+ [:h3 (use-style current-location-name-style) (pr-str (type navved-data))]
+ [:div (use-style current-location-controls-style)
[:span "View as "]
(for [v applicable-vs]
(let [click-fn #(swap! state assoc :viewer v)]
@@ -331,16 +434,12 @@
(defn query-component
[{:keys [eval-str result error]}]
- [:div {:style {:height "100%"}}
- [:> Input {:value eval-str
- :width "100%"
- :minHeight "2.5rem"
- :fontSize "12px"
- :background "background.basement"
- :fontFamily "code"
- :resize "none"
- :on-change handle-box-change!
- :on-key-down handle-box-key-down!}]
+ [:div (use-style {:height "100%"})
+ [autosize/textarea (use-style query-input-style
+ {:value eval-str
+ :resize "none"
+ :on-change handle-box-change!
+ :on-key-down handle-box-key-down!})]
(if-not error
[data-browser result]
[error-component result])])
@@ -353,7 +452,7 @@
(defn devtool-close-el
[]
- [:> Button {:onClick #(dispatch [:devtool/toggle])}
+ [:> Button {:on-click #(dispatch [:devtool/toggle])}
[:> Clear]])
@@ -362,28 +461,17 @@
(when devtool?
(let [{:keys [active-panel]} @state
switch-panel (fn [panel] (swap! state assoc :active-panel panel))]
- [:> Box {:gridArea "devtool"
- :flexDirection "column"
- :background "background.basement"
- :position "relative"
- :width "100vw"
- :height "33vh"
- :display "flex"
- :overflowY "auto"
- :right 0
- :zIndex 2}
- [:> ButtonGroup
- [:> Button {:onClick #(switch-panel :query)
- :isActive (= active-panel :query)}
- "Query"]
- [:> Button {:onClick #(switch-panel :txes)
- :mr "auto"
- :isActive (= active-panel :txes)}
- "Transactions"]
-
+ [:div (use-style container-style)
+ [:nav (use-style tabs-style)
+ [:div (use-style tabs-section-style)
+ [:> Button {:on-click #(switch-panel :query)
+ :is-pressed (= active-panel :query)}
+ [:<> [:> ShortText] [:span "Query"]]]
+ [:> Button {:on-click #(switch-panel :txes)
+ :is-pressed (= active-panel :txes)}]
+ [:<> [:> History] [:span "Transactions"]]]
[devtool-close-el]]
- [:> Box {:overflowY "auto"
- :padding "0.5rem"}
+ [:div (use-style panels-style)
(case active-panel
:query [query-component @state]
:txes [txes-component @state])]])))
diff --git a/src/cljs/athens/views/dropdown.cljs b/src/cljs/athens/views/dropdown.cljs
new file mode 100644
index 0000000000..a49d9e6ddb
--- /dev/null
+++ b/src/cljs/athens/views/dropdown.cljs
@@ -0,0 +1,129 @@
+(ns athens.views.dropdown
+ (:require
+ [athens.db]
+ [athens.style :refer [color DEPTH-SHADOWS ZINDICES]]
+ [garden.selectors :as selectors]
+ [stylefy.core :as stylefy]))
+
+
+;; Styles
+
+
+(stylefy/keyframes "dropdown-appear"
+ [:from {:opacity 0
+ :transform "translateY(-10%)"}]
+ [:to {:opacity 1
+ :transform "translateY(0)"}])
+
+
+(def dropdown-style
+ {:display "inline-flex"
+ :color (color :body-text-color)
+ :z-index (:zindex-dropdown ZINDICES)
+ :padding "0.25rem"
+ :border-radius "calc(0.25rem + 0.25rem)" ; Button corner radius + container padding makes "concentric" container radius
+ :min-height "2em"
+ :min-width "2em"
+ :animation "dropdown-appear 0.125s"
+ :animation-fill-mode "both"
+ :background (color :background-plus-2)
+ :box-shadow [[(:64 DEPTH-SHADOWS) ", 0 0 0 1px rgba(0, 0, 0, 0.05)"]]
+ :flex-direction "column"})
+
+
+(def menu-style
+ {:display "grid"
+ :grid-gap "0.125rem"
+ :min-width "9em"
+ :align-items "stretch"
+ :grid-auto-flow "row"
+ :overflow "auto"
+ ::stylefy/manual [[(selectors/& (selectors/not (selectors/first-child))) {:margin-block-start "0.25rem"}]
+ [(selectors/& (selectors/not (selectors/last-child))) {:margin-block-end "0.25rem"}]
+ [:button {:min-height "1.5rem"}]]})
+
+
+#_(def menu-heading-style
+ {:min-height "2rem"
+ :text-align "center"
+ :padding "0.375rem 0.5rem"
+ :display "flex"
+ :align-content "flex-end"
+ :justify-content "center"
+ :align-items "center"
+ :font-size "12px"
+ :max-width "100%"
+ :overflow "hidden"
+ :text-overflow "ellipsis"})
+
+
+(def menu-separator-style
+ {:border "0"
+ :background (color :border-color)
+ :align-self "stretch"
+ :justify-self "stretch"
+ :height "1px"
+ :margin "0.25rem 0"})
+
+
+#_(def submenu-indicator-style
+ {:margin-left "auto"
+ :opacity "0.5"
+ :display "flex"
+ :order 10
+ :align-self "flex-end"
+ :font-family "inherit"
+ ::stylefy/manual [[:&:last-child {:padding-inline-end "0"}]]})
+
+
+;; Components
+;;
+;;
+;; (defn block-context-menu-component
+;; [style]
+;; [dropdown {:style style :content
+;; [menu {:content
+;; [:<>
+;; ;; [menu-heading "Modify Block 'Day of Datomic On-Prem 2016'"]
+;; ;; [textinput {:icon [:> Face] :placeholder "Type to filter"}]
+;; [:> Button [:<> [:> Link] [:span "Copy Page Reference"]]]
+;; [:> Button [:<> [:> Star] [:span "Add to Shortcuts"]]]
+;; [:> Button [:<> [:> Face] [:span "Add Reaction"] [submenu-indicator]]]
+;; [menu-separator]
+;; [:> Button [:<> [:> LastPage] [:span "Open in Sidebar"] [:kbd "shift-click"]]]
+;; [:> Button [:<> [:> Launch] [:span "Open in New Window"] [:kbd "ctrl-o"]]]
+;; [:> Button [:<> [:> UnfoldMore] [:span "Expand All"]]]
+;; [:> Button [:<> [:> UnfoldLess] [:span "Collapse All"]]]
+;; [:> Button [:<> [:> Slideshow] [:span "View As"] [submenu-indicator]]]
+;; [menu-separator]
+;; [:> Button [:<> [:> FileCopy] [:span "Duplicate and Break Links"]]]
+;; [:> Button [:<> [:> LibraryAdd] [:span "Save as Template"]]]
+;; [:> Button [:<> [:> History] [:span "Browse Versions"]]]
+;; [:> Button [:<> [:> CloudDownload] [:span "Export As"]]]]}]}])
+;;
+;;
+;; (def items
+;; {"Amet" {:count 6 :state :added}
+;; "At" {:count 130 :state :excluded}
+;; "Diam" {:count 6}
+;; "Donec" {:count 6}
+;; "Elit" {:count 30}
+;; "Elitudomin mesucen defibocutruon" {:count 1}
+;; "Erat" {:count 11}
+;; "Est" {:count 2}
+;; "Eu" {:count 2}
+;; "Ipsum" {:count 2 :state :excluded}
+;; "Magnis" {:count 10 :state :added}
+;; "Metus" {:count 29}
+;; "Mi" {:count 7 :state :added}
+;; "Quam" {:count 1}
+;; "Turpis" {:count 97}
+;; "Vitae" {:count 1}})
+;;
+;;
+;; (defn filter-dropdown-component
+;; []
+;; [dropdown {:style {:width "20em" :height "20em"}
+;; :content [:<>
+;; [menu-heading "Filters"]
+;; [filters-el "((some-uid))" items]]}])
diff --git a/src/cljs/athens/views/help.cljs b/src/cljs/athens/views/help.cljs
index f77f700da5..1d22b089fa 100644
--- a/src/cljs/athens/views/help.cljs
+++ b/src/cljs/athens/views/help.cljs
@@ -1,31 +1,34 @@
(ns athens.views.help
(:require
- ["@chakra-ui/react" :refer [Text Heading Box Modal ModalOverlay ModalContent ModalHeader ModalBody ModalCloseButton]]
+ ["@material-ui/core/Modal" :default Modal]
+ [athens.style :refer [color]]
[athens.util :as util]
[clojure.string :as str]
[re-frame.core :refer [dispatch subscribe]]
- [reagent.core :as r]))
+ [reagent.core :as r]
+ [stylefy.core :as stylefy :refer [use-style]])
+ (:import
+ (goog.events
+ KeyCodes)))
;; Helpers to create the help content
;; ==========================
-(defn faded-text
+(defn opaque-text
[text]
- [:> Text {:as "span"
- :color "foreground.secondary"
- :fontWeight "normal"}
+ [:span (use-style {:color (color :body-text-color :opacity-med)
+ :font-weight "normal"})
text])
(defn space
[]
- [:> Box {:as "i"
- :width "0.5em"
- :display "inline-block"
- :marginInline "0.125em"
- :background "currentColor"
- :height "1px"
- :opacity "0.5"}])
+ [:i (use-style {:width "0.25em"
+ :display "inline-block"
+ :margin-inline "0.25em"
+ :height "0.125em"
+ :border (str "1px solid " (color :body-text-color :opacity-low))
+ :border-top 0})])
(defn- add-keys
@@ -38,7 +41,7 @@
(defn example
[template & args]
- (let [faded-texts (map #(r/as-element [faded-text %]) args)
+ (let [opaque-texts (map #(r/as-element [opaque-text %]) args)
space-component (r/as-element [space])
insert-spaces (fn [str-or-vec]
(if (and (string? str-or-vec)
@@ -49,13 +52,14 @@
(interleave (repeat space-component))
add-keys))
str-or-vec))]
- [:> Text {:fontSize "85%"
- :fontWeight "bold"
- :userSelect "all"
- :wordBreak "break-word"}
+ [:span (use-style
+ {:font-size "85%"
+ :font-weight "bold"
+ :user-select "all"
+ :word-break "break-word"})
(as-> template t
(str/split t #"\$text")
- (interleave t (concat faded-texts [nil]))
+ (interleave t (concat opaque-texts [nil]))
(map insert-spaces t)
(add-keys t)
(into [:<>] t))]))
@@ -177,15 +181,13 @@
;; :example [:span (use-style {:text-decoration "underline"}) "Athens"]
;; :shortcut "mod+u"}
{:description "Strikethrough"
- :example [:> Text {:as "span" :textDecoration "line-through"} "Athens"]
+ :example [:span (use-style {:text-decoration "line-through"}) "Athens"]
:shortcut "mod+y"}
{:description "Highlight"
- :example [:> Text {:as "span"
- :background "highlight"
- :color "highlightContrast"
- :borderRadius "0.1rem"
- :padding "0 0.125em"}
- "Athens"]
+ :example [:span (use-style {:background (color :highlight-color)
+ :color (color :background-color)
+ :border-radius "0.1rem"
+ :padding "0 0.125em"}) "Athens"]
:shortcut "mod+h"}]}
{:name "Graph"
:items [{:description "Open Node in Sidebar"
@@ -225,36 +227,69 @@
keys (as-> shortcut-str s
(str/split s #"\+")
(map key-to-display s))]
- [:> Box {:display "flex"
- :alignItems "center"
- :gap "0.3rem"}
+ [:div (use-style {:display "flex"
+ :align-items "center"
+ :gap "0.3rem"})
(doall
(for [key keys]
^{:key key}
- [:> Text {:fontFamily "inherit"
- :display "inline-flex"
- :gap "0.3em"
- :textTransform "uppercase"
- :fontSize "0.8em"
- :paddingInline "0.35em"
- :background "background.basement"
- :borderRadius "0.25rem"
- :fontWeight 600}
+ [:span (use-style {:font-family "inherit"
+ :display "inline-flex"
+ :gap "0.3em"
+ :text-transform "uppercase"
+ :font-size "0.8em"
+ :padding-inline "0.35em"
+ :background (color :background-plus-2)
+ :border-radius "0.25rem"
+ :font-weight 600})
key]))]))
+(def modal-body-styles
+ {:width "max-content"
+ :margin "2rem auto"
+ :max-width "calc(100% - 1rem)"
+ :border (str "1px solid " (color :border-color))
+ :border-radius "1rem"
+ :box-shadow (str "0 0.25rem 0.5rem -0.25rem " (color :shadow-color))
+ :display "flex"})
+
+
+(def help-styles
+ {:background-color (color :background-color)
+ :border-radius "1rem"
+ :display "flex"
+ :flex-direction "column"
+ :min-width "500px"})
+
+
+(def help-header-styles
+ {:display "flex"
+ :justify-content "space-between"
+ :margin 0
+ :align-items "center"
+ :border-bottom [["1px solid" (color :border-color)]]})
+
+
+(def help-title
+ {:padding "1rem 1.5rem"
+ :margin "0"
+ :font-size "2rem"
+ :color (color :header-text-color)})
+
+
(defn help-section
[title & children]
- [:> Box {:as "section"}
- [:> Heading {:as "h2"
- :color "foreground.primary"
- :textTransform "uppercase"
- :letterSpacing "0.06rem"
- :margin 0
- :font-weight 600
- :font-size "100%"
- :padding "1rem 1.5rem"}
+ [:section
+ [:h2 (use-style
+ {:color (color :body-text-color :opacity-med)
+ :text-transform "uppercase"
+ :letter-spacing "0.06rem"
+ :margin 0
+ :font-weight 600
+ :font-size "100%"
+ :padding "1rem 1.5rem"})
title]
(doall
(for [child children]
@@ -264,16 +299,15 @@
(defn help-section-group
[title & children]
- [:> Box {:display "grid"
- :padding "1.5rem"
- :gridTemplateColumns "12rem 1fr"
- :columnGap "1rem"
- :borderTop "1px solid"
- :borderColor "separator.divider"}
- [:> Heading {:fontSize "1.5em"
- :as "h3"
- :margin 0
- :font-weight "bold"}
+ [:section (use-style
+ {:display "grid"
+ :padding "1.5rem"
+ :grid-template-columns "12rem 1fr"
+ :column-gap "1rem"
+ :border-top [["1px solid" (color :border-color)]]})
+ [:h3 (use-style {:font-size "1.5em"
+ :margin 0
+ :font-weight "bold"})
title]
[:div
(doall
@@ -284,16 +318,18 @@
(defn help-item
[item]
- [:> Box {:borderRadius "0.5rem"
- :alignItems "center"
- :display "grid"
- :gap "1rem"
- :gridTemplateColumns "12rem 1fr"
- :padding "0.25rem 0.5rem"
- :sx {"&:nth-child(odd)"
- {:bg "background.floor"}}}
- [:> Text {:display "flex"
- :justify-content "space-between"}
+ [:div (use-style
+ {:border-radius "0.5rem"
+ :align-items "center"
+ :display "grid"
+ :gap "1rem"
+ :grid-template-columns "12rem 1fr"
+ :padding "0.25rem 0.5rem"
+ ::stylefy/manual ["&:nth-child(odd)"
+ {:background (color :background-plus-2 :opacity-low)}]})
+ [:span (use-style
+ {:display "flex"
+ :justify-content "space-between"})
;; Position of the example changes if there is a shortcut or not.
(:description item)
(when (contains? item :shortcut)
@@ -304,32 +340,56 @@
[shortcut (:shortcut item)])])
+;; Help popup UI
+;; Why the escape handler?
+;; Because when disabled the modal autofocus (which moves the modal to the top when
+;; opened and causes other issues like moving into the top the modal when clicking outside
+;; of it from the top), the escape handler from the modal itself doesn't work.
+;; Because of that, our own escape handler is added.
(defn help-popup
[]
(r/with-let [open? (subscribe [:help/open?])
- close #(dispatch [:help/toggle])]
- [:> Modal {:isOpen @open?
- :onClose close
- :scrollBehavior "outside"
- :size "full"}
- [:> ModalOverlay]
- [:> ModalContent {:maxWidth "calc(100% - 8rem)"
- :width "max-content"
- :my "4rem"}
- [:> ModalHeader "Help"
- [:> ModalCloseButton]]
- [:> ModalBody {:flexDirection "column"}
- (doall
- (for [section content]
- ^{:key section}
- [help-section (:name section)
- (doall
- (for [group (:groups section)]
- ^{:key group}
- [help-section-group (:name group)
- (doall
- (for [item (:items group)]
- ^{:key item}
- [help-item item]))]))]))]]]))
+ close #(dispatch [:help/toggle])
+ escape-handler (fn [event]
+ (when
+ (and @open? (= (.. event -keyCode) KeyCodes.ESC))
+ (close)))
+ _ (js/addEventListener "keydown" escape-handler)]
+ [:> Modal {:open @open?
+ :style {:overflow-y "auto"}
+ :disableAutoFocus true
+ :onClose close}
+ [:div (use-style modal-body-styles)
+ [:div (use-style help-styles)
+ [:header (use-style help-header-styles)
+ [:h1 (use-style help-title)
+ "Help"]
+ [:nav (use-style {:display "flex"
+ :gap "1rem"
+ :padding "1rem"})]]
+ ;; Links at the top of the help. Uncomment when the correct links are obtained.
+ ;; [help-link
+ ;; [:> LiveHelp]
+ ;; "Get Help on Discord"]
+ ;; [help-link
+ ;; [:> Error]
+ ;; "Get Help on Discord"]
+ ;; [help-link
+ ;; [:> AddToPhotos]
+ ;; "Get Help on Discord"]]]
+ [:div (use-style {:overflow-y "auto"})
+ (doall
+ (for [section content]
+ ^{:key section}
+ [help-section (:name section)
+ (doall
+ (for [group (:groups section)]
+ ^{:key group}
+ [help-section-group (:name group)
+ (doall
+ (for [item (:items group)]
+ ^{:key item}
+ [help-item item]))]))]))]]]]
+ (finally js/removeEventListener "keydown" escape-handler)))
diff --git a/src/cljs/athens/views/left_sidebar.cljs b/src/cljs/athens/views/left_sidebar.cljs
index 5ef33bcf9d..c5a522bb04 100644
--- a/src/cljs/athens/views/left_sidebar.cljs
+++ b/src/cljs/athens/views/left_sidebar.cljs
@@ -1,87 +1,155 @@
(ns athens.views.left-sidebar
(:require
- ["@chakra-ui/react" :refer [VStack Flex Heading Button Link Flex]]
- ["framer-motion" :refer [AnimatePresence motion]]
[athens.reactive :as reactive]
[athens.router :as router]
+ [athens.style :refer [color OPACITIES]]
[athens.util :as util]
[re-frame.core :as rf]
- [reagent.core :as r]))
+ [reagent.core :as r]
+ [stylefy.core :as stylefy :refer [use-style use-sub-style]]))
-;; Components
+;; Styles
+
+
+(def left-sidebar-style
+ {:width 0
+ :grid-area "left-sidebar"
+ :height "100%"
+ :display "flex"
+ :flex-direction "column"
+ :overflow-x "hidden"
+ :overflow-y "auto"
+ ::stylefy/supports {"overflow-y: overlay"
+ {:overflow-y "overlay"}}
+ :transition "width 0.5s ease"
+ ::stylefy/sub-styles {:top-line {:margin-bottom "2.5rem"
+ :display "flex"
+ :flex "0 0 auto"
+ :justify-content "space-between"}
+ :footer {:flex "0 0 auto"
+ :margin "auto 2rem 0"
+ :align-self "stretch"
+ :display "grid"
+ :grid-auto-flow "column"
+ :grid-template-columns "1fr auto auto"
+ :grid-gap "0.25rem"}
+ :small-icon {:font-size "16px"}
+ :large-icon {:font-size "22px"}}
+ ::stylefy/manual [[:&.is-open {:width "18rem"}]
+ [:&.is-closed {:width "0"}]]})
+
+
+(def left-sidebar-content-style
+ {:width "18rem"
+ :height "100%"
+ :display "flex"
+ :flex-direction "column"
+ :padding "7.5rem 0 1rem"
+ :transition "opacity 0.5s ease"
+ :opacity 0
+ ::stylefy/manual [[:&.is-open {:opacity 1}]
+ [:&.is-closed {:opacity 0}]]})
+
+
+(def shortcuts-list-style
+ {:flex "1 1 100%"
+ :display "flex"
+ :list-style "none"
+ :flex-direction "column"
+ :padding "0 2rem"
+ :margin "0 0 2rem"
+ :overflow-y "auto"
+ ::stylefy/supports {"overflow-y: overlay"
+ {:overflow-y "overlay"}}
+ ::stylefy/sub-styles {:heading {:flex "0 0 auto"
+ :opacity (:opacity-med OPACITIES)
+ :line-height "1"
+ :margin "0 0 0.25rem"
+ :font-size "inherit"}}})
+
-(def expanded-sidebar-width "clamp(12rem, 25vw, 18rem)")
+(def shortcut-style
+ {:color (color :link-color)
+ :cursor "pointer"
+ :display "flex"
+ :flex "0 0 auto"
+ :padding "0.25rem 0"
+ :transition "opacity 0.05s ease"
+ ::stylefy/mode [[:hover {:opacity (:opacity-high OPACITIES)}]]})
+
+
+(def notional-logotype-style
+ {:font-family "IBM Plex Serif"
+ :font-size "18px"
+ :opacity (:opacity-med OPACITIES)
+ :letter-spacing "-0.05em"
+ :font-weight "bold"
+ :text-decoration "none"
+ :justify-self "flex-start"
+ :color (color :header-text-color)
+ :transition "opacity 0.05s ease"
+ ::stylefy/mode [[:hover {:opacity (:opacity-high OPACITIES)}]]})
+
+
+(def version-style
+ {:color "inherit"
+ :text-decoration "none"
+ :opacity 0.3
+ :font-size "clamp(12px, 100%, 14px)"
+ ::stylefy/mode [[:hover {:opacity (:opacity-high OPACITIES)}]]})
+
+
+;; Components
(defn shortcut-component
[_]
(let [drag (r/atom nil)]
(fn [[order title]]
- [:> Flex {:as "li"
- :align "stretch"
- :border "1px solid transparent"
- :borderTopColor (when (:above @drag) "brand")
- :borderBottomColor (when (:below @drag) "brand")}
- [:> Button {:variant "link"
- :borderWidth "1px"
- :color "foreground.primary"
- :p "1rem"
- :display "block"
- :py "0.5rem"
- :mx "1rem"
- :textAlign "left"
- :justifyContent "flex-start"
- :overflow "hidden"
- :fontWeight "medium"
- :whiteSpace "nowrap"
- :textOverflow "ellipsis"
- :flex "1"
- :border "none"
- :bg "transparent"
- :borderRadius "md"
- :boxShadow "0 0 0 0.25rem transparent"
- :_focus {:outline "none"}
- :_hover {:bg "background.upper"}
- :_active {:bg "background.attic"
- :transitionDuration "0s"}
- :on-click (fn [e]
- (let [shift? (.-shiftKey e)]
- (rf/dispatch [:reporting/navigation {:source :left-sidebar
- :target :page
- :pane (if shift?
- :right-pane
- :main-pane)}])
- (router/navigate-page title e)))
- :draggable true
- :on-drag-over (fn [e]
- (.. e preventDefault)
- (let [offset (util/mouse-offset e)
- middle-y (util/vertical-center (.. e -target))
- ;; find closest li because sometimes event.target is anchor tag
- ;; if nextSibling is null, then target is last li and therefore end of list
- closest-li (.. e -target (closest "li"))
- next-sibling (.. closest-li -nextElementSibling)
- last-child? (nil? next-sibling)]
- (cond
- (> middle-y (:y offset)) (reset! drag :above)
- (and (< middle-y (:y offset)) last-child?) (reset! drag :below))))
- :on-drag-start (fn [e]
- (set! (.. e -dataTransfer -dropEffect) "move")
- (.. e -dataTransfer (setData "text/plain" order)))
- :on-drag-end (fn [_])
- :on-drag-leave (fn [_] (reset! drag nil))
- :on-drop (fn [e]
- (let [source-order (js/parseInt (.. e -dataTransfer (getData "text/plain")))]
- (prn source-order order)
- (cond
- (= source-order order) nil
- (and (= source-order
- (dec order))
- (= @drag :above)) nil
- (= @drag :below) (rf/dispatch [:left-sidebar/drop source-order order :after])
- :else (rf/dispatch [:left-sidebar/drop source-order order :before])))
- (reset! drag nil))}
+ [:li
+ [:a (use-style (merge shortcut-style
+ (case @drag
+ :above {:border-top [["1px" "solid" (color :link-color)]]}
+ :below {:border-bottom [["1px" "solid" (color :link-color)]]}
+ {}))
+ {:on-click (fn [e]
+ (let [shift? (.-shiftKey e)]
+ (rf/dispatch [:reporting/navigation {:source :left-sidebar
+ :target :page
+ :pane (if shift?
+ :right-pane
+ :main-pane)}])
+ (router/navigate-page title e)))
+ :draggable true
+ :on-drag-over (fn [e]
+ (.. e preventDefault)
+ (let [offset (util/mouse-offset e)
+ middle-y (util/vertical-center (.. e -target))
+ ;; find closest li because sometimes event.target is anchor tag
+ ;; if nextSibling is null, then target is last li and therefore end of list
+ closest-li (.. e -target (closest "li"))
+ next-sibling (.. closest-li -nextElementSibling)
+ last-child? (nil? next-sibling)]
+ (cond
+ (> middle-y (:y offset)) (reset! drag :above)
+ (and (< middle-y (:y offset)) last-child?) (reset! drag :below))))
+ :on-drag-start (fn [e]
+ (set! (.. e -dataTransfer -dropEffect) "move")
+ (.. e -dataTransfer (setData "text/plain" order)))
+ :on-drag-end (fn [_])
+ :on-drag-leave (fn [_] (reset! drag nil))
+ :on-drop (fn [e]
+ (let [source-order (js/parseInt (.. e -dataTransfer (getData "text/plain")))]
+ (prn source-order order)
+ (cond
+ (= source-order order) nil
+ (and (= source-order
+ (dec order))
+ (= @drag :above)) nil
+ (= @drag :below) (rf/dispatch [:left-sidebar/drop source-order order :after])
+ :else (rf/dispatch [:left-sidebar/drop source-order order :before])))
+ (reset! drag nil))})
title]])))
@@ -89,38 +157,19 @@
[]
(let [open? (rf/subscribe [:left-sidebar/open])
shortcuts (reactive/get-reactive-shortcuts)]
- [:> AnimatePresence {:initial false}
- (when @open?
- [:> (.-div motion)
- {:style {:display "flex"
- :flex-direction "column"
- :height "100%"
- :alignItems "stretch"
- :gridArea "left-sidebar"
- :position "relative"
- :overflow "hidden"}
- :initial {:width 0
- :opacity 0}
- :animate {:width expanded-sidebar-width
- :opacity 1}
- :exit {:width 0
- :opacity 0}}
+ (fn []
+ [:div (use-style left-sidebar-style
+ {:class (if @open?
+ "is-open"
+ "is-closed")})
+ [:div (use-style left-sidebar-content-style
+ {:class (if @open?
+ "is-open"
+ "is-closed")})
;; SHORTCUTS
- [:> VStack {:as "ol"
- :align "stretch"
- :width expanded-sidebar-width
- :py "1rem"
- :paddingTop "7rem"
- :spacing "0.25rem"
- :overflowY "overlay"
- :sx {:listStyle "none"
- :WebkitAppRegion "no-drag"}}
- [:> Heading {:as "h2"
- :px "2rem"
- :pb "0.5rem"
- :size "sm"
- :color "foreground.secondary"}
+ [:ol (use-style shortcuts-list-style)
+ [:h2 (use-sub-style shortcuts-list-style :heading)
"Shortcuts"]
(doall
(for [sh shortcuts]
@@ -128,20 +177,10 @@
[shortcut-component sh]))]
;; LOGO + BOTTOM BUTTONS
- [:> Flex {:as "footer"
- :width expanded-sidebar-width
- :flexWrap "wrap"
- :gap "0.25em 0.5em"
- :fontSize "sm"
- :p "2rem"
- :mt "auto"}
- [:> Link {:fontWeight "bold"
- :display "inline-block"
- :href "https://github.com/athensresearch/athens/issues/new/choose"
- :target "_blank"}
- "Athens"]
- [:> Link {:color "foreground.secondary"
- :display "inline-block"
- :href "https://github.com/athensresearch/athens/blob/master/CHANGELOG.md"
- :target "_blank"}
- (athens.util/athens-version)]]])]))
+ [:footer (use-sub-style left-sidebar-style :footer)
+ [:a (use-style notional-logotype-style {:href "https://github.com/athensresearch/athens/issues/new/choose" :target "_blank"}) "Athens"]
+ [:h5 (use-style {:align-self "center"})
+ [:a (use-style version-style {:href "https://github.com/athensresearch/athens/blob/master/CHANGELOG.md"
+ :target "_blank"})
+ (athens.util/athens-version)]]]]])))
+
diff --git a/src/cljs/athens/views/modal.cljs b/src/cljs/athens/views/modal.cljs
new file mode 100644
index 0000000000..ded8914e85
--- /dev/null
+++ b/src/cljs/athens/views/modal.cljs
@@ -0,0 +1,46 @@
+(ns athens.views.modal
+ (:require
+ [athens.db]
+ [athens.style :refer [color ZINDICES DEPTH-SHADOWS]]
+ [garden.selectors :as selectors]
+ [stylefy.core :as stylefy]))
+
+
+;; Styles
+
+(def modal-style
+ {:z-index (:zindex-modal ZINDICES)
+ :animation "fade-in 0.2s"
+ :position "relative"
+ ::stylefy/manual [[:.modal {:position "fixed"
+ :top "50vh"
+ :left "50vw"
+ :transform "translate(-50%, -50%)"
+ :border-radius "0.5rem"
+ :display "flex"
+ :flex-direction "column"
+ :background-clip "padding-box"
+ :background (color :background-plus-1)
+ :box-shadow [[(:64 DEPTH-SHADOWS) ", 0 0 0 1px " (color :body-text-color :opacity-low)]]}]
+ [:modal__header {:display "contents"}] ; Deactivate layout on the default header
+ [(selectors/> :.modal__header :button) {:display "none"}] ; Hide default close button
+ [:.modal__title :.modal__footer {:flex "0 0 auto"
+ :padding "0.25rem 1rem"
+ :display "flex"
+ :align-items "center"}
+ [:&:empty {:display "none"}]]
+ [:.modal__title {:padding-right "0.75rem"}
+ [(selectors/+ :svg :h4) {:margin-inline-start "0.5rem"}]
+ [:button {:margin-inline-start "auto"
+ :align-self "flex-start"
+ :margin-block "0.5rem"}]]
+ [:.modal__content {:flex "1 1 100%"
+ :overflow-y "auto"}]
+ [:.modal__footer {:display "flex"}]
+ [:.modal__backdrop {:position "fixed"
+ :top 0
+ :left 0
+ :background "rgba(0,0,0,0.1)"
+ :z-index -1
+ :width "100vw"
+ :height "100vh"}]]})
diff --git a/src/cljs/athens/views/pages/all_pages.cljs b/src/cljs/athens/views/pages/all_pages.cljs
index a2fc62df0c..4e524f9bfe 100644
--- a/src/cljs/athens/views/pages/all_pages.cljs
+++ b/src/cljs/athens/views/pages/all_pages.cljs
@@ -1,14 +1,80 @@
(ns athens.views.pages.all-pages
(:require
- ["@chakra-ui/react" :refer [Table Thead Tr Th Tbody Td Button Box]]
["@material-ui/icons/ArrowDropDown" :default ArrowDropDown]
["@material-ui/icons/ArrowDropUp" :default ArrowDropUp]
[athens.common-db :as common-db]
[athens.dates :as dates]
[athens.db :as db]
[athens.router :as router]
+ [athens.style :as style :refer [color OPACITIES]]
[clojure.string :refer [lower-case]]
- [re-frame.core :as rf]))
+ [garden.selectors :as selectors]
+ [re-frame.core :as rf]
+ [stylefy.core :as stylefy :refer [use-style]]))
+
+
+;; Styles
+
+
+(def page-style
+ {:display "flex"
+ :margin "5rem auto"
+ :flex-basis "100%"
+ :max-width "70rem"})
+
+
+(def table-style
+ {:flex "1 1 100%"
+ :margin "0 2rem"
+ :text-align "left"
+ :border-collapse "collapse"
+ ::stylefy/manual [[:tbody {:vertical-align "top"}
+ [:tr {:transition "background 0.1s ease"}
+ [:td {:border-top (str "1px solid " (color :border-color))
+ :transition "box-shadow 0.1s ease"}
+ [:&.title {:color (color :link-color)
+ :width "15vw"
+ :cursor "pointer"
+ :min-width "10em"
+ :word-break "break-word"
+ :font-weight "500"
+ :font-size "1.3125em"
+ :line-height "1.28"}]
+ [:&.links {:font-size "1em"
+ :text-align "center"}]
+ [:&.body-preview {:word-break "break-word"
+ :overflow "hidden"
+ :text-overflow "ellipsis"
+ :display "-webkit-box"
+ :-webkit-mask "linear-gradient(to bottom, #fff calc(100% - 1em), transparent)"
+ :-webkit-line-clamp "3"
+ :-webkit-box-orient "vertical"}
+ [:span:empty {:display "none"}]
+ [(selectors/+ :span :span)
+ [:&:before {:content "'•'"
+ :margin-inline "0.5em"
+ :opacity (:opacity-low OPACITIES)}]]]
+ [:&.date {:text-align "right"
+ :opacity (:opacity-high OPACITIES)
+ :font-size "0.75em"
+ :min-width "9em"}]
+ [:&:first-child {:border-radius "0.5rem 0 0 0.5rem"
+ :box-shadow "-1rem 0 transparent"}]
+ [:&:last-child {:border-radius "0 0.5rem 0.5rem 0"
+ :box-shadow "1rem 0 transparent"}]]
+ [:&:hover {:background-color (color :background-minus-1 :opacity-med)
+ :border-radius "0.5rem"}
+ [:td [:&:first-child {:box-shadow [["-1rem 0 " (color :background-minus-1 :opacity-med)]]}]]
+ [:td [:&:last-child {:box-shadow [["1rem 0 " (color :background-minus-1 :opacity-med)]]}]]]]]
+ [:td :th {:padding "0.5rem"}]
+ [:th {:opacity (:opacity-med OPACITIES)
+ :user-select "none"}
+ [:&.sortable {:cursor "pointer"}
+ [:.wrap-label {:display "flex"
+ :align-items "center"}]
+ [:&.date
+ [:.wrap-label {:flex-direction "row-reverse"}]]
+ [:&:hover {:opacity 1}]]]]})
;; Sort state and logic
@@ -63,19 +129,17 @@
;; Components
(defn- sortable-header
- ([column-id label width isNumeric]
+ ([column-id label]
+ (sortable-header column-id label {:date? false}))
+ ([column-id label {:keys [date?]}]
(let [sorted-by @(rf/subscribe [:all-pages/sorted-by])
growing? @(rf/subscribe [:all-pages/sort-order-ascending?])]
- [:> Th {:width width :isNumeric isNumeric}
- [:> Button {:onClick #(rf/dispatch [:all-pages/sort-by column-id])
- :size "sm"
- :variant "link"}
- (when-not isNumeric label)
+ [:th {:on-click #(rf/dispatch [:all-pages/sort-by column-id])
+ :class ["sortable" (when date? "date")]}
+ [:div.wrap-label
+ [:h5 label]
(when (= sorted-by column-id)
- (if growing?
- [:> ArrowDropUp]
- [:> ArrowDropDown]))
- (when isNumeric label)]])))
+ (if growing? [:> ArrowDropUp] [:> ArrowDropDown]))]])))
(defn page
@@ -83,39 +147,30 @@
(let [all-pages (common-db/get-all-pages @db/dsdb)]
(fn []
(let [sorted-pages @(rf/subscribe [:all-pages/sorted all-pages])]
- [:> Box {:px 4
- :margin "calc(var(--app-header-height) + 2rem) auto 5rem"}
- [:> Table {:variant "striped"}
- [:> Thead
- [:> Tr
+ [:div (use-style page-style)
+ [:table (use-style table-style)
+ [:thead
+ [:tr
[sortable-header :title "Title"]
- [sortable-header :links-count "Links" "12rem" true]
- [sortable-header :modified "Modified" "16rem" false {:date? true}]
- [sortable-header :created "Created" "16rem" false {:date? true}]]]
- [:> Tbody
+ [sortable-header :links-count "Links"]
+ [sortable-header :modified "Modified" {:date? true}]
+ [sortable-header :created "Created" {:date? true}]]]
+ [:tbody
(doall
(for [{:keys [block/uid node/title block/_refs]
modified :edit/time
created :create/time} sorted-pages]
- [:> Tr {:key uid}
- [:> Td {:overflow "hidden"}
- [:> Button {:variant "link"
- :justifyContent "flex-start"
- :textAlign "left"
- :padding "0"
- :color "link"
- :display "block"
- :maxWidth "100%"
- :whiteSpace "nowrap"
- :onClick (fn [e]
- (let [shift? (.-shiftKey e)]
- (rf/dispatch [:reporting/navigation {:source :all-pages
- :target :page
- :pane (if shift?
- :right-pane
- :main-pane)}])
- (router/navigate-page title e)))}
- title]]
- [:> Td {:width "12rem" :whiteSpace "nowrap" :color "foreground.secondary" :isNumeric true} (count _refs)]
- [:> Td {:width "16rem" :whiteSpace "nowrap" :color "foreground.secondary"} (dates/date-string modified)]
- [:> Td {:width "16rem" :whiteSpace "nowrap" :color "foreground.secondary"} (dates/date-string created)]]))]]]))))
+ [:tr {:key uid}
+ [:td {:class "title"
+ :on-click (fn [e]
+ (let [shift? (.-shiftKey e)]
+ (rf/dispatch [:reporting/navigation {:source :all-pages
+ :target :page
+ :pane (if shift?
+ :right-pane
+ :main-pane)}])
+ (router/navigate-page title e)))}
+ title]
+ [:td {:class "links"} (count _refs)]
+ [:td {:class "date"} (dates/date-string modified)]
+ [:td {:class "date"} (dates/date-string created)]]))]]]))))
diff --git a/src/cljs/athens/views/pages/block_page.cljs b/src/cljs/athens/views/pages/block_page.cljs
index 42db80a176..a1ef430442 100644
--- a/src/cljs/athens/views/pages/block_page.cljs
+++ b/src/cljs/athens/views/pages/block_page.cljs
@@ -1,16 +1,59 @@
(ns athens.views.pages.block-page
(:require
- ["/components/Page/Page" :refer [PageHeader PageBody PageFooter EditableTitleContainer]]
- ["@chakra-ui/react" :refer [Breadcrumb BreadcrumbItem BreadcrumbLink VStack AccordionIcon Accordion AccordionItem AccordionButton AccordionPanel]]
+ ["@material-ui/icons/Link" :default Link]
[athens.parse-renderer :as parse-renderer]
[athens.reactive :as reactive]
[athens.router :as router]
+ [athens.style :refer [color]]
[athens.views.blocks.core :as blocks]
+ [athens.views.breadcrumbs :refer [breadcrumbs-list breadcrumb]]
[athens.views.pages.node-page :as node-page]
- [athens.views.references :refer [reference-group reference-block]]
+ [garden.selectors :as selectors]
[komponentit.autosize :as autosize]
[re-frame.core :as rf :refer [dispatch subscribe]]
- [reagent.core :as r]))
+ [reagent.core :as r]
+ [stylefy.core :as stylefy :refer [use-style]]))
+
+
+;; Styles
+
+
+(def title-style
+ {:position "relative"
+ :overflow "visible"
+ :flex-grow "1"
+ :margin "0.1em 0"
+ :letter-spacing "-0.03em"
+ :word-break "break-word"
+ :line-height "1.4em"
+ ::stylefy/manual [[:textarea {:-webkit-appearance "none"
+ :cursor "text"
+ :resize "none"
+ :transform "translate3d(0,0,0)"
+ :color "inherit"
+ :font-weight "inherit"
+ :padding "0"
+ :letter-spacing "inherit"
+ :width "100%"
+ :min-height "100%"
+ :caret-color (color :link-color)
+ :background "transparent"
+ :margin "0"
+ :font-size "inherit"
+ :line-height "inherit"
+ :border-radius "0.25rem"
+ :transition "opacity 0.15s ease"
+ :border "0"
+ :font-family "inherit"
+ :visibility "hidden"
+ :position "absolute"}]
+ [:textarea ["::-webkit-scrollbar" {:display "none"}]]
+ [:textarea:focus
+ :.is-editing {:outline "none"
+ :visibility "visible"
+ :position "relative"}]
+ [(selectors/+ :.is-editing :span) {:visibility "hidden"
+ :position "absolute"}]]})
;; Helpers
@@ -52,45 +95,46 @@
[id]
(let [linked-refs (reactive/get-reactive-linked-references id)]
(when (seq linked-refs)
- [:> Accordion
- [:> AccordionItem
- [:h2
- [:> AccordionButton
- [:> AccordionIcon "LinkedReferences"]]]
- [:> AccordionPanel {:px 0}
- [:> VStack {:spacing 6
- :pl 6
- :align "stretch"}
- (doall
- (for [[group-title group] linked-refs]
- [reference-group {:key (str "group-" group-title)
- :title group-title
- :on-click-title (fn [e]
- (let [shift? (.-shiftKey e)
- parsed-title (parse-renderer/parse-title group-title)]
- (rf/dispatch [:reporting/navigation {:source :block-page-linked-refs
- :target :page
- :pane (if shift?
- :right-pane
- :main-pane)}])
- (router/navigate-page parsed-title)))}
- (doall
- (for [block group]
- [reference-block {:key (str "ref-" (:block/uid block))}
- [node-page/ref-comp block]]))]))]]]])))
+ [:div (use-style node-page/references-style {:key "Linked References"})
+ [:section
+ [:h4 (use-style node-page/references-heading-style)
+ [(r/adapt-react-class Link)]
+ [:span "Linked References"]]
+ ;; Hide button until feature is implemented
+ ;; [:> Button {:disabled true} [(r/adapt-react-class FilterList)]]]
+ [:div (use-style node-page/references-list-style)
+ (doall
+ (for [[group-title group] linked-refs]
+ [:div (use-style node-page/references-group-style {:key (str "group-" group-title)})
+ [:h4 (use-style node-page/references-group-title-style)
+ [:a {:on-click (fn [e]
+ (let [shift? (.-shiftKey e)
+ parsed-title (parse-renderer/parse-title group-title)]
+ (rf/dispatch [:reporting/navigation {:source :block-page-linked-refs
+ :target :page
+ :pane (if shift?
+ :right-pane
+ :main-pane)}])
+ (router/navigate-page parsed-title)))}
+ group-title]]
+ (doall
+ (for [block group]
+ [:div (use-style node-page/references-group-block-style {:key (str "ref-" (:block/uid block))})
+ [node-page/ref-comp block]]))]))]]])))
(defn parents-el
[uid id]
(let [parents (reactive/get-reactive-parents-recursively id)]
- [:> Breadcrumb {:gridArea "breadcrumb" :opacity 0.75}
- (doall
- (for [{:keys [node/title block/string] breadcrumb-uid :block/uid} parents]
- ^{:key breadcrumb-uid}
- [:> BreadcrumbItem {:key (str "breadcrumb-" breadcrumb-uid)}
- [:> BreadcrumbLink {:onClick #(breadcrumb-handle-click % uid breadcrumb-uid)}
+ [:span {:style {:color "gray"}}
+ [breadcrumbs-list {:style {:font-size "1.2rem"}}
+ (doall
+ (for [{:keys [node/title block/string] breadcrumb-uid :block/uid} parents]
+ ^{:key breadcrumb-uid}
+ [breadcrumb {:key (str "breadcrumb-" breadcrumb-uid)
+ :on-click #(breadcrumb-handle-click % uid breadcrumb-uid)}
[:span {:style {:pointer-events "none"}}
- [parse-renderer/parse-and-render (or title string)]]]]))]))
+ [parse-renderer/parse-and-render (or title string)]]]))]]))
(defn block-page-el
@@ -102,48 +146,41 @@
(when (not= string (:string/previous @state))
(swap! state assoc :string/previous string :string/local string))
- [:<>
+ [:div.block-page (use-style node-page/page-style {:data-uid uid})
+ ;; Parent Context
+ [parents-el uid id]
;; Header
- [:> PageHeader
-
- ;; Parent Context
- [parents-el uid id]
- [:> EditableTitleContainer {:isEditing @(subscribe [:editing/is-editing uid])
- :onClick (fn [e]
- (.. e preventDefault)
- (if (.. e -shiftKey)
- (do
- (dispatch [:reporting/navigation {:source :block-page
- :target :block
- :pane :right-pane}])
- (router/navigate-uid uid e))
-
- (dispatch [:editing/uid uid])))}
- [autosize/textarea
- {:value (:string/local @state)
- :class (when @(subscribe [:editing/is-editing uid]) "is-editing")
- :id (str "editable-uid-" uid)
- ;; :auto-focus true
- :on-blur (fn [_]
- (persist-textarea-string @state uid)
- (dispatch [:editing/uid nil]))
- :on-click #(dispatch [:editing/uid uid])
- :on-key-down (fn [e] (node-page/handle-key-down e uid state nil))
- :on-change (fn [e] (block-page-change e uid state))}]
- (if (clojure.string/blank? (:string/local @state))
- [:span [:wbr]]
- [parse-renderer/parse-and-render (:string/local @state) uid])]]
+ [:h1 (merge
+ (use-style title-style {:data-uid uid :class "block-header"})
+ {:on-click (fn [e]
+ (.. e preventDefault)
+ (if (.. e -shiftKey)
+ (do
+ (rf/dispatch [:reporting/navigation {:source :block-page
+ :target :block
+ :pane :right-pane}])
+ (router/navigate-uid uid e))
+ (dispatch [:editing/uid uid])))})
+ [autosize/textarea
+ {:id (str "editable-uid-" uid)
+ :value (:string/local @state)
+ :class (when @(subscribe [:editing/is-editing uid]) "is-editing")
+ :auto-focus true
+ :on-blur (fn [_] (persist-textarea-string @state uid))
+ :on-key-down (fn [e] (node-page/handle-key-down e uid state nil))
+ :on-change (fn [e] (block-page-change e uid state))}]
+ (if (clojure.string/blank? (:string/local @state))
+ [:wbr]
+ [:span [parse-renderer/parse-and-render (:string/local @state) uid]])]
;; Children
- [:> PageBody
- (for [child children]
- (let [{:keys [db/id]} child]
- ^{:key id} [blocks/block-el child]))]
+ [:div (for [child children]
+ (let [{:keys [db/id]} child]
+ ^{:key id} [blocks/block-el child]))]
;; Refs
- [:> PageFooter
- [linked-refs-el id]]]))))
+ [linked-refs-el id]]))))
(defn page
diff --git a/src/cljs/athens/views/pages/core.cljs b/src/cljs/athens/views/pages/core.cljs
index 9d0f5b1801..16c794553a 100644
--- a/src/cljs/athens/views/pages/core.cljs
+++ b/src/cljs/athens/views/pages/core.cljs
@@ -1,14 +1,34 @@
(ns athens.views.pages.core
(:require
- ["@chakra-ui/react" :refer [Box]]
- [athens.util :refer [toast]]
+ [athens.style :as style]
[athens.views.hoc.perf-mon :as perf-mon]
[athens.views.pages.all-pages :as all-pages]
[athens.views.pages.daily-notes :as daily-notes]
[athens.views.pages.graph :as graph]
[athens.views.pages.page :as page]
[athens.views.pages.settings :as settings]
- [re-frame.core :as rf]))
+ [re-frame.core :as rf]
+ [stylefy.core :as stylefy]))
+
+
+;; Styles
+
+(def main-content-style
+ {:flex "1 1 100%"
+ :grid-area "main-content"
+ :align-items "flex-start"
+ :justify-content "stretch"
+ :padding-top "2.5rem"
+ :display "flex"
+ :overflow-y "auto"
+ ::stylefy/supports {"overflow-y: overlay"
+ {:overflow-y "overlay"}}
+ ::stylefy/mode {"::-webkit-scrollbar" {:background (style/color :background-minus-1)
+ :width "0.5rem"
+ :height "0.5rem"}
+ "::-webkit-scrollbar-corner" {:background (style/color :background-minus-1)}
+ "::-webkit-scrollbar-thumb" {:background (style/color :background-minus-2)
+ :border-radius "0.5rem"}}})
;; View
@@ -18,43 +38,10 @@
(let [route-name (rf/subscribe [:current-route/name])]
;; TODO: create a UI to inform the player of the connection status
(when (= @(rf/subscribe [:connection-status]) :reconnecting)
- (toast (clj->js {:status "info"
- :title "Reconnecting to server..."})))
- [:> Box {:flex "1 1 100%"
- :position "relative"
- :gridArea "main-content"
- :alignItems "flex-start"
- :justifyContent "stretch"
- :display "flex"
- :overflowY "overlay"
- :sx {:maskImage "linear-gradient(to bottom,
- transparent,
- #000000cc 1rem,
- black 1.5rem,
- black calc(100vh - 5rem),
- #000000f0 calc(100vh - 4rem),
- #00000088 100vh)"
- ".os-mac &" {:maskImage "linear-gradient(to bottom,
- transparent var(--app-header-height),
- black calc(var(--app-header-height) + 2rem),
- black 5rem,
- black calc(100vh - 5rem),
- #000000f0 calc(100vh - 4rem),
- #00000088 100vh)"}
- "&:before" {:content "''"
- :position "fixed"
- :zIndex "-1"
- :inset 0
- :top "3.25rem"
- :WebkitAppRegion "no-drag"}
- "::WebkitScrollbar" {:background "background.basement"
- :width "0.5rem"
- :height "0.5rem"}
- "::WebkitScrollbar-corner" {:bg "background.basement"}
- "::WebkitScrollbar-thumb" {:bg "background.upper"
- :borderRadius "full"}}
- :on-scroll (when (= @route-name :home)
- #(rf/dispatch [:daily-note/scroll]))}
+ (rf/dispatch [:alert/js "Oops! Connection Lost. Reconnecting..."]))
+ [:div (stylefy/use-style main-content-style
+ {:on-scroll (when (= @route-name :home)
+ #(rf/dispatch [:daily-note/scroll]))})
(case @route-name
:settings [perf-mon/hoc-perfmon-no-new-tx {:span-name "pages/settings"}
[settings/page]]
diff --git a/src/cljs/athens/views/pages/daily_notes.cljs b/src/cljs/athens/views/pages/daily_notes.cljs
index 87222c3373..6b162b9c8d 100644
--- a/src/cljs/athens/views/pages/daily_notes.cljs
+++ b/src/cljs/athens/views/pages/daily_notes.cljs
@@ -1,11 +1,39 @@
(ns athens.views.pages.daily-notes
(:require
- ["/components/Page/Page" :refer [PageHeader TitleContainer DailyNotesPage]]
- ["@chakra-ui/react" :refer [VStack]]
[athens.dates :as dates]
[athens.reactive :as reactive]
+ [athens.style :refer [DEPTH-SHADOWS]]
[athens.views.pages.node-page :as node-page]
- [re-frame.core :refer [dispatch subscribe]]))
+ [re-frame.core :refer [dispatch subscribe]]
+ [stylefy.core :refer [use-style]]))
+
+
+;; Styles
+
+
+(def daily-notes-scroll-area-style
+ {:min-height "calc(100vh + 1px)"
+ :display "flex"
+ :padding "1.25rem 0"
+ :align-items "stretch"
+ :flex "1 1 100%"
+ :flex-direction "column"})
+
+
+(def daily-notes-page-style
+ {:box-shadow (:16 DEPTH-SHADOWS)
+ :align-self "stretch"
+ :justify-self "stretch"
+ :margin "1.25rem 2.5rem"
+ :padding "1rem 2rem"
+ :transition-duration "0s"
+ :border-radius "0.5rem"
+ :min-height "calc(100vh - 10rem)"})
+
+
+(def daily-notes-notional-page-style
+ (merge daily-notes-page-style {:box-shadow (:4 DEPTH-SHADOWS)
+ :opacity "0.5"}))
(defn reactive-pull-many
@@ -27,20 +55,12 @@
(if (empty? @note-refs)
(dispatch [:daily-note/next (dates/get-day)])
(let [notes (reactive-pull-many @note-refs)]
- [:> VStack {:id "daily-notes"
- :minHeight "calc(100vh + 1px)"
- :display "flex"
- :gap "1.5rem"
- :py "6rem"
- :px "2rem"
- :alignItems "stretch"
- :flex "1 1 100%"
- :flexDirection "column"}
+ [:div#daily-notes (use-style daily-notes-scroll-area-style)
(doall
(for [{:keys [block/uid]} notes]
- [:> DailyNotesPage {:key uid
- :isReal true}
- [node-page/page [:block/uid uid]]]))
- [:> DailyNotesPage {:isReal false}
- [:> PageHeader
- [:> TitleContainer "Earlier"]]]])))))
+ ^{:key uid}
+ [:<>
+ [:div (use-style daily-notes-page-style)
+ [node-page/page [:block/uid uid]]]]))
+ [:div (use-style daily-notes-notional-page-style)
+ [:h1 "Earlier"]]])))))
diff --git a/src/cljs/athens/views/pages/graph.cljs b/src/cljs/athens/views/pages/graph.cljs
index f76ff725ff..0b872ff53b 100644
--- a/src/cljs/athens/views/pages/graph.cljs
+++ b/src/cljs/athens/views/pages/graph.cljs
@@ -6,32 +6,26 @@
and customizations are based on that where as global doesn't have an explicit root
Relies on material ui comps for user inputs."}
- athens.views.pages.graph
+ athens.views.pages.graph
(:require
- ["@chakra-ui/react" :refer [Box Accordion AccordionButton AccordionItem AccordionPanel AccordionIcon]]
- ["@material-ui/core/Slider" :as OldSlider]
- ["@material-ui/core/Switch" :as OldSwitch]
- ["react-force-graph-2d" :as ForceGraph2D]
- [athens.dates :as dates]
- [athens.db :as db]
- [athens.router :as router]
- [clojure.set :as set]
- [datascript.core :as d]
- [re-frame.core :as rf :refer [subscribe]]
- [reagent.core :as r]
- [reagent.dom :as dom]))
-
-
-(def THEME-DARK
- {:graph-node-normal "hsla(0, 0%, 100%, 0.57)"
- :graph-node-hlt "#498eda"
- :graph-link-normal "#ffffff11"})
-
-
-(def THEME-LIGHT
- {:graph-node-normal "#909090"
- :graph-node-hlt "#0075E1"
- :graph-link-normal "#cfcfcf"})
+ ["@material-ui/core/ExpansionPanel" :as ExpansionPanel]
+ ["@material-ui/core/ExpansionPanelDetails" :as ExpansionPanelDetails]
+ ["@material-ui/core/ExpansionPanelSummary" :as ExpansionPanelSummary]
+ ["@material-ui/core/Slider" :as Slider]
+ ["@material-ui/core/Switch" :as Switch]
+ ["@material-ui/icons/KeyboardArrowRight" :default KeyboardArrowRight]
+ ["@material-ui/icons/KeyboardArrowUp" :default KeyboardArrowUp]
+ ["react-force-graph-2d" :as ForceGraph2D]
+ [athens.dates :as dates]
+ [athens.db :as db]
+ [athens.router :as router]
+ [athens.style :as styles]
+ [clojure.set :as set]
+ [datascript.core :as d]
+ [re-frame.core :as rf :refer [subscribe]]
+ [reagent.core :as r]
+ [reagent.dom :as dom]
+ [stylefy.core :as stylefy :refer [use-style]]))
;; all graph refs(react refs) reside in this atom
@@ -44,9 +38,19 @@
;; --- material ui ---
-(def m-slider (r/adapt-react-class (.-default OldSlider)))
+(def m-slider (r/adapt-react-class (.-default Slider)))
+
+
+(def m-expansion-panel (r/adapt-react-class (.-default ExpansionPanel)))
+
-(def m-switch (r/adapt-react-class (.-default OldSwitch)))
+(def m-expansion-panel-details (r/adapt-react-class (.-default ExpansionPanelDetails)))
+
+
+(def m-expansion-panel-summary (r/adapt-react-class (.-default ExpansionPanelSummary)))
+
+
+(def m-switch (r/adapt-react-class (.-default Switch)))
;; -------------------------------------------------------------------
@@ -169,29 +173,54 @@
;; -------------------------------------------------------------------
;; --- comps ---
+
+(defn graph-control-style
+ [theme]
+ {:position "absolute"
+ :right "10px"
+ :font-size "14px"
+ :z-index 2
+ ::stylefy/manual [[:.MuiExpansionPanelDetails-root {:flex-flow "column"
+ :color "grey"}
+ [:.switch {:display "flex"
+ :justify-content "space-between"
+ :align-items "center"}]]
+ [:.MuiSvgIcon-root {:font-size "1.2rem"}]
+ [:.MuiExpansionPanelSummary-content {:justify-content "space-between"}
+ [:&.Mui-expanded {:margin "24px 0"
+ :min-height "unset"}]]
+ [:.MuiExpansionPanelSummary-root
+ [:&.Mui-expanded {:min-height "unset"}]]
+ [:.MuiPaper-root {:background (:graph-control-bg theme)
+ :color (:graph-control-color theme)
+ :margin "10px 0 2px 0"}
+ [:&.Mui-expanded {:margin "0 0 5px 0"}]]]})
+
+
(defn expansion-panel
[{:keys [heading controls]} local-node-eid]
- (let [graph-conf @(subscribe [:graph/conf])
- graph-ref (get @graph-ref-map (or local-node-eid :global))]
- [:> AccordionItem
- [:> AccordionButton
- [:> AccordionIcon]
- heading]
- [:> AccordionPanel
- (doall
- (for [{:keys [key comp label onChange no-simulation-reheat? props class]} controls]
- ^{:key key}
- [:div {:class class} label
- [comp
- (merge
- props
- {:value (key graph-conf)
- :color "primary"
- :onChange (fn [_ n-val]
- (and onChange (onChange n-val))
- (rf/dispatch [:graph/set-conf key n-val])
- (when-not no-simulation-reheat?
- (.d3ReheatSimulation graph-ref)))})]]))]]))
+ (r/with-let [is-open? (r/atom false)]
+ (let [graph-conf @(subscribe [:graph/conf])
+ graph-ref (get @graph-ref-map (or local-node-eid :global))]
+ [m-expansion-panel
+ [m-expansion-panel-summary
+ {:onClick #(swap! is-open? not)}
+ [:<> [:span heading] (if @is-open? [:> KeyboardArrowUp] [:> KeyboardArrowRight])]]
+ [m-expansion-panel-details
+ (doall
+ (for [{:keys [key comp label onChange no-simulation-reheat? props class]} controls]
+ ^{:key key}
+ [:div {:class class} label
+ [comp
+ (merge
+ props
+ {:value (key graph-conf)
+ :color "primary"
+ :onChange (fn [_ n-val]
+ (and onChange (onChange n-val))
+ (rf/dispatch [:graph/set-conf key n-val])
+ (when-not no-simulation-reheat?
+ (.d3ReheatSimulation graph-ref)))})]]))]])))
(defn graph-controls
@@ -203,6 +232,7 @@
(fn []
(let [graph-conf @(subscribe [:graph/conf])
graph-ref (get @graph-ref-map (or local-node-eid :global))
+ theme (if @(rf/subscribe [:theme/dark]) styles/THEME-DARK styles/THEME-LIGHT)
;; code theme
;; category -- for eg node-section and section related data
@@ -267,11 +297,7 @@
:no-simulation-reheat? true}]
local-section {:heading "Local options"
:controls local-controls}]
- [:> Accordion {:width "14em"
- :position "fixed"
- :allowMultiple true
- :top "4rem"
- :right 0}
+ [:div (use-style (graph-control-style theme))
(doall
(for [{:keys [heading] :as section} (remove nil? [(when-not local-node-eid
node-section)
@@ -299,9 +325,9 @@
graph-conf @(subscribe [:graph/conf])
graph-ref (get @graph-ref-map (or local-node-eid :global))]
;; set canvas dimensions
- (swap! dimensions assoc :width (-> dom-node (.. (closest "#app"))
+ (swap! dimensions assoc :width (-> dom-node (.. (closest ".graph-page"))
.-parentNode .-clientWidth))
- (swap! dimensions assoc :height (-> dom-node (.. (closest "#app"))
+ (swap! dimensions assoc :height (-> dom-node (.. (closest ".graph-page"))
.-parentNode .-clientHeight))
;; set init forces for graph
(when graph-ref
@@ -388,15 +414,15 @@
(contains? filtered-nodes-set (get link-obj "target"))))))
theme (if dark?
- THEME-DARK
- THEME-LIGHT)]
+ styles/THEME-DARK
+ styles/THEME-LIGHT)]
[:> ForceGraph2D
{:graphData {:nodes nodes
:links links}
;; example data
#_{:nodes [{"id" "foo", "name" "name1", "val" 1}
- {"id" "bar", "name" "name2", "val" 10}]
- :links [{"source" "foo", "target" "bar"}]}
+ {"id" "bar", "name" "name2", "val" 10}]
+ :links [{"source" "foo", "target" "bar"}]}
:width (:width @dimensions)
:height (:height @dimensions)
:ref #(swap! graph-ref-map assoc (or local-node-eid :global) %)
@@ -463,12 +489,8 @@
(let [local-node-eid (when block-uid
(->> [:block/uid block-uid] (d/pull @db/dsdb '[:db/id])
:db/id))]
- [:> Box {:class "graph-page"
- :gridColumn "1 / -1"
- :position "fixed"
- :top 0
- :left 0
- :width "100vw"
- :height "100vh"}
- [graph-root local-node-eid]
- [graph-controls local-node-eid]])))
+ [:div.graph-page
+ {:style (merge (when local-node-eid {:min-height "500px"})
+ {:position "relative"})}
+ [graph-controls local-node-eid]
+ [graph-root local-node-eid]])))
diff --git a/src/cljs/athens/views/pages/node_page.cljs b/src/cljs/athens/views/pages/node_page.cljs
index 973d6d460b..e2fa358cb1 100644
--- a/src/cljs/athens/views/pages/node_page.cljs
+++ b/src/cljs/athens/views/pages/node_page.cljs
@@ -1,13 +1,16 @@
(ns athens.views.pages.node-page
(:require
["/components/Block/components/Anchor" :refer [Anchor]]
- ["/components/Confirmation/Confirmation" :refer [Confirmation]]
- ["/components/Page/Page" :refer [PageHeader PageBody PageFooter EditableTitleContainer]]
- ["@chakra-ui/react" :refer [Text Box Button Portal IconButton AccordionIcon AccordionItem AccordionPanel MenuDivider MenuButton Menu MenuList MenuItem Accordion AccordionButton Breadcrumb BreadcrumbItem BreadcrumbLink VStack]]
+ ["/components/Button/Button" :refer [Button]]
+ ["/components/Dialog/Dialog" :refer [Dialog]]
+ ["@material-ui/core/Popover" :as Popover]
["@material-ui/icons/Bookmark" :default Bookmark]
["@material-ui/icons/BookmarkBorder" :default BookmarkBorder]
["@material-ui/icons/BubbleChart" :default BubbleChart]
+ ["@material-ui/icons/ChevronRight" :default ChevronRight]
["@material-ui/icons/Delete" :default Delete]
+ ["@material-ui/icons/KeyboardArrowDown" :default KeyboardArrowDown]
+ ["@material-ui/icons/Link" :default Link]
["@material-ui/icons/MoreHoriz" :default MoreHoriz]
[athens.common-db :as common-db]
[athens.common.sentry :refer-macros [wrap-span-no-new-tx]]
@@ -17,21 +20,150 @@
[athens.parse-renderer :as parse-renderer :refer [parse-and-render]]
[athens.reactive :as reactive]
[athens.router :as router]
+ [athens.style :refer [color DEPTH-SHADOWS]]
[athens.util :refer [escape-str get-caret-position recursively-modify-block-for-embed]]
[athens.views.blocks.core :as blocks]
[athens.views.blocks.textarea-keydown :as textarea-keydown]
- [athens.views.hoc.perf-mon :as perf-mon]
- [athens.views.references :refer [reference-group reference-block]]
+ [athens.views.breadcrumbs :refer [breadcrumbs-list breadcrumb]]
+ [athens.views.dropdown :refer [menu-style menu-separator-style]]
+ [athens.views.hoc.perf-mon :as perf-mon]
[clojure.string :as str]
[datascript.core :as d]
+ [garden.selectors :as selectors]
[komponentit.autosize :as autosize]
[re-frame.core :as rf :refer [dispatch subscribe]]
- [reagent.core :as r])
+ [reagent.core :as r]
+ [stylefy.core :as stylefy :refer [use-style]])
(:import
(goog.events
KeyCodes)))
+;; -------------------------------------------------------------------
+;; --- material ui ---
+
+
+(def m-popover (r/adapt-react-class (.-default Popover)))
+
+
+;; Styles
+
+
+(def page-style
+ {:margin "2rem auto"
+ :padding "1rem 2rem 10rem 2rem"
+ :flex-basis "100%"
+ :max-width "55rem"})
+
+
+(def dropdown-style
+ {::stylefy/manual [[:.menu {:background (color :background-plus-2)
+ :color (color :body-text-color)
+ :border-radius "calc(0.25rem + 0.25rem)" ; Button corner radius + container padding makes "concentric" container radius
+ :padding "0.25rem"
+ :display "inline-flex"
+ :box-shadow [[(:64 DEPTH-SHADOWS) ", 0 0 0 1px rgba(0, 0, 0, 0.05)"]]}]]})
+
+
+(def page-header-style
+ {:position "relative"})
+
+
+(def title-style
+ {:position "relative"
+ :overflow "visible"
+ :flex-grow "1"
+ :margin "0.10em 0 0.10em 1rem"
+ :letter-spacing "-0.03em"
+ :white-space "pre-line"
+ :word-break "break-word"
+ :line-height "1.40em"
+ ::stylefy/manual [[:textarea {:-webkit-appearance "none"
+ :cursor "text"
+ :resize "none"
+ :transform "translate3d(0,0,0)"
+ :color "inherit"
+ :font-weight "inherit"
+ :padding "0"
+ :letter-spacing "inherit"
+ :width "100%"
+ :min-height "100%"
+ :caret-color (color :link-color)
+ :background "transparent"
+ :margin "0"
+ :font-size "inherit"
+ :line-height "inherit"
+ :border-radius "0.25rem"
+ :transition "opacity 0.15s ease"
+ :border "0"
+ :font-family "inherit"
+ :visibility "hidden"
+ :position "absolute"}]
+ [:textarea ["::-webkit-scrollbar" {:display "none"}]]
+ [:textarea:focus
+ :.is-editing {:outline "none"
+ :visibility "visible"
+ :position "relative"}]
+ [:abbr {:z-index 4}]
+ [(selectors/+ :.is-editing :span) {:visibility "hidden"
+ :position "absolute"}]]})
+
+
+(def references-style {:margin-top "3em"})
+
+
+(def references-heading-style
+ {:font-weight "normal"
+ :display "flex"
+ :padding "0 0.5rem 0 0"
+ :align-items "center"
+ ::stylefy/manual [[:svg {:margin-right "0.25em"
+ :font-size "1rem"}]]})
+
+
+(def references-list-style
+ {:font-size "14px"})
+
+
+(def references-group-title-style
+ {:color (color :link-color)
+ :margin "0 1.5rem"
+ :font-weight "500"
+ ::stylefy/manual [[:a:hover {:cursor "pointer"
+ :text-decoration "underline"}]]})
+
+
+(def references-group-style
+ {:background (color :background-minus-2 :opacity-med)
+ :padding "1rem 0.5rem"
+ :border-radius "0.25rem"
+ :margin "0.5em 0"})
+
+
+(def reference-breadcrumbs-style
+ {:font-size "12px"
+ :padding "0.25rem calc(2rem - 0.5em)"})
+
+
+(def references-group-block-style
+ {:border-top [["1px solid " (color :border-color)]]
+ :width "100%"
+ :padding-block-start "1em"
+ :margin-block-start "1em"
+ ::stylefy/manual [[:&:first-of-type {:border-top "0"
+ :margin-block-start "0"}]]})
+
+
+(def page-menu-toggle-style
+ {:position "absolute"
+ :left "-1.5rem"
+ :border-radius "1000px"
+ :padding "0.375rem 0.5rem"
+ :color (color :body-text-color :opacity-high)
+ :transform "translateY(-50%)"
+ :top "50%"})
+
+
;; Helpers
@@ -171,17 +303,10 @@
(defn placeholder-block-el
[parent-uid]
- [:> Box {:class "block-container"
- :pl "1em"
- :display "flex"}
- [:> Anchor]
- [:> Button {:variant "link"
- :flex "1 1 100%"
- :pl 1
- :textAlign "left"
- :justifyContent "flex-start"
- :onClick #(handle-new-first-child-block-click parent-uid)}
- "Click here to add content..."]])
+ [:div {:class "block-container"}
+ [:div {:style {:display "flex"}}
+ [:> Anchor]
+ [:span {:on-click #(handle-new-first-child-block-click parent-uid)} "Click here to add content..."]]])
(defn sync-title
@@ -209,52 +334,55 @@
(defn menu-dropdown
[node daily-note?]
- (let [{:block/keys [uid] sidebar
- :page/sidebar title
- :node/title} node]
- [:> Menu
- [:> MenuButton {:as IconButton
- :gridArea "menu"
- :justifySelf "flex-end"
- :alignSelf "center"
- :bg "transparent"
- :height "2.25em"
- :width "2.25em"
- :mr "0.5em"
- :borderRadius "full"
- :sx {"span" {:display "contents"}
- "button svg:first-of-type" {:marginRight "0.25rem"}}}
- [:> MoreHoriz]]
- [:> Portal
- [:> MenuList {:sx {"button svg:first-of-type" {:marginRight "0.25rem"}}}
- [:<>
- (if sidebar
- [:> MenuItem {:onClick #(dispatch [:left-sidebar/remove-shortcut title])}
- [:> BookmarkBorder]
- "Remove Shortcut"]
- [:> MenuItem {:onClick #(dispatch [:left-sidebar/add-shortcut title])}
- [:> Bookmark]
- [:span "Add Shortcut"]])
- [:> MenuItem {:onClick #(dispatch [:right-sidebar/open-item uid true])}
- [:> BubbleChart]
- "Show Local Graph"]]
- [:> MenuDivider]
- [:> MenuItem {:onClick (fn []
- ;; if page being deleted is in right sidebar, remove from right sidebar
- (when (contains? @(subscribe [:right-sidebar/items]) uid)
- (dispatch [:right-sidebar/close-item uid]))
- ;; if page being deleted is open, navigate to all pages
- (when (or (= @(subscribe [:current-route/page-title]) title)
- (= @(subscribe [:current-route/uid]) uid))
- (rf/dispatch [:reporting/navigation {:source :page-title-delete
- :target :all-pages
- :pane :main-pane}])
- (router/navigate :pages))
- ;; if daily note, delete page and remove from daily notes, otherwise just delete page
- (if daily-note?
- (dispatch [:daily-note/delete uid title])
- (dispatch [:page/delete title])))}
- [:> Delete] "Delete Page"]]]]))
+ (let [{:block/keys [uid] sidebar :page/sidebar title :node/title} node]
+ (r/with-let [ele (r/atom nil)]
+ [:<>
+ [:> Button {:class [(when @ele "is-active")]
+ :on-click #(reset! ele (.-currentTarget %))
+ :style page-menu-toggle-style}
+ [:> MoreHoriz]]
+ [m-popover
+ (merge (use-style dropdown-style)
+ {:style {:font-size "14px"}
+ :open (boolean @ele)
+ :anchorEl @ele
+ :onClose #(reset! ele nil)
+ :anchorOrigin #js{:vertical "bottom"
+ :horizontal "left"}
+ :marginThreshold 10
+ :transformOrigin #js{:vertical "top"
+ :horizontal "left"}
+ :classes {:root "backdrop"
+ :paper "menu"}})
+ [:div (use-style menu-style)
+ [:<>
+ (if sidebar
+ [:> Button {:on-click #(dispatch [:left-sidebar/remove-shortcut title])}
+ [:> BookmarkBorder]
+ [:span "Remove Shortcut"]]
+ [:> Button {:on-click #(dispatch [:left-sidebar/add-shortcut title])}
+ [:> Bookmark]
+ [:span "Add Shortcut"]])
+ [:> Button {:on-click #(dispatch [:right-sidebar/open-item uid true])}
+ [:> BubbleChart]
+ [:span "Show Local Graph"]]]
+ [:hr (use-style menu-separator-style)]
+ [:> Button {:on-click (fn []
+ ;; if page being deleted is in right sidebar, remove from right sidebar
+ (when (contains? @(subscribe [:right-sidebar/items]) uid)
+ (dispatch [:right-sidebar/close-item uid]))
+ ;; if page being deleted is open, navigate to all pages
+ (when (or (= @(subscribe [:current-route/page-title]) title)
+ (= @(subscribe [:current-route/uid]) uid))
+ (rf/dispatch [:reporting/navigation {:source :page-title-delete
+ :target :all-pages
+ :pane :main-pane}])
+ (router/navigate :pages))
+ ;; if daily note, delete page and remove from daily notes, otherwise just delete page
+ (if daily-note?
+ (dispatch [:daily-note/delete uid title])
+ (dispatch [:page/delete title])))}
+ [:> Delete] [:span "Delete Page"]]]]])))
(defn ref-comp
@@ -270,16 +398,15 @@
(let [{:keys [block parents embed-id]} @state
block (reactive/get-reactive-block-document (:db/id block))]
[:<>
- [:> Breadcrumb {:fontSize "0.7em" :pl 6 :opacity 0.75}
+ [breadcrumbs-list {:style reference-breadcrumbs-style}
(doall
(for [{:keys [node/title block/string block/uid]} parents]
- [:> BreadcrumbItem {:key (str "breadcrumb-" uid)}
- [:> BreadcrumbLink
- {:onClick #(let [new-B (db/get-block [:block/uid uid])
- new-P (drop-last parents)]
- (swap! state assoc :block new-B :parents new-P))}
- [parse-and-render (or title string) uid]]]))]
- [:> Box {:class "block-embed"}
+ [breadcrumb {:key (str "breadcrumb-" uid)
+ :on-click #(let [new-B (db/get-block [:block/uid uid])
+ new-P (drop-last parents)]
+ (swap! state assoc :block new-B :parents new-P))}
+ [parse-and-render (or title string) uid]]))]
+ [:div.block-embed
[blocks/block-el
(recursively-modify-block-for-embed block embed-id)
linked-ref-data
@@ -293,113 +420,114 @@
(reactive/get-reactive-linked-references [:node/title title]))]
(when (or (and daily-notes? (not-empty linked-refs))
(not daily-notes?))
- [:> Accordion {:index (if (get @state linked?) 0 nil)}
- [:> AccordionItem
- [:h2
- [:> AccordionButton {:onClick (fn [] (swap! state update linked? not))}
- [:> AccordionIcon]
- linked?
- [:> Text {:ml "auto"
- :fontWeight "medium"
- :color "foreground.secondary"
- :borderRadius "full"}
- (count linked-refs)]]]
- [:> AccordionPanel {:p 0}
- [:> VStack {:spacing 6
- :pl 9
- :align "stretch"}
+ [:section (use-style references-style)
+ [:h4 (use-style references-heading-style)
+ [:> Button {:on-click (fn [] (swap! state update linked? not))}
+ (if (get @state linked?)
+ [:> KeyboardArrowDown]
+ [:> ChevronRight])]
+ [(r/adapt-react-class Link)]
+ [:div {:style {:display "flex"
+ :flex "1 1 100%"
+ :justify-content "space-between"}}
+ [:span linked?]]]
+ (when (get @state linked?)
+ [:div (use-style references-list-style)
(doall
(for [[group-title group] linked-refs]
- [reference-group {:key (str "group-" group-title)
- :title group-title
- :on-click-title (fn [e]
- (let [shift? (.-shiftKey e)
- parsed-title (parse-renderer/parse-title group-title)]
- (rf/dispatch [:reporting/navigation {:source :main-page-linked-refs ; NOTE: this might be also used in right-pane situation
- :target :page
- :pane (if shift?
- :right-pane
- :main-pane)}])
- (router/navigate-page parsed-title e)))}
+ [:div (use-style references-group-style {:key (str "group-" group-title)})
+ [:h4 (use-style references-group-title-style)
+ [:a {:on-click (fn [e]
+ (let [shift? (.-shiftKey e)
+ parsed-title (parse-renderer/parse-title group-title)]
+ (rf/dispatch [:reporting/navigation {:source :main-page-linked-refs ; NOTE: this might be also used in right-pane situation
+ :target :page
+ :pane (if shift?
+ :right-pane
+ :main-pane)}])
+ (router/navigate-page parsed-title e)))}
+ group-title]]
(doall
(for [block group]
- [reference-block {:key (str "ref-" (:block/uid block))}
- [ref-comp block]]))]))]]]])))
+ ^{:key (str "ref-" (:block/uid block))}
+ [:div {:style {:display "flex"
+ :flex "1 1 100%"
+ :justify-content "space-between"
+ :align-items "flex-start"}}
+ [:div (use-style references-group-block-style)
+ [ref-comp block]]]))]))])])))
(defn unlinked-ref-el
[state daily-notes? unlinked-refs title]
(let [unlinked? "Unlinked References"]
(when (not daily-notes?)
- [:> Accordion {:index (if (get @state unlinked?) 0 nil)}
- [:> AccordionItem {:isDisabled (empty @unlinked-refs)}
- [:> Box {:as "h2" :position "relative"}
- [:> AccordionButton {:onClick (fn []
- (if (get @state unlinked?)
- (swap! state assoc unlinked? false)
- (let [un-refs (get-unlinked-references (escape-str title))]
- (swap! state assoc unlinked? true)
- (reset! unlinked-refs un-refs))))}
- [:> AccordionIcon]
- unlinked?
- [:> Text {:ml "auto"
- :fontWeight "medium"
- :color "foreground.secondary"
- :borderRadius "full"}
- (count @unlinked-refs)]]
- (when (and unlinked? (not-empty @unlinked-refs))
- [:> Button {:position "absolute"
- :size "xs"
- :top 1
- :right 1
- :onClick (fn []
- (let [unlinked-str-ids (->> @unlinked-refs
- (mapcat second)
- (map #(select-keys % [:block/string :block/uid])))] ; to remove the unnecessary data before dispatching the event
- (dispatch [:unlinked-references/link-all unlinked-str-ids title]))
-
+ [:section (use-style references-style)
+ [:h4 (use-style references-heading-style)
+ [:> Button {:on-click (fn []
+ (if (get @state unlinked?)
(swap! state assoc unlinked? false)
-
- (reset! unlinked-refs []))}
- "Link All"])]
- [:> AccordionPanel {:p 0}
- [:> VStack {:spacing 6
- :pl 1
- :align "stretch"}
+ (let [un-refs (get-unlinked-references (escape-str title))]
+ (swap! state assoc unlinked? true)
+ (reset! unlinked-refs un-refs))))}
+ (if (get @state unlinked?)
+ [:> KeyboardArrowDown]
+ [:> ChevronRight])]
+ [(r/adapt-react-class Link)]
+ [:div {:style {:display "flex"
+ :justify-content "space-between"
+ :width "100%"}}
+ [:span unlinked?]
+ (when (and unlinked? (not-empty @unlinked-refs))
+ [:> Button {:style {:font-size "14px"}
+ :on-click (fn []
+ (let [unlinked-str-ids (->> @unlinked-refs
+ (mapcat second)
+ (map #(select-keys % [:block/string :block/uid])))] ; to remove the unnecessary data before dispatching the event
+ (dispatch [:unlinked-references/link-all unlinked-str-ids title]))
+
+ (swap! state assoc unlinked? false)
+
+ (reset! unlinked-refs []))}
+ "Link All"])]]
+ (when (get @state unlinked?)
+ [:div (use-style references-list-style)
(doall
(for [[[group-title] group] @unlinked-refs]
- [reference-group
- {:title group-title
- :on-click-title (fn [e]
- (let [shift? (.-shiftKey e)
- parsed-title (parse-renderer/parse-title group-title)]
- (rf/dispatch [:reporting/navigation {:source :main-unlinked-refs ; NOTE: this isn't always `:main-unlinked-refs` it can also be `:right-pane-unlinked-refs`
- :target :page
- :pane (if shift?
- :right-pane
- :main-pane)}])
- (router/navigate-page parsed-title e)))}
+ [:div (use-style references-group-style {:key (str "group-" group-title)})
+ [:h4 (use-style references-group-title-style)
+ [:a {:on-click (fn [e]
+ (let [shift? (.-shiftKey e)
+ parsed-title (parse-renderer/parse-title group-title)]
+ (rf/dispatch [:reporting/navigation {:source :main-unlinked-refs ; NOTE: this isn't always `:main-unlinked-refs` it can also be `:right-pane-unlinked-refs`
+ :target :page
+ :pane (if shift?
+ :right-pane
+ :main-pane)}])
+ (router/navigate-page parsed-title e)))}
+ group-title]]
(doall
(for [block group]
- [reference-block
- {:key (str "ref-" (:block/uid block))
- :actions (when unlinked?
- [:> Button {:marginTop "1.5em"
- :size "xs"
- :flex "0 0"
- :float "right"
- :variant "link"
- :onClick (fn []
- (let [hm (into (hash-map) @unlinked-refs)
- new-unlinked-refs (->> (update-in hm [group-title] #(filter (fn [{:keys [block/uid]}]
- (= uid (:block/uid block)))
- %))
- seq)]
- ;; ctrl-z doesn't work though, because Unlinked Refs aren't reactive to datascript.
- (reset! unlinked-refs new-unlinked-refs)
- (dispatch [:unlinked-references/link block title])))}
- "Link"])}
- [ref-comp block]]))]))]]]])))
+ ^{:key (str "ref-" (:block/uid block))}
+ [:div {:style {:display "flex"
+ :justify-content "space-between"
+ :align-items "flex-start"}}
+ [:div (merge
+ (use-style references-group-block-style)
+ {:style {:max-width "90%"}})
+ [ref-comp block]]
+ (when unlinked?
+ [:> Button {:style {:margin-top "1.5em"}
+ :on-click (fn []
+ (let [hm (into (hash-map) @unlinked-refs)
+ new-unlinked-refs (->> (update-in hm [group-title] #(filter (fn [{:keys [block/uid]}]
+ (= uid (:block/uid block)))
+ %))
+ seq)]
+ ;; ctrl-z doesn't work though, because Unlinked Refs aren't reactive to datascript.
+ (reset! unlinked-refs new-unlinked-refs)
+ (dispatch [:unlinked-references/link block title])))}
+ "Link"])]))]))])])))
;; TODO: where to put page-level link filters?
@@ -422,26 +550,48 @@
daily-note? (dates/is-daily-note uid)
on-daily-notes? (= :home @(subscribe [:current-route/name]))]
+
(sync-title title state)
- [:<>
+ [:div (use-style page-style {:class ["node-page"]
+ :data-uid uid})
+
+ (when alert-show
+ [:> Dialog {:isOpen true
+ :title message
+ :onConfirm confirm-fn
+ :onDismiss cancel-fn}])
- [:> Confirmation {:isOpen alert-show
- :title message
- :onConfirm confirm-fn
- :onClose cancel-fn}]
;; Header
- [:> PageHeader
+ [:header (use-style page-header-style)
;; Dropdown
[menu-dropdown node daily-note?]
- [:> EditableTitleContainer
- {:isEditing @(subscribe [:editing/is-editing uid])}
+ [:h1 (use-style title-style
+ {:data-uid uid
+ :class "page-header"
+ :on-click (fn [e]
+ (let [shift? (.-shiftKey e)]
+ (.. e preventDefault)
+ (if (or daily-note? shift?)
+ (do
+ (rf/dispatch [:reporting/navigation {:source :page-title ; NOTE: this might be also used in right-pane situation
+ :target (if title
+ :page
+ :block)
+ :pane (if shift?
+ :right-pane
+ :main-pane)}])
+ (if title
+ (router/navigate-page title e)
+ (router/navigate-uid uid e)))
+ (dispatch [:editing/uid uid]))))})
;; Prevent editable textarea if a node/title is a date
;; Don't allow title editing from daily notes, right sidebar, or node-page itself.
+
(when-not daily-note?
[autosize/textarea
{:value (:title/local @state)
@@ -460,23 +610,20 @@
[perf-mon/hoc-perfmon {:span-name "parse-and-render"}
[parse-renderer/parse-and-render (:title/local @state) uid]])]]
- [:> PageBody
-
- ;; Children
- (if (empty? children)
- [placeholder-block-el uid]
- [:div
- (for [{:block/keys [uid] :as child} children]
- ^{:key uid}
- [perf-mon/hoc-perfmon {:span-name "block-el"}
- [blocks/block-el child]])])]
+ ;; Children
+ (if (empty? children)
+ [placeholder-block-el uid]
+ [:div
+ (for [{:block/keys [uid] :as child} children]
+ ^{:key uid}
+ [perf-mon/hoc-perfmon {:span-name "block-el"}
+ [blocks/block-el child]])])
;; References
- [:> PageFooter
- [perf-mon/hoc-perfmon-no-new-tx {:span-name "linked-ref-el"}
- [linked-ref-el state on-daily-notes? title]]
- [perf-mon/hoc-perfmon-no-new-tx {:span-name "unlinked-ref-el"}
- [unlinked-ref-el state on-daily-notes? unlinked-refs title]]]]))))
+ [perf-mon/hoc-perfmon-no-new-tx {:span-name "linked-ref-el"}
+ [linked-ref-el state on-daily-notes? title]]
+ [perf-mon/hoc-perfmon-no-new-tx {:span-name "unlinked-ref-el"}
+ [unlinked-ref-el state on-daily-notes? unlinked-refs title]]]))))
(defn page
diff --git a/src/cljs/athens/views/pages/page.cljs b/src/cljs/athens/views/pages/page.cljs
index 7146879b95..bcb5061db0 100644
--- a/src/cljs/athens/views/pages/page.cljs
+++ b/src/cljs/athens/views/pages/page.cljs
@@ -1,6 +1,5 @@
(ns athens.views.pages.page
(:require
- ["/components/Page/Page" :refer [PageContainer]]
[athens.common-db :as common-db]
[athens.db :as db]
[athens.reactive :as reactive]
@@ -14,8 +13,7 @@
(let [title (rf/subscribe [:current-route/page-title])
page-eid (common-db/e-by-av @db/dsdb :node/title @title)]
(if (int? page-eid)
- [:> PageContainer {:uid page-eid :type "node"}
- [node-page/page page-eid]]
+ [node-page/page page-eid]
[:h3 (str "404: Page with title '" @title "' doesn't exist")])))
@@ -24,8 +22,7 @@
[]
(let [uid (rf/subscribe [:current-route/uid])
{:keys [node/title block/string db/id]} (reactive/get-reactive-block-or-page-by-uid @uid)]
- [:> PageContainer {:uid @uid :type (if title "node" "block")}
- (cond
- title [node-page/page id]
- string [block-page/page id]
- :else [:h3 "404: This page doesn't exist"])]))
+ (cond
+ title [node-page/page id]
+ string [block-page/page id]
+ :else [:h3 "404: This page doesn't exist"])))
diff --git a/src/cljs/athens/views/pages/settings.cljs b/src/cljs/athens/views/pages/settings.cljs
index 9d2d454587..a51cf38786 100644
--- a/src/cljs/athens/views/pages/settings.cljs
+++ b/src/cljs/athens/views/pages/settings.cljs
@@ -1,16 +1,58 @@
(ns athens.views.pages.settings
(:require
- ["@chakra-ui/react" :refer [Text Heading Box FormControl FormLabel ButtonGroup Grid Input Button Switch Modal ModalOverlay ModalContent ModalHeader ModalBody ModalCloseButton]]
+ ["/components/Button/Button" :refer [Button]]
+ ["/components/Toggle/Toggle" :refer [Toggle]]
+ ["@material-ui/icons/Check" :default Check]
+ ["@material-ui/icons/NotInterested" :default NotInterested]
[athens.db :refer [default-athens-persist]]
- [athens.util :refer [toast]]
+ [athens.views.textinput :as textinput]
[cljs-http.client :as http]
[cljs.core.async :refer [js {:title "Account connected"
- :status "success"})))
+ (update-fn @value)
;; Open Collective Lambda doesn't find email
(and (:success resp) (false? (:email_exists (:body resp))))
(do
(update-fn nil)
-
- (toast (clj->js {:title "Account not found"
- :status "error"
- :description "No OpenCollective account was found with this email address."})))
+ (js/alert "No OpenCollective account was found with this email address."))
;; Something else, e.g. networking error
:else
- (toast (clj->js {:title "Unknown error"
- :status "error"
- :description resp})))))))
+ (js/alert (str "Unexpected error" resp)))))))
(defn handle-reset-email
@@ -75,49 +110,13 @@
;; Components
-(defn title
- [children]
- [:> Heading {:size "md"}
- children])
-
-
-(defn header
- [children]
- [:> Box {:gridArea "header"} children])
-
-
-(defn glance
- [children]
- [:> Box children])
-
-
-(defn form
- [children]
- [:> Box {:gridArea "form"} children])
-
-
-(defn help
- [children]
- [:> Text {:color "foreground.secondary"
- :gridArea "help"} children])
-
-
(defn setting-wrapper
([children]
[setting-wrapper {} children])
([config children]
(let [{:keys [disabled] :as _props} config]
- [:> Grid {:as "section"
- :py 7
- :gap "1rem"
- :gridTemplateColumns "12rem 1fr"
- :gridTemplateAreas "'header form'
- 'header help'"
- :_first {:borderTop "none"}
- :_notFirst {:borderTop "1px solid"
- :borderColor "separator.divider"}
- :sx {"*" {:opacity (if disabled 0.5 1)}}}
- children])))
+ [:div (stylefy/use-style settings-wrap-style
+ {:class [(when disabled "disabled")]}) children])))
(defn email-comp
@@ -128,43 +127,48 @@
(fn []
[setting-wrapper
[:<>
- [header
- [title "OpenCollective Address"]
- [glance (if (clojure.string/blank? email)
- "Not set"
- email)]]
- [form
- [:<> [:> FormControl
- [:> FormLabel "Email address"]
- [:> Input {:type " email "
- :width "25em"
- :placeholder " Open Collective Email "
- :onChange #(reset! value (.. % -target -value))
- :value @value}]]
- [:> ButtonGroup {:pt 2}
- [:> Button {:isDisabled (not (clojure.string/blank? email))
- :onClick #(handle-submit-email value update-fn)}
- "Submit"]
- [:> Button {:onClick #(handle-reset-email value update-fn)}
- "Reset"]]]]
- [help
- [:p (if (clojure.string/blank? email)
- "You are using the free version of Athens. You are hosting your own data. Please be careful!"
- "Thank you for supporting Athens! Backups are coming soon.")]]]])))
+ [:header
+ [:h3 "Email"]
+ [:span.glance (if (clojure.string/blank? email)
+ "Not set"
+ email)]]
+ [:main
+ [:div
+ [textinput/textinput {:type " email "
+ :placeholder " Open Collective Email "
+ :on-change #(reset! value (.. % -target -value))
+ :value @value}]
+ [:> Button {:is-primary true
+ :disabled (not (clojure.string/blank? email))
+ :on-click #(handle-submit-email value update-fn)}
+ "Submit"]
+ [:> Button {:on-click #(handle-reset-email value update-fn)}
+ "Reset"]]
+ [:aside
+ [:p (if (clojure.string/blank? email)
+ "You are using the free version of Athens. You are hosting your own data. Please be careful!"
+ "Thank you for supporting Athens! Backups are coming soon.")]]]]])))
(defn monitoring-comp
[monitoring update-fn]
[setting-wrapper
[:<>
- [header
- [title "Usage and Diagnostics"]]
- [form
- [:> Switch {:defaultChecked monitoring
- :onChange #(handle-monitoring-click monitoring update-fn)}
- "Send usage data and diagnostics to Athens"]]
- [help
- [:<> [:p "Athens has never and will never look at the contents of your database."]
+ [:header
+ [:h3 "Usage and Diagnostics"]
+ [:span.glance (if (true? monitoring)
+ [:<>
+ [:> Check]
+ [:span "Sending usage data"]]
+ [:<>
+ [:> NotInterested]
+ [:span "Not sending usage data"]])]]
+ [:main
+ [:> Toggle {:defaultSelected monitoring
+ :on-change #(handle-monitoring-click monitoring update-fn)}
+ "Send usage data and diagnostics to Athens"]
+ [:aside
+ [:p "Athens has never and will never look at the contents of your database."]
[:p "Athens will never ever sell your data."]]]]])
@@ -172,23 +176,21 @@
[backup-time update-fn]
[setting-wrapper
[:<>
- [header
- [title "On-disk Backups"]]
- [form
- [:> FormControl
- [:> FormLabel "Idle time before saving new backup"]
- [:> Input {:type "number"
- :defaultValue backup-time
- :width "6em"
- :mr "0.5rem"
- :min 0
- :step 15
- :max 100
- :onBlur #(update-fn (.. % -target -value))}]
- " seconds"]]
- [help
- [:<> [:> Text "Changes are saved immediately."]
- [:> Text (str "Athens will save a new backup " backup-time " seconds after your last edit.")]]]]])
+ [:header
+ [:h3 "Backups"]
+ [:span.glance (str backup-time " seconds after last edit")]]
+ [:main
+ [:label
+ [textinput/textinput {:type "number"
+ :defaultValue backup-time
+ :min 0
+ :step 15
+ :max 100
+ :on-blur #(update-fn (.. % -target -value))}]
+ " seconds"]
+ [:aside
+ [:p "Changes are saved immediately."]
+ [:p (str "Athens will save a new backup " backup-time " seconds after your last edit.")]]]]])
(defn remote-backups-comp
@@ -196,30 +198,35 @@
[setting-wrapper
{:disabled true}
[:<>
- [header
- [title "Remote Backups"]
- [glance "Coming soon to "
+ [:header
+ [:h3 "Remote Backups"]
+ [:span.glance "Coming soon to "
[:a {:href "https://opencollective.com/athens"
:target "_blank"
:rel "noreferrer"}
" paid users and sponsors"]]]
- [form
- [:> Button {:isDisabled true} "Backup my DB to the cloud"]]]])
+ [:main
+ [:> Button {:disabled true} "Backup my DB to the cloud"]]]])
+
+
+(defn settings-container
+ [child]
+ [:div (stylefy/use-style settings-page-styles) child])
(defn reset-settings-comp
[reset-fn]
[setting-wrapper
[:<>
- [header
- [title "Reset settings"]]
- [form
- [:> Button {:onClick reset-fn}
- "Reset all settings to defaults"]]
- [help
- [:<> [:> Text "All settings saved between sessions will be restored to defaults."]
- [:> Text "Databases on disk will not be deleted, but you will need to add them to Athens again."]
- [:> Text "Athens will restart after reset and open the default database path."]]]]])
+ [:header
+ [:h3 "Reset settings"]]
+ [:main
+ [:> Button {:on-click reset-fn}
+ "Reset all settings to defaults"]
+ [:aside
+ [:p "All settings saved between sessions will be restored to defaults."]
+ [:p "Databases on disk will not be deleted, but you will need to add them to Athens again."]
+ [:p "Athens will restart after reset and open the default database path."]]]]])
(reg-event-fx
@@ -238,24 +245,13 @@
(defn page
[]
(let [{:keys [email monitoring backup-time]} @(subscribe [:settings])]
- [:> Modal {:isOpen true
- :scrollBehavior "inside"
- :onClose #(.back js/window.history)
- :size "xl"}
- [:> ModalOverlay]
- [:> ModalContent {:maxWidth "calc(100% - 8rem)"
- :width "50rem"
- :my "4rem"}
- [:> ModalHeader
- {:borderBottom "1px solid" :borderColor "separator.divider"}
- "Settings"
- [:> ModalCloseButton]]
- [:> ModalBody {:flexDirection "column"}
- [:<>
- [email-comp email #(dispatch [:settings/update :email %])]
- [monitoring-comp monitoring #(dispatch [:settings/update :monitoring %])]
- [backup-comp backup-time (fn [x]
- (dispatch [:settings/update :backup-time x])
- (dispatch [:fs/update-write-db]))]
- [remote-backups-comp]
- [reset-settings-comp #(dispatch [:settings/reset])]]]]]))
+ [settings-container
+ [:<>
+ [:h1 "Settings"]
+ [email-comp email #(dispatch [:settings/update :email %])]
+ [monitoring-comp monitoring #(dispatch [:settings/update :monitoring %])]
+ [backup-comp backup-time (fn [x]
+ (dispatch [:settings/update :backup-time x])
+ (dispatch [:fs/update-write-db]))]
+ [remote-backups-comp]
+ [reset-settings-comp #(dispatch [:settings/reset])]]]))
diff --git a/src/cljs/athens/views/references.cljs b/src/cljs/athens/views/references.cljs
deleted file mode 100644
index 8dc8bc756e..0000000000
--- a/src/cljs/athens/views/references.cljs
+++ /dev/null
@@ -1,44 +0,0 @@
-(ns athens.views.references
- (:require
- ["@chakra-ui/react" :refer [Box Button Heading VStack]]))
-
-
-(defn reference-header
- ([props]
- (let [{:keys [on-click title]} props]
- [:> Heading {:as "h4"
- :color "foreground.secondary"
- :textTransform "uppercase"
- :pt 4
- :borderTop "1px solid"
- :borderTopColor "separator.divider"
- :fontWeight "bold"
- :fontSize "0.75rem"
- :size "md"}
- [:> Button {:onClick on-click
- :color "inherit"
- :textTransform "inherit"
- :_hover {:textDecoration "none"
- :opacity 0.5}
- :fontWeight "inherit"
- :fontSize "inherit"
- :variant "link"}
- title]])))
-
-
-(defn reference-group
- ([props children]
- (let [{:keys [on-click-title title]} props]
- [:> VStack {:spacing 2 :align "stretch"}
- [reference-header {:on-click on-click-title
- :title title}]
- children])))
-
-
-(defn reference-block
- ([props children]
- (let [{:keys [actions]} props]
- [:> Box
- children
- actions])))
-
diff --git a/src/cljs/athens/views/right_sidebar.cljs b/src/cljs/athens/views/right_sidebar.cljs
index 9a73961d12..5cbea23ae0 100644
--- a/src/cljs/athens/views/right_sidebar.cljs
+++ b/src/cljs/athens/views/right_sidebar.cljs
@@ -1,14 +1,191 @@
(ns athens.views.right-sidebar
(:require
- ["/components/Icons/Icons" :refer [RightSidebarAddIcon]]
- ["/components/Layout/Layout" :refer [RightSidebarContainer SidebarItem]]
- ["@chakra-ui/react" :refer [Flex Text Box]]
+ ["/components/Button/Button" :refer [Button]]
+ ["@material-ui/icons/BubbleChart" :default BubbleChart]
+ ["@material-ui/icons/ChevronRight" :default ChevronRight]
+ ["@material-ui/icons/Close" :default Close]
+ ["@material-ui/icons/Description" :default Description]
+ ["@material-ui/icons/FiberManualRecord" :default FiberManualRecord]
+ ["@material-ui/icons/VerticalSplit" :default VerticalSplit]
[athens.parse-renderer :as parse-renderer]
+ [athens.style :refer [color OPACITIES ZINDICES]]
[athens.views.pages.block-page :as block-page]
[athens.views.pages.graph :as graph]
[athens.views.pages.node-page :as node-page]
[re-frame.core :refer [dispatch subscribe]]
- [reagent.core :as r]))
+ [reagent.core :as r]
+ [stylefy.core :as stylefy :refer [use-style]]))
+
+
+;; Styles
+
+
+(def sidebar-style
+ {:justify-self "stretch"
+ :overflow "hidden"
+ :width "0"
+ :grid-area "secondary-content"
+ :display "flex"
+ :justify-content "space-between"
+ :padding-top "2.75rem"
+ :transition-property "width, border, background"
+ :transition-duration "0.35s"
+ :transition-timing-function "ease-out"
+ :box-shadow [["0 -100px 0 " (color :background-minus-1) ", inset 1px 0 " (color :background-minus-1)]]
+ ::stylefy/manual [[:svg {:color (color :body-text-color :opacity-high)}]
+ [:&.is-closed {:width "0"}]
+ [:&.is-open {:width "32vw"}]
+ ["::-webkit-scrollbar" {:background (color :background-minus-1)
+ :width "0.5rem"
+ :height "0.5rem"}]
+ ["::-webkit-scrollbar-corner" {:background (color :background-minus-1)}]
+ ["::-webkit-scrollbar-thumb" {:background (color :background-plus-1)
+ :border-radius "0.5rem"}]]})
+
+
+(def sidebar-content-style
+ {:display "flex"
+ :flex "1 1 32vw"
+ :flex-direction "column"
+ :margin-left "0"
+ :overflow-y "auto"
+ ::stylefy/supports {"overflow-y: overlay"
+ {:overflow-y "overlay"}}
+ ::stylefy/manual [[:&.is-closed {:margin-left "-32vw"
+ :opacity 0}]
+ [:&.is-open {:opacity 1}]]})
+
+
+(def sidebar-section-heading-style
+ {:font-size "14px"
+ :display "flex"
+ :flex-direction "row"
+ :align-items "center"
+ :min-height "2.75rem"
+ :padding "0.5rem 1rem 0.25rem 1.5rem"
+ ::stylefy/manual [[:h1 {:font-size "inherit"
+ :margin "0 auto 0 0"
+ :line-height "1"
+ :color (color :body-text-color :opacity-med)}]]})
+
+
+(def sidebar-item-style
+ {:display "flex"
+ :flex "0 0 auto"
+ :flex-direction "column"})
+
+
+(def sidebar-item-toggle-style
+ {:margin "auto 0.5rem auto 0"
+ :flex "0 0 auto"
+ :width "1.75rem"
+ :height "1.75rem"
+ :padding "0"
+ :border-radius "1000px"
+ :cursor "pointer"
+ :place-content "center"
+ ::stylefy/manual [[:svg {:transition "transform 0.1s ease-out"
+ :margin "0"}]
+ [:&.is-open [:svg {:transform "rotate(90deg)"}]]]})
+
+
+(def sidebar-item-container-style
+ {:padding "0 0 1.25rem"
+ :line-height "1.5rem"
+ :font-size "95%"
+ :position "relative"
+ :background "inherit"
+ :z-index 1
+ ::stylefy/manual [[:h1 {:font-size "1.5em"
+ :display "-webkit-box"
+ :-webkit-box-orient "vertical"
+ :-webkit-line-clamp 1
+ :line-clamp 1
+ :overflow "hidden"
+ :text-overflow "ellipsis"}]
+ [:.node-page :.block-page {:margin-top 0}]]})
+
+
+(def sidebar-item-heading-style
+ {:font-size "100%"
+ :display "flex"
+ :flex "0 0 auto"
+ :align-items "center"
+ :padding "0.25rem 1rem"
+ :position "sticky"
+ :z-index 2
+ :background (color :background-color)
+ :box-shadow [["0 -1px 0 0" (color :border-color)]]
+ :top "0"
+ :bottom "0"
+ ::stylefy/manual [[:h2 {:font-size "inherit"
+ :flex "1 1 100%"
+ :line-height "1"
+ :margin "0"
+ :white-space "nowrap"
+ :text-overflow "ellipsis"
+ :font-weight "normal"
+ :max-width "100%"
+ :overflow "hidden"
+ :align-items "center"
+ :color (color :body-text-color)}
+ [:svg {:opacity (:opacity-med OPACITIES)
+ :display "inline"
+ :vertical-align "-4px"
+ :margin-right "0.2em"}]]
+ [:.controls {:display "flex"
+ :flex "0 0 auto"
+ :align-items "stretch"
+ :flex-direction "row"
+ :transition "opacity 0.3s ease-out"
+ :opacity "0.5"}]
+ [:&:hover [:.controls {:opacity "1"}]]
+ [:svg {:font-size "18px"}]
+ [:hr {:width "1px"
+ :background (color :background-minus-1)
+ :border "0"
+ :margin "0.25rem"
+ :flex "0 0 1px"
+ :height "1em"
+ :justify-self "stretch"}]
+ [:&.is-open [:h2 {:font-weight "500"}]]]})
+
+
+(def panel-drag-handle-style
+ {:cursor "col-resize"
+ :height "100%"
+ :position "absolute"
+ :top 0
+ :width "1px"
+ :z-index (:zindex-fixed ZINDICES)
+ :background-color (color :border-color)
+ ::stylefy/manual [[:&:after {:content "''"
+ :position "absolute"
+ :background (color :link-color)
+ :transition "opacity 0.2s ease"
+ :top 0
+ :bottom 0
+ :left 0
+ :right "-4px"
+ :opacity 0}]
+ [:&:hover:after {:opacity 0.5}]
+ [:&.is-dragging:after {:opacity 1}]]})
+
+
+(def empty-message-style
+ {:align-self "center"
+ :display "flex"
+ :flex-direction "column"
+ :margin "auto auto"
+ :align-items "center"
+ :text-align "center"
+ :color (color :body-text-color :opacity-med)
+ :font-size "80%"
+ :border-radius "0.5rem"
+ :line-height 1.3
+ ::stylefy/manual [[:svg {:opacity (:opacity-low OPACITIES)
+ :font-size "1000%"}]
+ [:p {:max-width "13em"}]]})
;; Components
@@ -16,20 +193,9 @@
(defn empty-message
[]
- [:> Box {:alignSelf "center"
- :display "flex"
- :flexDirection "column"
- :margin "auto"
- :padding 5
- :gap "1rem"
- :alignItems "center"
- :textAlign "center"
- :color "foreground.secondary"
- :fontSize "80%"
- :borderRadius "0.5rem"
- :lineHeight 1.3}
- [:> RightSidebarAddIcon {:boxSize "4rem"}]
- [:> Text {:maxWidth "15em"}
+ [:div (use-style empty-message-style)
+ [:> VerticalSplit]
+ [:p
"Hold " [:kbd "shift"] " when clicking a page link to view the page in the sidebar."]])
@@ -60,51 +226,45 @@
(js/document.removeEventListener "mousemove" move-handler)
(js/document.removeEventListener "mouseup" mouse-up-handler))
:reagent-render (fn [open? items _]
- [:> RightSidebarContainer
- {:isOpen open?
- :isDragging (:dragging @state)
- :width (:width @state)}
- [:> Box
- {:role "separator"
- :aria-orientation "vertical"
- :cursor "col-resize"
- :position "absolute"
- :top 0
- :bottom 0
- :width "1px"
- :zIndex 1
- :transitionDuration "0.2s"
- :transitionTimingFunction "ease-in-out"
- :transitionProperty "common"
- :bg "separator.divider"
- :sx {:WebkitAppRegion "no-drag"}
- :_hover {:bg "link"}
- :_active {:bg "link"}
- :_after {:content "''"
- :position "absolute"
- :sx {:WebkitAppRegion "no-drag"}
- :inset "-4px"}
- :on-mouse-down #(swap! state assoc :dragging true)
- :class (when (:dragging @state) "is-dragging")}]
- [:> Flex
- {:flexDirection "column"
- :flex 1;
- :maxHeight "calc(100vh - 3.25rem - 1px)"
- :width (str (:width @state) "vw")
- :overflowY "overlay"}
+ [:div (merge (use-style sidebar-style
+ {:class ["right-sidebar" (if open? "is-open" "is-closed")]})
+ {:style (cond-> {}
+ (:dragging @state) (assoc :transition-duration "0s")
+ open? (assoc :width (str (:width @state) "vw")))})
+ [:div (use-style panel-drag-handle-style
+ {:on-mouse-down #(swap! state assoc :dragging true)
+ :class (when (:dragging @state) "is-dragging")})]
+ [:div (use-style sidebar-content-style {:class [(if open? "is-open" "is-closed") "right-sidebar-content"]})
+ ;; [:header (use-style sidebar-section-heading-style)] ;; Waiting on additional sidebar contents
+ ;; [:h1 "Pages and Blocks"]]
+ ;; [:> Button [:> FilterList]]
(if (empty? items)
[empty-message]
(doall
- (for [[uid {:keys [node/title block/string is-graph?]}] items]
+ (for [[uid {:keys [open node/title block/string is-graph?]}] items]
^{:key uid}
- [:> SidebarItem {:defaultIsOpen true
- :onRemove #(dispatch [:right-sidebar/close-item uid])
- ;; nth 1 to get just the title
- :title (nth [parse-renderer/parse-and-render (or title string) uid] 1)}
- (cond
- is-graph? [graph/page uid]
- title [node-page/page [:block/uid uid]]
- :else [block-page/page [:block/uid uid]])])))]])})))
+ [:article (use-style sidebar-item-style)
+ [:header (use-style sidebar-item-heading-style {:class (when open "is-open")})
+ [:> Button (use-style sidebar-item-toggle-style
+ {:on-click #(dispatch [:right-sidebar/toggle-item uid])
+ :class (when open "is-open")})
+ [:> ChevronRight]]
+ [:h2
+ (cond
+ is-graph? [:<> [:> BubbleChart] [parse-renderer/parse-and-render title uid]]
+ title [:<> [:> Description] [parse-renderer/parse-and-render title uid]]
+ :else [:<> [:> FiberManualRecord] [parse-renderer/parse-and-render string uid]])]
+ [:div {:class "controls"}
+ ;; [:> Button [:> DragIndicator]]
+ ;; [:hr]
+ [:> Button {:on-click #(dispatch [:right-sidebar/close-item uid])}
+ [:> Close]]]]
+ (when open
+ [:div (use-style sidebar-item-container-style)
+ (cond
+ is-graph? [graph/page uid]
+ title [node-page/page [:block/uid uid]]
+ :else [block-page/page [:block/uid uid]])])])))]])})))
(defn right-sidebar
diff --git a/src/cljs/athens/views/textinput.cljs b/src/cljs/athens/views/textinput.cljs
new file mode 100644
index 0000000000..1d0db4722e
--- /dev/null
+++ b/src/cljs/athens/views/textinput.cljs
@@ -0,0 +1,61 @@
+(ns athens.views.textinput
+ (:require
+ [athens.db]
+ [athens.style :refer [color OPACITIES DEPTH-SHADOWS]]
+ [stylefy.core :as stylefy :refer [use-style]]))
+
+
+;; Styles
+
+
+(def textinput-style
+ {:min-height "2rem"
+ :color (color :body-text-color)
+ :caret-color (color :link-color)
+ :border-radius "0.25rem"
+ :background (color :background-minus-1)
+ :padding "0.125rem 0.5rem"
+ :border [["1px solid " (color :border-color)]]
+ :transition-property "box-shadow, border, background"
+ :transition-duration "0.1s"
+ :transition-timing-function "ease"
+ ::stylefy/manual [[:placeholder {:opacity (:opacity-med OPACITIES)}]
+ [:&:hover {:box-shadow (:4 DEPTH-SHADOWS)}]
+ [:&:focus :&:focus:hover {:outline "none"
+ :border "1px solid"
+ :box-shadow (:8 DEPTH-SHADOWS)}]]})
+
+
+(def input-wrap
+ {:position "relative"
+ :display "inline-flex"
+ :align-items "stretch"
+ :justify-content "stretch"
+ ::stylefy/manual [[:input {:padding-left "1.75rem"}]]})
+
+
+(def input-icon
+ {:position "absolute"
+ :top "50%"
+ :display "flex"
+ :pointer-events "none"
+ :transform "translateY(-50%)"
+ :left "0.375rem"
+ :color (color :body-text-color)
+ :opacity (:opacity-med OPACITIES)
+ ::stylefy/manual [[:svg {:font-size "20px"}]]})
+
+
+;; Components
+
+
+(defn textinput
+ [{:keys [style icon class] :as props}]
+ (let [props- (dissoc props :style :icon :class)]
+ (if icon
+ [:div (use-style input-wrap)
+ [:input (use-style (merge textinput-style style)
+ (merge props- {:class (vec (flatten class))}))]
+ [:span (use-style input-icon) icon]]
+ [:input (use-style (merge textinput-style style)
+ (merge props- {:class (vec (flatten class))}))])))
diff --git a/src/js/components/AppToolbar/AppToolbar.stories.tsx b/src/js/components/AppToolbar/AppToolbar.stories.tsx
index 540135621e..856725c6c8 100644
--- a/src/js/components/AppToolbar/AppToolbar.stories.tsx
+++ b/src/js/components/AppToolbar/AppToolbar.stories.tsx
@@ -1,20 +1,32 @@
import React from 'react';
+import styled from 'styled-components';
import { BADGE, Storybook } from '@/utils/storybook';
+import { mockDatabases } from '@/concept/DatabaseMenu/mockData';
import * as mockPresence from '@/PresenceDetails/mockData';
import { useAppState } from '@/utils/useAppState';
import { AppToolbar, AppToolbarProps } from './AppToolbar';
+import { DatabaseMenu } from '@/concept/DatabaseMenu';
import { PresenceDetails } from '@/PresenceDetails';
+const ToolbarStoryWrapper = styled(Storybook.Desktop)`
+ > * {
+ /* Make the macOS toolbar behave inside the story */
+ position: static !important;
+ width: 100%;
+ }
+`;
+
export default {
title: 'Sections/AppToolbar',
component: AppToolbar,
- subcomponents: { PresenceDetails },
+ subcomponents: { DatabaseMenu, PresenceDetails },
argTypes: {},
parameters: {
badges: [BADGE.DEV]
},
+ decorators: [(Story) =>
{Story()} ]
};
const Template = (args: AppToolbarProps) => {
@@ -41,6 +53,7 @@ const Template = (args: AppToolbarProps) => {
} = useAppState();
return
}
route={route}
isWinFullscreen={isWinFullscreen}
isWinFocused={isWinFocused}
diff --git a/src/js/components/AppToolbar/AppToolbar.tsx b/src/js/components/AppToolbar/AppToolbar.tsx
index 5fb920ca0f..f708956718 100644
--- a/src/js/components/AppToolbar/AppToolbar.tsx
+++ b/src/js/components/AppToolbar/AppToolbar.tsx
@@ -1,150 +1,82 @@
import React from 'react';
+import styled from 'styled-components';
+import { BubbleChart, ChevronLeft, ChevronRight, FileCopy, Help, Menu as MenuIcon, MergeType, Search, Settings, Storage, Today, ToggleOff, ToggleOn, VerticalSplit } from '@material-ui/icons';
-import {
- RightSidebarIcon,
- SearchIcon,
- MenuIcon,
- HelpIcon,
- ChevronLeftIcon,
- ChevronRightIcon,
- AllPagesIcon,
- SettingsIcon,
- ContrastIcon,
- DailyNotesIcon
-} from '@/Icons/Icons';
-
-import {
- BubbleChart,
- MoreHoriz,
-} from '@material-ui/icons';
-
-import {
- HTMLChakraProps,
- Portal,
- ThemingProps,
- Menu,
- MenuButton,
- MenuItem,
- MenuList,
- Tooltip,
- Flex,
- Button,
- ButtonOptions,
- HStack,
- IconButton,
- ButtonGroup,
- useColorMode,
- useMediaQuery
-} from '@chakra-ui/react';
-
+import { Button } from '@/Button';
import { WindowButtons } from './components/WindowButtons';
-interface ToolbarButtonProps extends ButtonOptions, HTMLChakraProps<'button'>, ThemingProps<"Button"> {
- children: React.ReactChild;
-};
-interface ToolbarIconButtonProps extends ButtonOptions, HTMLChakraProps<'button'>, ThemingProps<"Button"> {
- children: React.ReactChild;
-}
-
-const toolbarButtonStyle = {
- background: 'background.floor',
- color: "foreground.secondary",
- sx: { WebkitAppRegion: "no-drag" }
-}
+const AppToolbarWrapper = styled.header`
+ background: var(--color-background);
+ grid-area: app-header;
+ justify-content: flex-start;
+ background-clip: padding-box;
+ background: var(--background-plus-1);
+ color: var(--body-text-color---opacity-high);
+ border-bottom: 1px solid transparent;
+ align-items: center;
+ display: grid;
+ height: 48px;
+ padding-left: 10px;
+ grid-template-columns: auto 1fr auto;
+ transition: border-color 1s ease;
+ z-index: var(--zindex-sticky);
+ grid-auto-flow: column;
+ -webkit-app-region: drag;
-const toolbarIconButtonStyle = {
- background: 'background.floor',
- color: "foreground.secondary",
- sx: {
- WebkitAppRegion: "no-drag",
- "svg": {
- fontSize: "1.5em"
- }
+ .is-fullscreen & {
+ height: 44px;
}
-}
-const ToolbarButton = React.forwardRef((props: ToolbarButtonProps, ref) => {
- const { children } = props;
- return
{children} Button>
-});
+ svg {
+ font-size: 20px;
+ }
-const ToolbarIconButton = React.forwardRef((props: ToolbarIconButtonProps, ref) => {
- const { children } = props;
- return {children}
-});
+ &:hover {
+ transition: border-color 0.15s ease;
+ border-bottom-color: var(--body-text-color---opacity-lower);
+ }
-const AppToolbarWrapper = ({ children, ...props }) =>
- {children}
- ;
+ }
+`;
-export interface AppToolbarProps extends React.HTMLAttributes {
+export interface AppToolbarProps extends React.HTMLAttributes, DatabaseMenuProps, PresenceDetailsProps {
/**
* The application's current route
*/
@@ -182,6 +114,10 @@ export interface AppToolbarProps extends React.HTMLAttributes {
*/
isCommandBarOpen: boolean;
/**
+ * Whether the merge from roam dialog is open
+ */
+ isMergeDialogOpen: boolean;
+ /**
* Whether the choose database dialog is open
*/
isDatabaseDialogOpen: boolean;
@@ -207,6 +143,7 @@ export interface AppToolbarProps extends React.HTMLAttributes {
onPressGraph(): void;
onPressHelp(): void;
onPressThemeToggle(): void;
+ onPressMerge(): void;
onPressSettings(): void;
onPressHistoryBack(): void;
onPressHistoryForward(): void;
@@ -216,39 +153,7 @@ export interface AppToolbarProps extends React.HTMLAttributes {
presenceDetails?: React.FC;
}
-const SecondaryToolbarItems = (items) => {
- return
- {items.map((item) =>
-
- {item.icon}
-
- )}
-
-}
-
-const SecondaryToolbarOverflowMenu = (items) => {
- return
- {({ isOpen }) => <>
-
-
-
- {items.map((item) => (
- {item.label}
-
- ))}
-
-
- >
- }
-
-}
-
export const AppToolbar = (props: AppToolbarProps): React.ReactElement => {
-
const {
os,
route,
@@ -261,12 +166,14 @@ export const AppToolbar = (props: AppToolbarProps): React.ReactElement => {
isLeftSidebarOpen,
isRightSidebarOpen,
isCommandBarOpen,
+ isMergeDialogOpen,
onPressCommandBar: handlePressCommandBar,
onPressDailyNotes: handlePressDailyNotes,
onPressAllPages: handlePressAllPages,
onPressGraph: handlePressGraph,
onPressHelp: handlePressHelp,
onPressThemeToggle: handlePressThemeToggle,
+ onPressMerge: handlePressMerge,
onPressSettings: handlePressSettings,
onPressHistoryBack: handlePressHistoryBack,
onPressHistoryForward: handlePressHistoryForward,
@@ -279,129 +186,78 @@ export const AppToolbar = (props: AppToolbarProps): React.ReactElement => {
presenceDetails,
...rest
} = props;
- const { colorMode, toggleColorMode } = useColorMode();
- const [ canShowFullSecondaryMenu ] = useMediaQuery('(min-width: 900px)');
-
- // If the database color mode doesn't match
- // the chakra color mode, update the chakra color mode
- React.useEffect(() => {
- if (isThemeDark && colorMode !== 'dark') {
- toggleColorMode()
- } else if (!isThemeDark && colorMode !== 'light') {
- toggleColorMode()
- }
- }, [ isThemeDark, toggleColorMode ])
- const secondaryTools = [
- {
- label: "Help",
- isActive: isHelpOpen,
- onClick: handlePressHelp,
- icon:
- },
- {
- label: "Toggle theme",
- onClick: handlePressThemeToggle,
- icon:
- },
- {
- label: "Settings",
- isActive: route === '/settings',
- onClick: handlePressSettings,
- icon:
- },
- {
- label: 'Show right sidebar',
- onClick: handlePressRightSidebarToggle,
- icon:
- }
- ];
+ return (
+
+ {databaseMenu}
+
+
+
+ {isElectron && (
+ <>
+
+
+
+ >)
+ }
+
+
+
+ Find or create a page
+
+
+ {presenceDetails}
+
+
+
+ {isThemeDark ? : }
+
+
+
+
+
+
+
+ {isElectron && (os === 'windows' || os === 'linux') && (
+ )}
+ );
+};
- return (
-
-
-
- {databaseMenu}
-
-
-
-
-
- {isElectron && (
-
-
-
-
-
-
-
-
-
-
-
- )
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- }
- isActive={isCommandBarOpen}
- onClick={handlePressCommandBar}
- pl="0.5rem"
- >
- Find or create a page
-
-
+AppToolbar.Separator = styled.hr`
+ border: 0;
+ margin-inline: 0.125rem;
+ margin-block: 0;
+ block-size: auto;
+`;
- {presenceDetails}
+AppToolbar.MainControls = styled.div`
+ display: grid;
+ grid-auto-flow: column;
+ grid-gap: 0.25rem;
+ align-items: center;
+`;
- {canShowFullSecondaryMenu
- ? SecondaryToolbarItems(secondaryTools)
- : SecondaryToolbarOverflowMenu(secondaryTools)}0
+AppToolbar.SecondaryControls = styled(AppToolbar.MainControls)`
+ justify-self: flex-end;
+ margin-left: auto;
-
- {isElectron && (os === 'windows' || os === 'linux') && (
- )}
- );
-};
+ button {
+ color: inherit;
+ background: inherit;
+ }
+`;
diff --git a/src/js/components/AppToolbar/components/WindowButtons.tsx b/src/js/components/AppToolbar/components/WindowButtons.tsx
index 5ea5b09528..e06be83c78 100644
--- a/src/js/components/AppToolbar/components/WindowButtons.tsx
+++ b/src/js/components/AppToolbar/components/WindowButtons.tsx
@@ -1,134 +1,142 @@
-import { Box } from '@chakra-ui/react';
+import styled from 'styled-components';
+import { classnames } from '@/utils/classnames';
import { SvgIcon } from '@material-ui/core';
-const Wrapper = ({ children }) => {children} ;
+ }
+
+`;
export interface WindowButtonsProps {
- handlePressMinimize(): void,
+
+ handlePressMinimize(): void;
}
export const WindowButtons = ({
+ os,
isWinFocused,
isWinFullscreen,
isWinMaximized,
@@ -136,7 +144,14 @@ export const WindowButtons = ({
handlePressMaximizeRestore,
handlePressClose
}) => {
- return (
+ return (
{/* Minimize button */}
]
+};
+
+// Stories
+
+const Template = (args) => ;
+
+export const Basic = Template.bind({});
+Basic.args = {
+ username: 'Jeff Tang',
+ size: "3rem",
+ color: '#0000f7',
+};
+
+export const WithTooltip = () => (
+
+)
+
+export const Sizes = () => (
+
+)
+
+export const Colors = () => (
+ <>
+
+
+
+
+
+
+ >
+)
+
+export const isMuted = () => (
+ <>
+
+
+
+
+
+
+ >
+)
+
+export const Stack = () => (
+
+
+
+
+
+
+
+
+)
+
+export const StackNarrow = () => (
+
+
+
+
+
+
+
+
+)
+export const StackWide = () => (
+
+
+
+
+
+
+
+
+)
+
+export const OnBlocks = WithPresence;
+OnBlocks.decorators = [(Story) => ];
\ No newline at end of file
diff --git a/src/js/components/Avatar/Avatar.tsx b/src/js/components/Avatar/Avatar.tsx
new file mode 100644
index 0000000000..4a2054be8a
--- /dev/null
+++ b/src/js/components/Avatar/Avatar.tsx
@@ -0,0 +1,280 @@
+import React from "react";
+import styled, { css } from "styled-components";
+import { Fade, Popper, PopperPlacementType } from "@material-ui/core";
+import { readableColor } from "polished";
+
+import { DOMRoot } from "@/utils/config";
+
+const Wrapper = styled.svg`
+ overflow: visible;
+ cursor: default;
+ border-radius: 100%;
+ height: var(--size, 1.5em);
+ width: var(--size, 1.5em);
+`;
+
+const Name = styled.text`
+ text-anchor: middle;
+ color: var(--avatar-text-color);
+ transform: scale(0.8);
+ transform-origin: center;
+`;
+
+const FullName = styled.span`
+ background: var(--tooltip-background-color);
+ color: var(--tooltip-text-color);
+ display: inline-flex;
+ width: max-content;
+ line-height: 1;
+ padding: 0.2em 0.5em;
+ border-radius: 0.2em;
+ margin: 0.25rem;
+ pointer-events: none;
+`;
+
+export interface AvatarProps extends React.SVGProps, Person {
+ /**
+ * The primary color of the icon
+ */
+ color: string;
+ /**
+ * The full username of the person
+ */
+ username: string;
+ /**
+ * Height and width of the avatar
+ */
+ size?: string;
+ /**
+ * Whether to display the avatar in a muted visual style
+ */
+ isMuted?: boolean;
+ /**
+ * Whether to show the user's full name. Set to 'hover' to show it when interacting with the icon.
+ */
+ showTooltip?: boolean | "hover";
+ /**
+ * Where the tooltip should appear relative to the icon.
+ */
+ tooltipPlacement?: PopperPlacementType;
+ /**
+ * Whether to draw a colored circle around the icon.
+ */
+ isOutlined?: boolean
+}
+
+/**
+ * Visual representation of a human user
+ */
+export const Avatar = ({
+ username,
+ color,
+ showTooltip = "hover",
+ tooltipPlacement = "right",
+ isMuted = false,
+ size,
+ isOutlined,
+ ...props
+}: AvatarProps) => {
+ const [avatarEl, setAvatarEl] = React.useState();
+ const [isShowingTooltip, setIsShowingTooltip] = React.useState(
+ showTooltip === "hover" ? false : showTooltip
+ );
+
+ const avatarColors = {
+ "--avatar-background-opacity": isMuted ? 0.2 : 1,
+ "--avatar-background-color": color,
+ "--avatar-text-color": isMuted ? color : readableColor(color),
+ "--tooltip-text-color": readableColor(color),
+ "--tooltip-background-color": color,
+ };
+
+ let initials;
+ if (username) {
+ initials = username
+ .split(" ")
+ .map((word) => word[0])
+ .join("")
+ .slice(0, 2)
+ .toUpperCase();
+ }
+
+ return (
+ <>
+ {
+ if (showTooltip === "hover") setIsShowingTooltip(true);
+ }}
+ onMouseLeave={() => {
+ if (showTooltip === "hover") setIsShowingTooltip(false);
+ }}
+ {...props}
+ style={{ ...avatarColors, "--size": size, ...props.style }}
+ >
+
+
+ {initials || username}
+
+ {isOutlined && (
+
+ )}
+
+
+ {({ TransitionProps }) => (
+
+
+ {username}
+
+
+ )}
+
+ >
+ );
+};
+
+/*
+ * Wraps a horizontal series of avatars and causes them to overlap each other.
+ */
+interface AvatarStackProps extends React.HTMLAttributes {
+ children: JSX.Element[];
+ /**
+ * The width of the mask added to overlapping Avatars.
+ */
+ limit?: number;
+ /**
+ * The width of the mask added to overlapping Avatars.
+ */
+ maskSize?: string;
+ /**
+ * The size of Avatars. Can be overridden by the same property on child Avatars.
+ */
+ stackOrder?: "from-left" | "from-right";
+ /**
+ * How much Avatars should overlap. 0.5 is 50% overlap.
+ */
+ size?: string;
+ /**
+ * How much Avatars should overlap. 0.5 is 50% overlap.
+ */
+ overlap?: number;
+}
+
+const StackWrapper = styled.div`
+ --mask-size: ${(props) => props.maskSize || "3px"};
+ --size: ${(props) => props.size || undefined};
+ --stack-overlap: ${(props) => props.overlap || "0.5"};
+ display: inline-flex;
+ align-items: center;
+ width: max-content;
+ height: max-content;
+ border-radius: 100em;
+
+ ${Wrapper} {
+ ${(props) =>
+ props.stackOrder === `from-left`
+ ? css`
+ &:not(:last-of-type) {
+ margin-inline-end: calc(
+ var(--size, 1.5em) * (var(--stack-overlap) * -1)
+ );
+ mask-image: radial-gradient(
+ calc(var(--size, 1.5em) + var(--mask-size))
+ calc((var(--size, 1.5em) * (2 / 3)) + var(--mask-size)) at
+ calc(100% + (100% * (var(--stack-overlap)))) 50%,
+ transparent 99%,
+ #000 100%
+ );
+ }
+ `
+ : css`
+ &:not(:first-of-type) {
+ margin-inline-start: calc(
+ var(--size, 1.5em) * (var(--stack-overlap) * -1)
+ );
+ mask-image: radial-gradient(
+ calc(var(--size, 1.5em) + var(--mask-size))
+ calc((var(--size, 1.5em) * (2 / 3)) + var(--mask-size)) at
+ calc(-100% + (100% * (var(--stack-overlap)))) 50%,
+ transparent 99%,
+ #000 100%
+ );
+ }
+ `}
+ }
+`;
+
+Avatar.Stack = React.forwardRef((props: AvatarStackProps, ref) => {
+ const {
+ children,
+ limit = Infinity,
+ size,
+ maskSize = "3px",
+ overlap = 0.5,
+ stackOrder = "from-right",
+ style,
+ ...rest
+ } = props;
+
+ let Children = React.Children.toArray(children);
+ let overflow = Children.length - limit;
+
+ return (
+
+ {Children.slice(0, limit).map((avatar: JSX.Element) => React.cloneElement(avatar, { size }))}
+ {overflow > 0 && (
+
+ )}
+
+ );
+});
+
+Avatar.Wrapper = Wrapper;
+Avatar.Fullname = FullName;
+Avatar.Name = Name;
diff --git a/src/js/components/Avatar/index.ts b/src/js/components/Avatar/index.ts
new file mode 100644
index 0000000000..379dda34bf
--- /dev/null
+++ b/src/js/components/Avatar/index.ts
@@ -0,0 +1,2 @@
+import { Avatar } from './Avatar';
+export { Avatar }
\ No newline at end of file
diff --git a/src/js/components/Avatar/mockData.ts b/src/js/components/Avatar/mockData.ts
new file mode 100644
index 0000000000..9cbc800044
--- /dev/null
+++ b/src/js/components/Avatar/mockData.ts
@@ -0,0 +1,98 @@
+export const mockPeople =
+ [{
+ username: "oorgen0",
+ personId: "5058615e-b5ce-4e48-9a70-1bb48079883f",
+ color: "#accb85"
+ }, {
+ username: "pcanepe1",
+ personId: "a22896b5-0cc0-40de-ae64-7105106beb6a",
+ color: "#12b47e"
+ }, {
+ username: "jthorrold2",
+ personId: "54f83c31-ac3b-412c-b02a-794db3cbff1d",
+ color: "#c542ed"
+ }, {
+ username: "afick3",
+ personId: "2bb8342d-e276-4333-8bc8-01201adf7778",
+ color: "#7920ef"
+ }, {
+ username: "nhannah4",
+ personId: "3c48194e-6506-402f-a17b-e979b3ae6026",
+ color: "#980e37"
+ }, {
+ username: "dmcentegart5",
+ personId: "648994a7-4970-4418-85f5-6710872e4dd2",
+ color: "#3d53cb"
+ }, {
+ username: "tclimar6",
+ personId: "62201231-bcdd-4b91-9d7e-d3869bfa8c5f",
+ color: "#594977"
+ }, {
+ username: "gchivrall7",
+ personId: "fd37b6e2-1b9d-4bbd-898c-fc4981835a59",
+ color: "#4fff47"
+ }, {
+ username: "dthomel8",
+ personId: "f452b384-79e3-4850-b077-770155cbf5ee",
+ color: "#8e9c1a"
+ }, {
+ username: "blinfoot9",
+ personId: "c547ffaf-63cb-4c45-b8b4-517c46c8eaa5",
+ color: "#696cf2"
+ }, {
+ username: "phardsona",
+ personId: "390cc8be-0b09-41e5-afa5-341dc69f03c4",
+ color: "#b33a7c"
+ }, {
+ username: "cdowneb",
+ personId: "dd4a68be-1a92-4afe-856c-7dc4ec4b58b4",
+ color: "#0f5dfe"
+ }, {
+ username: "ahortopc",
+ personId: "41a06c67-eb68-465c-8190-93a2c9a20875",
+ color: "#e8487d"
+ }, {
+ username: "osuthereld",
+ personId: "63d40d9a-1615-44c7-aa3b-9a910cc1aabb",
+ color: "#4f0d1a"
+ }, {
+ username: "mgrimblebye",
+ personId: "e0357779-edf2-490e-9a43-c4918d204e3b",
+ color: "#77b002"
+ }, {
+ username: "ccoochf",
+ personId: "3d2efbf6-3f43-4fa8-9136-b9d1fb7b7615",
+ color: "#803b68"
+ }, {
+ username: "anasseyg",
+ personId: "184ffca6-8acb-41cb-ae0e-9683dc234cc7",
+ color: "#3250c9"
+ }, {
+ username: "slasselleh",
+ personId: "a590cf82-2d5b-49a6-a625-cb2d98022a3c",
+ color: "#257179"
+ }, {
+ username: "ilerouxi",
+ personId: "aaccec70-7b66-40fe-8186-cbf5660282e7",
+ color: "#096908"
+ }, {
+ username: "fdransfieldj",
+ personId: "769e3b69-7270-43fc-b3fd-0c5401a352c8",
+ color: "#e75898"
+ }, {
+ username: "dsteinhamk",
+ personId: "ab64c0e7-4d2d-415d-8b14-6996d363bf10",
+ color: "#dee1fd"
+ }, {
+ username: "glouisetl",
+ personId: "2886b735-2104-4715-a717-e7041462d507",
+ color: "#b52850"
+ }, {
+ username: "jprozesckym",
+ personId: "ee321686-ad80-4796-b640-008a3fcb88e8",
+ color: "#228c57"
+ }, {
+ username: "pdrucen",
+ personId: "beb6085e-1437-438a-a1d6-933a0f9fa870",
+ color: "#497828"
+ }];
diff --git a/src/js/components/Block/components/Anchor.tsx b/src/js/components/Block/components/Anchor.tsx
index 1533ee0151..09971e97c6 100644
--- a/src/js/components/Block/components/Anchor.tsx
+++ b/src/js/components/Block/components/Anchor.tsx
@@ -1,62 +1,114 @@
-import React, { ReactNode } from 'react';
-import { Menu, MenuList, MenuItem, MenuGroup, MenuDivider, MenuButton, IconButton, Portal, Box, Text } from '@chakra-ui/react';
+import React from 'react'
+import styled from 'styled-components';
+import { useFocusRing } from '@react-aria/focus';
+import { useTooltipTrigger } from '@react-aria/tooltip'
+import { useTooltipTriggerState } from '@react-stately/tooltip';
+import { TooltipTriggerProps } from '@react-types/tooltip';
+import { mergeProps } from '@react-aria/utils';
+import { DetailPopover } from '@/Block/components/DetailPopover';
-const ANCHORS = {
- CIRCLE:
+export const AnchorButton = styled.button`
+ flex-shrink: 0;
+ grid-area: bullet;
+ position: relative;
+ z-index: 2;
+ cursor: pointer;
+ appearance: none;
+ border: 0;
+ background: transparent;
+ transition: all 0.05s ease;
+ color: inherit;
+ margin-right: 0.25em;
+ display: flex;
+ place-items: center;
+ place-content: center;
+ padding: 0;
+ height: 2em;
+ width: 1em;
+
+ svg {
+ pointer-events: none;
+ transform: scale(1.0001); // Prevents the bullet being squished
+ overflow: visible; // Prevents the bullet being cropped
+ width: 1em;
+ height: 1em;
+ color: var(--user-color, var(--body-text-color---opacity-low));
+
+ * {
+ vector-effect: non-scaling-stroke;
+ }
+ }
+
+ circle {
+ fill: currentColor;
+ transition: fill 0.05s ease, opacity 0.05s ease;
+ }
+
+ &:focus {
+ outline: none;
+ }
+
+ &:before {
+ content: '';
+ inset: 0.25rem -0.125rem;
+ z-index: -1;
+ transition: opacity 0.1s ease;
+ position: absolute;
+ border-radius: 0.25rem;
+ opacity: 0;
+ background: var(--background-plus-2);
+ box-shadow: var(--depth-shadow-8);
+ }
+
+ &:hover {
+ color: var(--link-color);
+ z-index: 100;
+ }
+
+ &:hover,
+ &:hover:before,
+ &:focus-visible:before {
+ opacity: 1;
+ }
+
+ &.closed-with-children {
+ circle {
+ stroke: var(--body-text-color);
+ fill: var(--body-text-color---opacity-low);
+ r: 5;
+ stroke-width: 2px;
+ opacity: var(--opacity-med);
+ }
+ }
+
+ &:hover svg {
+ transform: scale(1.3);
+ }
+
+ &.dragging {
+ z-index: 1;
+ cursor: grabbing;
+ color: var(--body-text-color);
+ }
+`;
+
+const anchorElements = {
+ circle:
,
- DASH:
-
+ dash:
+
}
-const showValue = (value) => {
- if (typeof value === 'object') return (value = JSON.stringify(value));
- else if (typeof value === 'boolean') return (value = value ? 'true' : 'false');
- else return value;
-}
-
-const properties = (block) => ({
- "uid": block?.uid,
- "db/id": block?.id,
- "order": block?.order,
- "open": block?.open,
- "refs": block?._refs?.length || 0,
-});
-
-const Item = ({ children }) => {
- return ({children} )
-}
+const FocusRing = styled.div`
+ position: absolute;
+ inset: 0.25rem -0.125rem;
+ border: 2px solid var(--link-color);
+ border-radius: 0.25rem;
+`;
-const propertiesList = (block) => {
- return Object.entries(properties(block)).map(([ key, value ]) => {
- return -
-
{key}
- {showValue(value)}
-
- })
-}
-
-export interface AnchorProps {
+export interface AnchorProps extends TooltipTriggerProps {
/**
* What style of anchor to display
*/
@@ -66,120 +118,31 @@ export interface AnchorProps {
*/
isClosedWithChildren: boolean;
block: any;
- uidSanitizedBlock: any;
shouldShowDebugDetails: boolean;
- as: ReactNode;
- onContextMenu: (event: React.MouseEvent) => void;
- onCopyRef: () => void;
- onCopyUnformatted: () => void;
- onDragStart: () => void;
- onDragEnd: () => void;
- onClick: () => void;
}
-const anchorButtonStyleProps = (isClosedWithChildren) => {
- return ({
- bg: "transparent",
- "aria-label": "Block anchor",
- className: [ 'anchor', isClosedWithChildren && 'closed-with-children' ].filter(Boolean).join(' '),
- draggable: true,
- gridArea: "bullet",
- flexShrink: 0,
- position: 'relative',
- appearance: "none",
- border: "0",
- color: "inherit",
- display: "flex",
- placeItems: "center",
- placeContent: "center",
- zIndex: 2,
- minWidth: "0",
- minHeight: "0",
- h: "2em",
- w: "fit-content",
- fontSize: "inherit",
- mx: "-0.125em",
- size: "sm",
- p: 0,
- sx: {
- "svg": {
- pointerEvents: "none",
- transform: "scale(1.0001)", // Prevents the bullet being squished
- overflow: "visible", // Prevents the bullet being cropped
- width: "1em",
- height: "1em",
- color: "foreground.secondary",
- "*": {
- vectorEffect: "non-scaling-stroke"
- }
- },
- "circle": {
- transformOrigin: 'center',
- transition: 'all 0.15s ease-in-out',
- stroke: "transparent",
- strokeWidth: "0.125em",
- fill: "currentColor",
- ...isClosedWithChildren && ({
- transform: "scale(1.25)",
- stroke: 'currentColor',
- fill: "none",
- })
- }
- }
- })
-};
-
-
/**
* A handle and indicator of a block's position in the document
*/
export const Anchor = (props: AnchorProps) => {
- const { isClosedWithChildren,
- anchorElement,
- shouldShowDebugDetails,
- onCopyRef,
- onCopyUnformatted,
- onDragStart,
- onDragEnd,
- onClick,
- block,
- uidSanitizedBlock,
- } = props;
-
- const [ isOpen, setIsOpen ] = React.useState(false);
+ const { isClosedWithChildren, anchorElement, shouldShowDebugDetails, block } = props;
+ const ref = React.useRef();
+ let state = useTooltipTriggerState(props);
+ let { triggerProps, tooltipProps } = useTooltipTrigger({ delay: 500 }, state, ref);
+ const { isFocusVisible, focusProps } = useFocusRing();
return (
- setIsOpen(false)}>
- {
- e.preventDefault();
- e.stopPropagation();
- setIsOpen(true);
- }}
- as={MenuButton}
+ <>
+
- {ANCHORS[ anchorElement ] || ANCHORS.CIRCLE}
-
-
-
- Copy block refs
- Copy unformatted
- {shouldShowDebugDetails && (
- <>
-
-
-
- {propertiesList(uidSanitizedBlock)}
-
-
- >)}
-
-
-
+ {anchorElements[anchorElement] || anchorElements['circle']}
+ {isFocusVisible && }
+
+ {shouldShowDebugDetails && state.isOpen && }
+ >
)
};
diff --git a/src/js/components/Block/components/Container.tsx b/src/js/components/Block/components/Container.tsx
deleted file mode 100644
index bd9fb77845..0000000000
--- a/src/js/components/Block/components/Container.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import { Box } from "@chakra-ui/react";
-import { withErrorBoundary } from "react-error-boundary";
-
-const _Container = ({ children, isDragging, isSelected, isOpen, hasChildren, hasPresence, isLinkedRef, uid, childrenUids, ...props }) => {
- return .block-toggle, &:focus-within > .block-toggle": { opacity: "1" },
- "button.block-edit-toggle": {
- position: "absolute",
- appearance: "none",
- width: "100%",
- background: "none",
- border: 0,
- cursor: "text",
- display: "block",
- zIndex: 1,
- top: 0,
- right: 0,
- bottom: 0,
- left: 0,
- },
- ".block-embed": {
- borderRadius: "sm",
- "--block-surface-color": "background.basement",
- bg: "background.basement",
- ".block-container": { marginLeft: 0.5 },
- },
- ".block-content": {
- gridArea: "content",
- minHeight: "1.5em",
- },
- "&.is-linked-ref": { bg: "background-attic" },
- ".block-container": {
- marginLeft: "2rem",
- gridArea: "body"
- }
- }}
- {...props}
- > {children} ;
-}
-
-export const Container = withErrorBoundary(_Container, { fallback: oops
});
\ No newline at end of file
diff --git a/src/js/components/Block/components/Content.tsx b/src/js/components/Block/components/Content.tsx
deleted file mode 100644
index 5950c3e33a..0000000000
--- a/src/js/components/Block/components/Content.tsx
+++ /dev/null
@@ -1,139 +0,0 @@
-import { Box } from '@chakra-ui/react';
-import { withErrorBoundary } from 'react-error-boundary';
-
-const _Content = ({ children, fontSize, ...props }) => {
- return span": {
- gridArea: "main",
- },
- // deactivate noninteractive (text) content
- "div, span, p, blockquote": {
- pointerEvents: "none",
- },
- // activate interactive content (links, buttons)
- "a, button": {
- pointerEvents: "auto",
- zIndex: 2,
- position: "relative"
- },
- // manage the textarea interactions
- "&:hover textarea:not:(.is-editing)": { lineHeight: 2 },
- "textarea.is-editing": {
- zIndex: 3,
- lineHeight: "inherit",
- opacity: 1,
- },
- "textarea.is-editing ~ *": { opacity: "0" },
- "& > abbr": {
- gridArea: "main",
- zIndex: 4,
- "& > span": {
- position: "relative",
- zIndex: 2,
- }
- },
- // style block children
- "code, pre": {
- fontFamily: "code",
- fontSize: "0.85em",
- },
- ".media-16-9": {
- height: 0,
- width: "calc(100% - 0.25rem)",
- zIndex: 1,
- transformOrigin: "right center",
- transitionDuration: "0.2s",
- transitionTimingFunction: "ease-in-out",
- transitionProperty: "common",
- paddingBottom: "56.25%",
- marginBlock: "0.25rem",
- marginInlineEnd: "0.25rem",
- position: "relative",
- },
- "iframe": {
- border: 0,
- boxShadow: "inset 0 0 0 0.125rem",
- position: "absolute",
- height: "100%",
- width: "100%",
- cursor: "default",
- top: 0,
- right: 0,
- left: 0,
- bottom: 0,
- borderRadius: "0.25rem",
- },
- "img": {
- borderRadius: "0.25rem",
- maxWidth: "calc(100% - 0.25rem)",
- },
- "h1": { fontSize: "xl" },
- "h2": { fontSize: "lg" },
- "h3": { fontSize: "md" },
- "h4": { fontSize: "sm" },
- "h5": { fontSize: "xs" },
- "h6": { fontSize: "xs" },
- "blockquote": {
- marginInline: "0.5em",
- marginBlock: "0.125rem",
- paddingBlock: "calc(0.5em - 0.125rem - 0.125rem)",
- paddingInline: "1.5em",
- borderRadius: "0.25em",
- background: "background.basement",
- borderInlineStart: "1px solid",
- borderColor: "separator.divider",
- color: "foreground.primary",
- },
- "p": {
- paddingBottom: "1em",
- "&last:-child": { paddingBottom: 0 },
- },
- "mark.contents.highlight": {
- padding: "0 0.2em",
- borderRadius: "0.125rem",
- background: "highlight",
- }
- }
- }
- {...props}
- > {children}
-}
-
-export const Content = withErrorBoundary(_Content, { fallback: oops
});
\ No newline at end of file
diff --git a/src/js/components/Block/components/DetailPopover.stories.tsx b/src/js/components/Block/components/DetailPopover.stories.tsx
new file mode 100644
index 0000000000..f57043b3ea
--- /dev/null
+++ b/src/js/components/Block/components/DetailPopover.stories.tsx
@@ -0,0 +1,23 @@
+import { DetailPopover } from './DetailPopover';
+import { BADGE, Storybook } from '@/utils/storybook';
+
+export default {
+ title: 'components/DetailPopover',
+ component: DetailPopover,
+ argTypes: {},
+ parameters: {
+ layout: 'centered',
+ badges: [BADGE.DEV]
+ },
+ decorators: [(Story) => ]
+};
+
+export const Typical = () => ;
diff --git a/src/js/components/Block/components/DetailPopover.tsx b/src/js/components/Block/components/DetailPopover.tsx
new file mode 100644
index 0000000000..e56a1dd775
--- /dev/null
+++ b/src/js/components/Block/components/DetailPopover.tsx
@@ -0,0 +1,77 @@
+import React from 'react';
+import styled from 'styled-components';
+import { TooltipTriggerState } from '@react-stately/tooltip';
+import { useTooltip } from '@react-aria/tooltip';
+
+import { Overlay } from '@/Overlay';
+
+const Details = styled(Overlay)`
+ height: max-content;
+ width: max-content;
+ display: flex;
+ flex-direction: column;
+ list-style: none;
+ margin: 0;
+ padding: 0.25rem 0.5rem;
+ font-size: var(--font-size--text-sm);
+ gap: -0.125rem;
+ position: relative;
+ top: 0.25rem;
+ left: 2.5rem;
+ z-index: 9999;
+ line-height: 1.3;
+`;
+
+const Item = styled.li`
+ margin: 0;
+ padding: 0;
+ display: flex;
+ justify-content: space-between;
+
+ span {
+ color: var(--body-text-color---opacity-med);
+ flex: 1 1 50%;
+ }
+ span + span {
+ margin-left: 1ch;
+ color: var(--body-text-color);
+ }
+`;
+
+const showValue = (value) => {
+ if (typeof value === 'object') return (value = JSON.stringify(value));
+ else if (typeof value === 'boolean') return (value = value ? 'true' : 'false');
+ else return value;
+}
+
+interface DetailPopoverProps extends React.HTMLAttributes {
+ state: TooltipTriggerState;
+ block: any;
+}
+
+export const DetailPopover = React.forwardRef((props: DetailPopoverProps, ref) => {
+ const { block, state, } = props;
+ let { tooltipProps } = useTooltip(props, state);
+
+ const properties = {
+ "uid": block.uid,
+ "db/id": block.id,
+ "order": block.order,
+ "open": block.open,
+ "refs": block._refs?.length || 0,
+ }
+
+ return (
+ { e.stopPropagation(); }}
+ onClick={(e) => { e.stopPropagation(); }}
+ >
+ {Object.entries(properties).map(([key, value]) => -
+
{key} {showValue(value)}
+ )}
+
+ );
+});
diff --git a/src/js/components/Block/components/Toggle.tsx b/src/js/components/Block/components/Toggle.tsx
index bd357f4df9..fc3b14e45c 100644
--- a/src/js/components/Block/components/Toggle.tsx
+++ b/src/js/components/Block/components/Toggle.tsx
@@ -1,56 +1,94 @@
import React from 'react'
-import { IconButton } from '@chakra-ui/react';
+import styled from 'styled-components';
+import { useFocusRing } from '@react-aria/focus';
+import { mergeProps } from '@react-aria/utils';
+
+export const ToggleButton = styled.button`
+ width: 1em;
+ grid-area: toggle;
+ height: 2em;
+ position: relative;
+ z-index: 2;
+ flex-shrink: 0;
+ display: flex;
+ background: none;
+ border: none;
+ transition: color 0.05s ease;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ color: inherit;
+ padding: 0;
+ -webkit-appearance: none;
+ color: var(--body-text-color---opacity-low);
+
+ &:focus {
+ outline: none;
+ }
+
+ &:hover {
+ color: var(--body-text-color---opacity-med);
+ }
+
+ &:before {
+ content: '';
+ inset: 0.25rem -0.125rem;
+ z-index: -1;
+ position: absolute;
+ transition: opacity 0.1s ease;
+ border-radius: 0.25rem;
+ opacity: 0;
+ background: var(--background-plus-2);
+ box-shadow: var(--depth-shadow-8);
+ }
+
+ &:hover:before,
+ &:focus-visible:before {
+ opacity: 1;
+ }
+
+ svg {
+ transform: rotate(90deg);
+ vector-effect: non-scaling-stroke;
+ transition: transform 0.1s ease-in-out;
+ }
+
+ &.closed {
+ svg {
+ transform: rotate(0deg);
+ };
+ }
+
+ &:empty {
+ pointer-events: none;
+ }
+`;
+
+const FocusRing = styled.div`
+ position: absolute;
+ inset: 0.25rem -0.125rem;
+ border: 2px solid var(--link-color);
+ border-radius: 0.25rem;
+`;
interface ToggleProps extends React.HTMLAttributes {
isOpen: boolean;
- onClick: () => void;
}
/**
* Button to toggle the visibility of a block's child blocks.
*/
-export const Toggle = (props) => {
- const { isOpen, onClick } = props;
+export const Toggle = React.forwardRef((props: ToggleProps, ref) => {
+ const { isFocusVisible, focusProps } = useFocusRing();
+ const {
+ isOpen,
+ } = props;
return (
-
{
-
+ {isFocusVisible && }
+
)
-};
-
-// /**
-// * Button to toggle the visibility of a block's child blocks.
-// */
-// export const Toggle = React.forwardRef((props: ToggleProps, ref) => {
-// const { isOpen, onClick } = props;
-
-// return (
-//
-//
-//
-//
-//
-// )
-// });
+});
diff --git a/src/js/components/BrowserApp.stories.tsx b/src/js/components/BrowserApp.stories.tsx
new file mode 100644
index 0000000000..593f8e83ad
--- /dev/null
+++ b/src/js/components/BrowserApp.stories.tsx
@@ -0,0 +1,230 @@
+import styled from 'styled-components';
+
+import { Storybook } from '@/utils/storybook';
+import { classnames } from '@/utils/classnames';
+import { getOs } from '@/utils/getOs';
+
+import { useAppState } from '@/utils/useAppState';
+
+import { LeftSidebar } from '@/concept/LeftSidebar';
+import { RightSidebar } from '@/concept/RightSidebar';
+import { AppToolbar } from '@/AppToolbar';
+import { CommandBar } from '@/concept/CommandBar';
+import { AppLayout, MainContent } from '@/concept/App';
+import { Page } from '@/concept/Page';
+import { WithToggle } from '@/concept/Block/Block.stories';
+
+export default {
+ title: 'App/Browser',
+ component: Window,
+ argTypes: {},
+ parameters: {
+ layout: 'fullscreen'
+ },
+ decorators: [(Story) => ]
+};
+
+const BrowserWrapper = styled.div`
+ width: 100%;
+ border-radius: 5px;
+ box-shadow: 0 10px 12px rgb(0 0 0 / 0.1);
+ overflow: hidden;
+ position: relative;
+ background: var(--background-color);
+ --browser-toolbar-height: 48px;
+
+ > * {
+ z-index: 1;
+ }
+
+ &.is-storybook-docs {
+ height: 700px;
+ }
+
+ ${AppToolbar} {
+ height: calc(100vh - var(--browser-toolbar-height));
+ margin-top: 52px;
+ }
+
+ #app-layout {
+ height: calc(100% - var(--browser-toolbar-height));
+ margin-top: var(--browser-toolbar-height);
+ }
+`;
+
+const BrowserToolbarWrapper = styled.div`
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ height: var(--browser-toolbar-height);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--background-minus-2);
+ pointer-events: none;
+
+ span {
+ padding: 0.25rem 1rem;
+ color: var(--body-text-color---opacity-med);
+ font-weight: bold;
+ }
+
+ input {
+ border-radius: 100em;
+ padding: 0.25rem;
+ border: 0;
+ background: var(--background-plus-1);
+ color: inherit;
+ width: max(70%, 70em);
+ height: 60%;
+ text-align: center;
+ margin: auto;
+ color: var(--body-text-color---opacity-med);
+ }
+`;
+
+const BrowserToolbar = () => {
+ return (
+
+ Browser
+
+
+ )
+}
+
+const Template = (args, context) => {
+ const {
+ currentUser,
+ setCurrentUser,
+ isSynced,
+ route,
+ currentPageMembers,
+ differentPageMembers,
+ activeDatabase,
+ setActiveDatabase,
+ inactiveDatabases,
+ connectionStatus,
+ isElectron,
+ setRoute,
+ hostAddress,
+ isThemeDark,
+ setIsThemeDark,
+ isWinFullscreen,
+ isWinFocused,
+ isWinMaximized,
+ isLeftSidebarOpen,
+ setIsLeftSidebarOpen,
+ isRightSidebarOpen,
+ setIsRightSidebarOpen,
+ setIsSettingsOpen,
+ isCommandBarOpen,
+ setIsCommandBarOpen,
+ isMergeDialogOpen,
+ setIsMergeDialogOpen,
+ isDatabaseDialogOpen,
+ } = useAppState();
+
+ return (
+
+
+
+ setActiveDatabase(database)}
+ handlePressAddDatabase={() => console.log('pressed add database')}
+ handlePressRemoveDatabase={() => console.log('pressed remove database')}
+ handlePressImportDatabase={() => console.log('pressed import database')}
+ handlePressMoveDatabase={() => console.log('pressed move database')}
+ handlePressMember={(person) => console.log(person)}
+ handlePressCommandBar={() => setIsCommandBarOpen(!isCommandBarOpen)}
+ handlePressDailyNotes={() => setRoute('/daily-notes')}
+ handlePressAllPages={() => setRoute('/all-pages')}
+ handlePressGraph={() => setRoute('/graph')}
+ handlePressThemeToggle={() => setIsThemeDark(!isThemeDark)}
+ handlePressMerge={() => setIsMergeDialogOpen(true)}
+ handlePressSettings={() => setIsSettingsOpen(true)}
+ handlePressHistoryBack={() => console.log('pressed go back')}
+ handlePressHistoryForward={() => console.log('pressed go forward')}
+ handlePressLeftSidebarToggle={() => setIsLeftSidebarOpen(!isLeftSidebarOpen)}
+ handlePressRightSidebarToggle={() => setIsRightSidebarOpen(!isRightSidebarOpen)}
+ handlePressMinimize={() => console.log('pressed minimize')}
+ handlePressMaximizeRestore={() => console.log('pressed maximize/restore')}
+ handlePressClose={() => console.log('pressed close')}
+ handlePressHostAddress={(hostAddress) => console.log('pressed', hostAddress)}
+ handleUpdateProfile={(person) => setCurrentUser(person)}
+ />
+ null}
+ shortcuts={[{
+ uid: "4b89dde0-3ccf-481a-875b-d11adfda3f7e",
+ title: "Passer domesticus",
+ order: 1
+ }, {
+ uid: "bd4a892f-c7e5-45d8-bab8-68a8ed9d224f",
+ title: "Spermophilus richardsonii",
+ order: 2
+ }, {
+ uid: "b60fc12e-bf48-415c-a059-a7a4d5ef686e",
+ title: "Leprocaulinus vipera",
+ order: 3
+ }, {
+ uid: "c58d62e5-0e1b-4f30-a156-af8467317c1c",
+ title: "Rangifer tarandus",
+ order: 4
+ }, {
+ uid: "dd099e5d-1f6d-4be7-8bf0-9fc0310ba489",
+ title: "Nycticorax nycticorax",
+ order: 5
+ }]}
+ version="1.0.0"
+ />
+
+
+
+
+
+
+ {/* */}
+ {isCommandBarOpen && ( setIsCommandBarOpen(false)}
+ />)
+ }
+
+ )
+};
+
+export const Browser = Template.bind({});
+Browser.args = {
+ os: () => getOs(window)
+};
diff --git a/src/js/components/Button/Button.stories.tsx b/src/js/components/Button/Button.stories.tsx
new file mode 100644
index 0000000000..45607a572c
--- /dev/null
+++ b/src/js/components/Button/Button.stories.tsx
@@ -0,0 +1,119 @@
+import styled from 'styled-components';
+
+import { Button } from '@/Button';
+import { BADGE, Storybook } from '@/utils/storybook';
+import { CheckCircledOutline } from 'iconoir-react';
+
+const Wrapper = styled.div`
+ display: flex;
+ gap: 1rem;
+`;
+
+export default {
+ title: 'Components/Button',
+ component: Button,
+ argTypes: {},
+ parameters: {
+ layout: 'centered',
+ badges: [BADGE.BETA, BADGE.IN_USE]
+ },
+ decorators: [(Story) => ]
+};
+
+const Template = (args) => ;
+
+export const Default = Template.bind({});
+Default.args = {
+ children: 'Button',
+};
+
+
+export const isPrimary = Template.bind({});
+isPrimary.args = {
+ isPrimary: true,
+ className: 'is-cool',
+ children: 'Button',
+};
+
+
+export const Pressed = Template.bind({});
+Pressed.args = {
+ isPressed: true,
+ children: 'Button',
+};
+
+const ThemeWrapper = styled.div`
+ padding: 1rem;
+ border-radius: 1rem;
+ max-width: max-content;
+ margin: auto;
+ border: 1px solid var(--border-color);
+ margin: 0.5rem auto;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+`;
+
+export const Variants = () => <>
+
+
+ Plain
+ Gray
+ Tinted
+ Filled
+ Icon
+ Plain
+ Gray
+ Tinted
+ Filled
+ Icon
+
+
+ Plain
+ Gray
+ Tinted
+ Filled
+ Icon
+ Plain
+ Gray
+ Tinted
+ Filled
+ Icon
+
+
+
+
+ Plain
+ Gray
+ Tinted
+ Filled
+ Icon
+ Plain
+ Gray
+ Tinted
+ Filled
+ Icon
+
+
+ Plain
+ Gray
+ Tinted
+ Filled
+ Icon
+ Plain
+ Gray
+ Tinted
+ Filled
+ Icon
+
+ >;
+
+
+export const Icon = Template.bind({});
+Icon.args = {
+ children:
+
+
+
+ ,
+};
diff --git a/src/js/components/Button/Button.tsx b/src/js/components/Button/Button.tsx
new file mode 100644
index 0000000000..8fb7d13256
--- /dev/null
+++ b/src/js/components/Button/Button.tsx
@@ -0,0 +1,196 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import styled from 'styled-components';
+import { classnames } from '@/utils/classnames';
+import { useFocusRing } from '@react-aria/focus'
+import { useFocusRingEl } from '@/utils/useFocusRingEl';
+import { mergeProps } from '@react-aria/utils';
+import { DOMRoot } from '@/utils/config';
+
+export interface ButtonProps extends React.ButtonHTMLAttributes {
+ /**
+ * Whether this button should have a stronger style
+ */
+ isPrimary?: boolean;
+ /**
+ * Whether the button should appear pressed
+ */
+ isPressed?: boolean;
+ /**
+ * Button shape. Set to 'unset' to manually style padding and radius.
+ */
+ shape?: 'rect' | 'round' | 'unset';
+ /**
+ * Button shape style. Set to 'unset' to manually style color and interaction styles.
+ */
+ variant?: 'plain' | 'gray' | 'tinted' | 'filled' | 'unset';
+ /**
+ * Styles provided to the button's focus ring.
+ */
+ focusRingStyle?: React.CSSProperties;
+}
+
+
+/**
+ * Primary UI component for user interaction
+ */
+export const ButtonWrap = styled.button.attrs(props => {
+ if (props.isPrimary) props.variant = 'tinted';
+ return ({
+ "aria-pressed": props.isPressed ? 'true' : undefined,
+ className: classnames(
+ 'button',
+ props.className,
+ 'shape-' + props.shape,
+ 'variant-' + props.variant)
+ })
+}) `
+ margin: 0;
+ font-family: inherit;
+ font-size: inherit;
+ font-weight: 500;
+ border: none;
+ display: inline-flex;
+ place-items: center;
+ place-content: center;
+ color: var(--body-text-color);
+ background-color: transparent;
+ transition-property: background, color;
+ transition-duration: 0.075s;
+ transition-timing-function: ease;
+ gap: 1ch;
+ text-align: left;
+
+ &:focus {
+ outline: none;
+ }
+
+ &:enabled {
+ cursor: pointer;
+ }
+
+ span {
+ flex: 1 0 auto;
+ }
+
+ /* Shapes */
+ &.shape-rect {
+ --padding-v: 0.375rem;
+ --padding-h: 0.625rem;
+ border-radius: 0.25rem;
+ padding: var(--padding-v) var(--padding-h);
+ }
+
+ &.shape-round {
+ --padding-v: 0.375rem;
+ --padding-h: 0.625rem;
+ border-radius: 2rem;
+ padding: var(--padding-v) var(--padding-h);
+ }
+
+ :where(:not(& * svg), :not(.unset-margin)) svg {
+ --icon-padding: 0.25rem;
+ margin: calc((var(--padding-v) * -1) + var(--icon-padding)) calc((var(--padding-h) * -1) + var(--icon-padding));
+
+ &:not(:first-child) {
+ margin-left: 0.251em;
+ }
+ &:not(:last-child) {
+ margin-right: 0.251em;
+ }
+ }
+
+ /* Variants */
+ &.variant-plain {
+ background: transparent;
+
+ &:hover {
+ background: var(--body-text-color---opacity-05);
+ }
+
+ &[aria-pressed="true"],
+ &:active {
+ background: var(--body-text-color---opacity-10);
+ }
+ }
+
+ &.variant-gray {
+ color: var(--link-color);
+ background: var(--body-text-color---opacity-10);
+
+ &:hover {
+ background: var(--body-text-color---opacity-15);
+ }
+
+ &[aria-pressed="true"],
+ &:active {
+ background: var(--body-text-color---opacity-20);
+ }
+ }
+
+ &.variant-tinted {
+ color: var(--link-color);
+ background: var(--link-color---opacity-15);
+
+ &:hover {
+ background: var(--link-color---opacity-20);
+ }
+
+ &[aria-pressed="true"],
+ &:active {
+ background: var(--link-color---opacity-25);
+ }
+ }
+
+ &.variant-filled {
+ color: var(--link-color---contrast);
+ background: var(--link-color);
+
+ &:hover {
+ background: var(--link-color---opacity-90);
+ }
+
+ &[aria-pressed="true"],
+ &:active {
+ background: var(--link-color---opacity-80);
+ }
+ }
+
+ &:disabled {
+ &,
+ &:hover,
+ &:active {
+ color: var(--body-text-color---opacity-med);
+ background: var(---body-text-color---opacity-10);
+ cursor: not-allowed;
+ }
+ }
+`;
+
+interface Result extends React.ForwardRefExoticComponent {
+ Wrap?: typeof ButtonWrap;
+}
+
+const _Button: Result = React.forwardRef((props: ButtonProps, ref): any => {
+ ref = ref || React.useRef();
+ let { FocusRing, focusProps } = useFocusRingEl(ref);
+
+ return <>
+
+ {props.children}
+
+ {FocusRing}
+ >;
+});
+
+_Button.defaultProps = {
+ shape: 'rect',
+ variant: 'plain',
+}
+
+export { _Button as Button };
diff --git a/src/js/components/Button/index.ts b/src/js/components/Button/index.ts
new file mode 100644
index 0000000000..3b186e1359
--- /dev/null
+++ b/src/js/components/Button/index.ts
@@ -0,0 +1,2 @@
+import { Button, ButtonWrap } from './Button';
+export { Button, ButtonWrap };
\ No newline at end of file
diff --git a/src/js/components/Confirmation/Confirmation.tsx b/src/js/components/Confirmation/Confirmation.tsx
deleted file mode 100644
index 8b67b7e64d..0000000000
--- a/src/js/components/Confirmation/Confirmation.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { Modal, ModalOverlay, ModalContent, ModalBody, ModalHeader, ModalFooter, Button, ButtonGroup } from '@chakra-ui/react';
-
-export const Confirmation = ({
- isOpen,
- onClose,
- onConfirm,
- title,
- description
-}) => {
- return (
-
-
-
- {title}
- {description && {description} }
-
-
-
- Confirm
-
-
- Cancel
-
-
-
-
-
- );
-};
\ No newline at end of file
diff --git a/src/js/components/Design.stories.tsx b/src/js/components/Design.stories.tsx
new file mode 100644
index 0000000000..be3da01dab
--- /dev/null
+++ b/src/js/components/Design.stories.tsx
@@ -0,0 +1,160 @@
+import styled, { css } from 'styled-components';
+import { readableColor } from 'polished';
+import { permuteColorOpacities, themeLight, themeDark } from '@/utils/style/style'
+
+export default {
+ title: 'Design',
+ argTypes: {},
+ parameters: {
+ layout: 'fullscreen'
+ }
+};
+
+const Stack = styled.div`
+ width: 40em;
+ margin: 4em auto;
+
+ h2 {
+ margin: 0;
+ text-align: center;
+ }
+`;
+
+const Title = styled.div`
+ font-weight: bold;
+ font-size: var(--font-size--text-xl);
+`;
+
+const Description = styled.div``
+
+const Wrapper = styled.div`
+ padding: 2rem;
+ display: flex;
+ gap: 1rem;
+ flex-direction: column;
+
+ p {
+ margin: 0;
+ }
+
+ code {
+ color: var(--link-color);
+ padding: 0.25rem 0.5rem;
+ background: var(--background-minus-2);
+ border-radius: 0.25rem;
+ font-size: 0.75rem;
+ font-family: var(--font-family-code);
+ user-select: all;
+ }
+`;
+
+const ColorInstance = styled.div`
+ width: 3rem;
+ height: 3rem;
+ flex: 0 0 3rem;
+ color: var(--background);
+ position: relative;
+ border-radius: 100em;
+ transition: all 0.12s ease-in-out;
+
+ &:after,
+ &:before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ border-radius: inherit;
+ background: var(--background);
+ opacity: var(--opacity);
+ transition: all 0.12s ease-in-out;
+ }
+
+ &:before {
+ opacity: 0;
+ background: var(--contrast-background, var(--background-color));
+ inset: 0.25rem;
+ filter: blur(0.25rem);
+ transition: all 0.12s ease-in-out;
+ }
+
+ &:hover {
+ transform: scale(1.1);
+ z-index: 10;
+ box-shadow: var(--depth-shadow-8);
+
+ &:before {
+ opacity: 0.7;
+ }
+ }
+`;
+
+const ColorStack = styled.div`
+ display: flex;
+
+ ${props => props.hasContrast && css`
+ padding: 0.75rem 1.5rem 0.75rem 1rem;
+ border-radius: 100em;
+ background: var(--body-text-color);
+ --contrast-background: var(--body-text-color);
+ width: max-content;
+ `}
+
+ > * {
+ margin-inline-end: -0.5rem;
+ }
+`;
+
+const DepthStack = styled.div`
+ display: flex;
+ gap: 1rem;
+`;
+
+const ColorDemo = ({ name, color, description, hasContrast = false }) =>
+
+ {name}
+ {description}
+ {color}
+
+
+
+
+
+
+
+
+
+
+
+
+export const Design = () => <>
+
+ Intent colors
+
+
+
+
+ Interface colors
+
+
+
+
+
+
+
+
+
+ Depth
+
+
+ Depth Shadows
+ Use shadows sparingly. Shadows may also be paired with a 1px shadow on the same element to better cut it out of its context.
+
+
+
+
+
+
+
+
+
+
+>
\ No newline at end of file
diff --git a/src/js/components/Dialog/Dialog.stories.tsx b/src/js/components/Dialog/Dialog.stories.tsx
new file mode 100644
index 0000000000..ea2dd0d12d
--- /dev/null
+++ b/src/js/components/Dialog/Dialog.stories.tsx
@@ -0,0 +1,36 @@
+import { Dialog } from './Dialog';
+import { BADGE, Storybook } from '@/utils/storybook';
+
+export default {
+ title: 'Components/Dialog',
+ component: Dialog,
+ argTypes: {},
+ parameters: {
+ layout: 'centered',
+ badges: [BADGE.DEV, BADGE.IN_USE]
+ },
+ decorators: [(Story) => ]
+};
+
+const Template = (args) => ;
+
+export const Default = Template.bind({});
+Default.args = {
+ title: 'Lorem ipsum dolor sit amet.',
+ children: 'Lorem ipsum dolor sit amet',
+ isOpen: true,
+};
+
+export const Image = Template.bind({});
+Image.args = {
+ image: ,
+ title: 'Lorem ipsum dolor sit amet.',
+ children: 'Lorem ipsum dolor sit amet',
+ isOpen: true,
+};
+
+export const Minimal = Template.bind({});
+Minimal.args = {
+ title: 'Lorem ipsum dolor sit amet.',
+ isOpen: true,
+};
diff --git a/src/js/components/Dialog/Dialog.tsx b/src/js/components/Dialog/Dialog.tsx
new file mode 100644
index 0000000000..f6b3f9ae53
--- /dev/null
+++ b/src/js/components/Dialog/Dialog.tsx
@@ -0,0 +1,189 @@
+import React from 'react';
+import styled from 'styled-components';
+
+import {
+ useOverlay,
+ usePreventScroll,
+ useModal,
+ OverlayProps,
+ OverlayContainer
+} from '@react-aria/overlays';
+import { useDialog } from '@react-aria/dialog';
+import { AriaDialogProps } from '@react-types/dialog';
+import { FocusScope } from '@react-aria/focus';
+import { mergeProps } from '@react-aria/utils';
+
+import { Button } from '@/Button';
+import { Overlay } from '@/Overlay';
+
+const Container = styled(Overlay)`
+ display: flex;
+ flex-direction: row;
+ gap: 1rem;
+ width: max-content;
+ padding: 1rem;
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ min-width: 20rem;
+`;
+
+const Image = styled.div``;
+
+const Title = styled.h1`
+ font-size: 1em;
+ margin: 0;
+`;
+
+const Message = styled.p`
+ margin: 0;
+`;
+
+const Body = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 0.125rem;
+ min-width: 15rem;
+ flex: 1 1 100%;
+`;
+
+const Actions = styled.div`
+ display: grid;
+ grid-auto-flow: column;
+ grid-auto-columns: 1fr;
+ gap: 0.25rem;
+ width: max-content;
+ margin-top: auto;
+ margin-left: auto;
+ padding-top: 1rem;
+ align-self: flex-end;
+`;
+
+const Backdrop = styled.div`
+ position: fixed;
+ z-index: var(--zindex-modal);
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+`;
+
+const DismissButton = styled(Button)`
+ font-weight: normal;
+`;
+const ConfirmButton = styled(Button)`
+ font-weight: normal;
+`;
+
+interface DialogProps extends OverlayProps, AriaDialogProps {
+ isOpen: boolean,
+ title: string,
+ children?: React.ReactNode,
+ image?: JSX.Element;
+ defaultAction?: 'confirm' | 'dismiss';
+ dismiss?: {
+ label?: string;
+ variant?: 'filled' | 'tinted' | 'gray' | 'plain';
+ },
+ confirm?: {
+ label?: string;
+ variant?: 'filled' | 'tinted' | 'gray' | 'plain';
+ }
+ // onClose?: () => void;
+ onConfirm?: () => void;
+}
+
+export const Dialog = (props: DialogProps): JSX.Element | null => {
+ const {
+ isOpen,
+ title,
+ children,
+ image,
+ onConfirm: handleConfirm,
+ onClose: handleClose,
+ defaultAction,
+ dismiss,
+ confirm
+ } = props;
+
+ let ref = React.useRef();
+ let { overlayProps, underlayProps } = useOverlay(props, ref);
+ let { modalProps } = useModal();
+ let { dialogProps, titleProps } = useDialog(props, ref);
+ usePreventScroll();
+
+ const dismissProps = {
+ ...mergeProps({
+ onClick: handleClose,
+ label: 'Cancel',
+ variant: 'plain',
+ autoFocus: defaultAction === 'dismiss',
+ ...dismiss,
+ })
+ }
+
+ const confirmProps = {
+ ...mergeProps({
+ onClick: handleConfirm,
+ label: 'Cancel',
+ variant: 'filled',
+ autoFocus: defaultAction === 'confirm',
+ ...confirm,
+ })
+ }
+
+ return (
+ isOpen ?
+
+
+
+
+ {children
+ ? children
+ : (
+ <>
+ {image && {image} }
+ (
+ {title}
+ {children}
+
+ Cancel
+ Confirm
+
+ )
+ >
+ )}
+
+
+
+
+ : null
+ );
+};
+
+Dialog.defaultProps = {
+ defaultAction: 'confirm',
+}
+
+Dialog.Container = Container;
+Dialog.Image = Image;
+Dialog.Title = Title;
+Dialog.Message = Message;
+Dialog.Body = Body;
+Dialog.Actions = Actions;
+Dialog.DismissButton = DismissButton;
+Dialog.ConfirmButton = ConfirmButton;
diff --git a/src/js/components/Dialog/index.ts b/src/js/components/Dialog/index.ts
new file mode 100644
index 0000000000..96d19d0055
--- /dev/null
+++ b/src/js/components/Dialog/index.ts
@@ -0,0 +1,2 @@
+import { Dialog } from './Dialog';
+export { Dialog };
\ No newline at end of file
diff --git a/src/js/components/Icons/ConnectedGraphConnection.tsx b/src/js/components/Icons/ConnectedGraphConnection.tsx
new file mode 100644
index 0000000000..7fc4f5899b
--- /dev/null
+++ b/src/js/components/Icons/ConnectedGraphConnection.tsx
@@ -0,0 +1,7 @@
+export const ConnectedGraphConnection = () => {
+ return (
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/js/components/Icons/ConnectedGraphHost.tsx b/src/js/components/Icons/ConnectedGraphHost.tsx
new file mode 100644
index 0000000000..995738cc0e
--- /dev/null
+++ b/src/js/components/Icons/ConnectedGraphHost.tsx
@@ -0,0 +1,10 @@
+export const ConnectedGraphHost = () => {
+ return (
+ <>
+
+
+
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/src/js/components/Icons/Icon.tsx b/src/js/components/Icons/Icon.tsx
new file mode 100644
index 0000000000..4cf4054d4c
--- /dev/null
+++ b/src/js/components/Icons/Icon.tsx
@@ -0,0 +1,31 @@
+import React from "react";
+import styled from "styled-components";
+
+export const Icon = React.memo(styled.svg.attrs({
+ viewBox: "0 0 24 24",
+})`
+ width: var(--size, 2em);
+ height: var(--size, 2em);
+
+ &,
+ * {
+ vector-effect: non-scaling-stroke;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ }
+
+ .fill {
+ fill: var(--fill, currentColor);
+ stroke: none;
+ }
+
+ .stroke {
+ stroke: var(--stroke, currentColor);
+ stroke-width: var(--stroke-width, 1.5);
+ fill: none;
+ }
+
+ .fill.stroke {
+ fill: var(--fill, currentColor);
+ }
+`);
diff --git a/src/js/components/Icons/Icons.tsx b/src/js/components/Icons/Icons.tsx
deleted file mode 100644
index a11e314272..0000000000
--- a/src/js/components/Icons/Icons.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-import { createIcon } from '@chakra-ui/react'
-
-
-export const RightSidebarIcon = createIcon({
- displayName: 'RightSidebarIcon',
- viewBox: '0 0 24 24',
- path: (
-
- ),
-})
-
-export const AllPagesIcon = createIcon({
- displayName: 'AllPagesIcon',
- viewBox: '0 0 24 24',
- path: (
-
-
- ),
-})
-
-export const XmarkIcon = createIcon({
- displayName: 'XmarkIcon',
- viewBox: '0 0 24 24',
- path: (
-
- ),
-})
-
-export const DailyNotesIcon = createIcon({
- displayName: 'DailyNotesIcon',
- viewBox: '0 0 24 24',
- path: (
-
- ),
-})
-
-export const SearchIcon = createIcon({
- displayName: 'SearchIcon',
- viewBox: '0 0 24 24',
- path: (
-
- ),
-})
-
-export const SettingsIcon = createIcon({
- displayName: 'SettingsIcon',
- viewBox: '0 0 24 24',
- path: (
-
- ),
-})
-
-export const RightSidebarAddIcon = createIcon({
- displayName: 'RightSidebarAddIcon',
- viewBox: '0 0 24 24',
- path: (
-
- ),
-})
-
-export const HelpIcon = createIcon({
- displayName: 'HelpIcon',
- viewBox: '0 0 24 24',
- path: (
-
- ),
-})
-
-export const MenuIcon = createIcon({
- displayName: 'MenuIcon',
- viewBox: '0 0 24 24',
- path: (
-
- ),
-})
-
-export const ChevronUpIcon = createIcon({
- displayName: 'ChevronUpIcon',
- viewBox: '0 0 24 24',
- path: (
-
- ),
-})
-
-export const ChevronLeftIcon = createIcon({
- displayName: 'ChevronLeftIcon',
- viewBox: '0 0 24 24',
- path: (
-
- ),
-})
-
-export const ChevronRightIcon = createIcon({
- displayName: 'ChevronRightIcon',
- viewBox: '0 0 24 24',
- path: (
-
- ),
-})
-
-export const ChevronDownIcon = createIcon({
- displayName: 'ChevronDownIcon',
- viewBox: '0 0 24 24',
- path: (
-
- ),
-})
-
-export const ContrastIcon = createIcon({
- displayName: 'ContrastIcon',
- viewBox: '0 0 24 24',
- path: (
-
- ),
-})
-
diff --git a/src/js/components/Icons/X.tsx b/src/js/components/Icons/X.tsx
new file mode 100644
index 0000000000..cfffd0c99c
--- /dev/null
+++ b/src/js/components/Icons/X.tsx
@@ -0,0 +1 @@
+export const X = () => ;
\ No newline at end of file
diff --git a/src/js/components/Input/Input.stories.tsx b/src/js/components/Input/Input.stories.tsx
new file mode 100644
index 0000000000..046e087e34
--- /dev/null
+++ b/src/js/components/Input/Input.stories.tsx
@@ -0,0 +1,76 @@
+import React from 'react'
+import { Input } from './Input';
+import { BADGE, Storybook } from '@/utils/storybook';
+import { Mail, Check } from '@material-ui/icons';
+import styled from 'styled-components';
+
+const InputStoryWrapper = styled(Storybook.Wrapper)`
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ gap: 1rem 2rem;
+
+ hr {
+ grid-column: 1 / -1;
+ width: 100%;
+ border: 0 0 1px;
+ opacity: var(--opacity-low);
+ }
+`;
+
+export default {
+ title: 'Components/Input',
+ component: Input,
+ argTypes: {},
+ parameters: {
+ layout: 'centered',
+ badges: [BADGE.DEV, BADGE.IN_USE]
+ },
+ decorators: [(Story) => ]
+};
+
+const Template = (args) => ;
+
+export const Default = Template.bind({});
+Default.args = {
+ defaultValue: 'Input',
+ type: 'text'
+};
+
+export const Password = () => <>
+
+
+
+
+
+
+
+>
+
+export const WithLayout = () => {
+ return <>
+
+
+
+ Label
+
+ Help text
+
+ >
+}
+
+export const WithValidation = () => {
+ const [value, setValue] = React.useState(null);
+
+ const regex = /^[_a-zA-Z0-9-+_.]+@[a-zA-Z0-9-\.]+(\.[a-zA-Z]{2,3})$/;
+ const isValid = regex.exec(value);
+
+ return <>
+
+
+ {isValid && }
+ Email Address
+ setValue(e.target.value)} />
+ Provide a valid email
+
+ >
+}
\ No newline at end of file
diff --git a/src/js/components/Input/Input.tsx b/src/js/components/Input/Input.tsx
new file mode 100644
index 0000000000..96aae78e91
--- /dev/null
+++ b/src/js/components/Input/Input.tsx
@@ -0,0 +1,131 @@
+import React from 'react';
+import styled from 'styled-components';
+import { Check, Warning } from '@material-ui/icons';
+
+export interface InputProps extends React.InputHTMLAttributes { }
+
+export const Input = styled.input`
+ padding: 0.375rem 0.625rem;
+ margin: 0;
+ font-family: inherit;
+ font-size: inherit;
+ border-radius: 0.25rem;
+ font-weight: 500;
+ border: none;
+ display: inline-flex;
+ align-items: center;
+ color: var(--body-text-color);
+ caret-color: var(--link-color);
+ background: var(--body-text-color---opacity-lower);
+ transition-property: filter, background, color, opacity, border-color;
+ transition-duration: 0.1s;
+ transition-timing-function: ease-in-out;
+ gap: 0.5rem;
+
+ &:enabled {
+ &:hover {
+ background: var(--body-text-color---opacity-low);
+ }
+
+ &:active {
+ background: var(--body-text-color---opacity-low);
+ }
+ }
+
+ &:disabled {
+ color: var(--body-text-color---opacity-low);
+ background: var(--body-text-color---opacity-lower);
+ cursor: default;
+ }
+
+ &.is-invalid {
+ color: var(--warning-color);
+ background: var(--warning-color---opacity-lower);
+
+ &:hover {
+ background: var(--warning-color---opacity-low);
+ }
+
+ &:active {
+ background: var(--warning-color---opacity-low);
+ }
+ }
+
+ &.is-valid {
+ color: var(--confirmation-color);
+ background: var(--confirmation-color---opacity-lower);
+
+ &:hover {
+ background: var(--confirmation-color---opacity-low);
+ }
+
+ &:active {
+ background: var(--confirmation-color---opacity-low);
+ }
+ }
+`;
+
+Input.Label = styled.span`
+ font-weight: bold;
+`;
+
+Input.Help = styled.small``;
+
+Input.LabelWrapper = styled.label`
+ display: grid;
+ grid-template-areas: "label" "input" "help";
+
+ .label,
+ ${Input.Label} {
+ grid-area: label;
+ }
+
+ .input,
+ ${Input} {
+ grid-area: input;
+ }
+
+ .help,
+ ${Input.Help} {
+ grid-area: help;
+ }
+
+ .input-right {
+ grid-area: input;
+ margin: auto 0;
+ margin-left: auto;
+ z-index: 1;
+ }
+
+ .icon-right,
+ .icon-left {
+ grid-area: input;
+ pointer-events: none;
+ margin: auto;
+ }
+
+ .icon-right {
+ margin-right: 0.25rem;
+
+ ~ ${Input} {
+ padding-right: 2rem;
+ }
+ }
+
+ .icon-left {
+ margin-left: 0.25rem;
+
+ ~ ${Input} {
+ padding-left: 2rem;
+ }
+ }
+`;
+
+Input.Invalid = styled(Warning).attrs({
+ className: 'icon-right',
+})`
+`;
+
+Input.Valid = styled(Check).attrs({
+ className: 'icon-right',
+})``;
diff --git a/src/js/components/Input/index.ts b/src/js/components/Input/index.ts
new file mode 100644
index 0000000000..f0fee8fc27
--- /dev/null
+++ b/src/js/components/Input/index.ts
@@ -0,0 +1,2 @@
+import { Input } from './Input';
+export { Input };
\ No newline at end of file
diff --git a/src/js/components/Layout/Layout.tsx b/src/js/components/Layout/Layout.tsx
deleted file mode 100644
index b12d3ef3a5..0000000000
--- a/src/js/components/Layout/Layout.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-import { Button, IconButton, Box, useDisclosure, Collapse, VStack } from '@chakra-ui/react';
-import { AnimatePresence, motion } from 'framer-motion';
-import { XmarkIcon, ChevronUpIcon } from '@/Icons/Icons';
-
-const Container = motion(Box)
-
-export const RightSidebarContainer = ({ isOpen, width, isDragging, children }) => {
- return
- {isOpen &&
-
- {children}
- }
-
-}
-
-export const SidebarItem = ({ title, defaultIsOpen, onRemove, onClose, children }) => {
- const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: defaultIsOpen, onClose: onClose });
-
- return (
-
-
-
-
- {title}
-
-
-
-
-
-
- {children}
-
- )
-}
\ No newline at end of file
diff --git a/src/js/components/Link/Link.stories.tsx b/src/js/components/Link/Link.stories.tsx
new file mode 100644
index 0000000000..27d8a93f27
--- /dev/null
+++ b/src/js/components/Link/Link.stories.tsx
@@ -0,0 +1,18 @@
+import { Link } from './Link';
+import { BADGE, Storybook } from '@/utils/storybook';
+
+export default {
+ title: 'components/Link',
+ component: Link,
+ argTypes: {},
+ parameters: {
+ layout: 'centered',
+ badges: [BADGE.DEV]
+ },
+ decorators: [(Story) => ]
+};
+
+export const BidirectionalLink = () => (
+
+ Abberton Reservoir is a pumped storage freshwater reservoir in England near the Essex coast, with an area of 700 hectares (1,700 acres).
+
);
diff --git a/src/js/components/Link/Link.ts b/src/js/components/Link/Link.ts
new file mode 100644
index 0000000000..cfbca65e87
--- /dev/null
+++ b/src/js/components/Link/Link.ts
@@ -0,0 +1,37 @@
+import styled from 'styled-components';
+
+export const Link = styled.a`
+ display: inline-flex;
+ color: var(--link-color);
+ margin-inline: calc(-0.25em + 0.1ch);
+ padding-inline: calc(0.25em);
+ border-radius: 0.25em;
+ text-decoration: none;
+ cursor: pointer;
+ transition: background 0.1s ease-in-out;
+
+ &:hover {
+ opacity: var(--opacity-higher);
+ }
+
+ &:active {
+ opacity: var(--opacity-high);
+ user-select: none;
+ }
+
+ &:before,
+ &:after {
+ color: var(--link-color---opacity-low);
+ letter-spacing: -0.2ch;
+ }
+
+ &:before {
+ content: '[[';
+ margin-inline-end: 0.1ch;
+ }
+
+ &:after {
+ content: ']]';
+ margin-inline-start: 0.1ch;
+ }
+`;
\ No newline at end of file
diff --git a/src/js/components/Link/index.ts b/src/js/components/Link/index.ts
new file mode 100644
index 0000000000..d60acc35d3
--- /dev/null
+++ b/src/js/components/Link/index.ts
@@ -0,0 +1,2 @@
+import { Link } from './Link';
+export { Link };
\ No newline at end of file
diff --git a/src/js/components/Menu/Menu.stories.tsx b/src/js/components/Menu/Menu.stories.tsx
new file mode 100644
index 0000000000..adcf36871c
--- /dev/null
+++ b/src/js/components/Menu/Menu.stories.tsx
@@ -0,0 +1,54 @@
+import { BADGE, Storybook } from '@/utils/storybook';
+
+import { Link } from '@material-ui/icons'
+
+import { Menu } from './Menu';
+import { Overlay } from '@/Overlay';
+
+export default {
+ title: 'Components/Menu',
+ component: Menu,
+ argTypes: {},
+ parameters: {
+ layout: 'centered',
+ badges: [BADGE.DEV]
+ },
+ decorators: [(Story) => ]
+};
+
+const Template = (args) => ;
+
+export const Basic = Template.bind({});
+Basic.args = {
+ children: <>
+ Menu Item
+ Menu Item
+ Menu Item
+
+ Menu Item
+ >,
+};
+
+export const InAnOverlay = Template.bind({});
+InAnOverlay.args = {
+ children: <>
+ Menu Item
+ Menu Item
+ Menu Item
+
+ Menu Item
+ >,
+};
+InAnOverlay.decorators = [(Story) => ]
+
+export const Typical = Template.bind({});
+Typical.args = {
+ children: <>
+ Menu Item
+ Menu Item
+ Menu Item
+
+ Menu Item
+ >
+};
+Typical.decorators = [(Story) => ]
diff --git a/src/js/components/Menu/Menu.ts b/src/js/components/Menu/Menu.ts
new file mode 100644
index 0000000000..bc4f955993
--- /dev/null
+++ b/src/js/components/Menu/Menu.ts
@@ -0,0 +1,55 @@
+import styled from 'styled-components';
+
+import { Button } from '@/Button';
+
+/**
+ * Wraps buttons into a menu.
+ */
+export const Menu = styled.div`
+ display: flex;
+ gap: 0.125rem;
+ min-width: 9em;
+ align-items: stretch;
+ flex-direction: column;
+ &:focus {
+ outline: none;
+ }
+`;
+
+/**
+ * Divider between sections of a menu.
+ */
+Menu.Separator = styled.hr`
+ border: 0;
+ background: var(--border-color);
+ align-self: stretch;
+ justify-self: stretch;
+ height: 1px;
+ margin: 0.25rem 0;
+ flex: 0 0 auto;
+`;
+
+/**
+ * Wraps a menu item.
+ */
+Menu.Button = styled(Button).attrs({
+ shape: 'unset'
+})`
+ font-size: var(--font-size--text-sm);
+ flex: 0 0 auto;
+ justify-content: flex-start;
+ padding: 0.125rem 0.5rem;
+ border-radius: 0.25rem;
+`;
+
+/**
+ * Heading for a section of a menu.
+ */
+Menu.Heading = styled.h3`
+ margin: 0;
+ font-weight: 500;
+ flex: 1 1 100%;
+ padding: 0.25rem 0.5rem;
+ font-size: var(--font-size--text-sm);
+ color: var(--body-text-color---opacity-med);
+`;
\ No newline at end of file
diff --git a/src/js/components/Menu/hooks/useMenu.ts b/src/js/components/Menu/hooks/useMenu.ts
new file mode 100644
index 0000000000..5fcc28287c
--- /dev/null
+++ b/src/js/components/Menu/hooks/useMenu.ts
@@ -0,0 +1,79 @@
+import React from 'react';
+
+const contextMenuAnchor = (e) => ({
+ clientHeight: 0,
+ clientWidth: 0,
+ getBoundingClientRect: () => ({
+ width: 0,
+ height: 0,
+ top: e.clientY,
+ right: e.clientX,
+ bottom: e.clientY,
+ left: e.clientX,
+ })
+});
+
+type TriggerType = 'contextMenu' | 'click' | 'hover';
+
+export const useMenu = () => {
+ const [isOpen, setIsOpen] = React.useState(false);
+ const [position, setPosition] = React.useState(null);
+ const [anchorEl, setAnchorEl] = React.useState(null);
+ const [placement, setPlacement] = React.useState('bottom-start');
+ const [triggerType, setTriggerType] = React.useState(null);
+
+ const closeMenu = () => {
+ setIsOpen(false);
+ setPosition(null);
+ setTriggerType(null);
+ };
+
+ const triggerProps = (type: TriggerType, placement?) => {
+ if (type === 'contextMenu') {
+ return ({
+ isPressed: triggerType === 'contextMenu',
+ onContextMenu: (e) => {
+ setAnchorEl(contextMenuAnchor(e));
+ e.preventDefault();
+ e.stopPropagation();
+ setIsOpen(true);
+ setPlacement(placement || 'bottom-start');
+ setTriggerType('contextMenu');
+ }
+ });
+ } else if (type === 'click') {
+ return ({
+ isPressed: triggerType === 'click',
+ onClick: (e) => {
+ setAnchorEl(e.currentTarget);
+ setIsOpen(true);
+ setPlacement(placement || 'bottom-end');
+ setTriggerType('click');
+ }
+ });
+ } else if (type === 'hover') {
+ return ({
+ isPressed: triggerType === 'hover',
+ onMouseEnter: (e) => {
+ setAnchorEl(e.currentTarget);
+ setIsOpen(true);
+ setPlacement(placement || 'bottom-end');
+ setTriggerType('hover');
+ }
+ });
+ }
+ };
+
+ const menuProps = {
+ position,
+ anchorEl,
+ isOpen,
+ placement
+ };
+
+ return {
+ triggerProps,
+ menuProps,
+ closeMenu
+ };
+};
diff --git a/src/js/components/Menu/index.ts b/src/js/components/Menu/index.ts
new file mode 100644
index 0000000000..52361e565f
--- /dev/null
+++ b/src/js/components/Menu/index.ts
@@ -0,0 +1,2 @@
+import { Menu } from './Menu';
+export { Menu };
\ No newline at end of file
diff --git a/src/js/components/Notifications/Notification.stories.tsx b/src/js/components/Notifications/Notification.stories.tsx
new file mode 100644
index 0000000000..d977daaca4
--- /dev/null
+++ b/src/js/components/Notifications/Notification.stories.tsx
@@ -0,0 +1,99 @@
+import { Storybook } from "@/utils/storybook";
+
+import { notify, Notification } from "@/Notifications/Notifications";
+import { Button } from "@/Button";
+import { Indeterminate } from "@/Spinner/components/Indeterminate";
+
+export default {
+ title: "Components/Notification",
+ component: Notification,
+ argTypes: {},
+ parameters: {
+ layout: "centered",
+ decorators: [
+ (Story, args) => (
+
+
+
+ ),
+ ],
+ },
+};
+
+export const Active = () => {
+ return (
+
+
+ notify("You did something you might not have meant to", {
+ id: "undoable-0",
+ onUndo: () => console.log('undid action')
+ } as Notification)
+ }
+ >
+ Undoable Action
+
+
+ notify.success("Ping", {
+ id: "undoable-1",
+ duration: Infinity,
+ isDismissable: true,
+ undoMessage: 'Undid the thing',
+ onUndo: () => console.log('undid action')
+ } as Notification)
+ }
+ >
+ Undoable Success Action
+
+
+ notify.loading(
+ <>
+ Loading...
+ >,
+ {
+ id: "loading",
+ position: "bottom-center",
+ } as Notification
+ )
+ }
+ >
+ Loading
+
+
+ notify.error("Error! Something went wrong", { isDismissable: true } as Notification)
+ }
+ >
+ Dismissable Error
+
+
+ notify.error("Error! Something went wrong", {
+ duration: Infinity,
+ isDismissable: true,
+ position: "top-left",
+ } as Notification)
+ }
+ >
+ Custom Position
+
+ notify("Nothing happened")}
+ >
+ Plain
+
+
+ notify.dismiss()}>
+ Clear all
+
+
+ );
+};
diff --git a/src/js/components/Notifications/Notifications.tsx b/src/js/components/Notifications/Notifications.tsx
new file mode 100644
index 0000000000..6fed3649ca
--- /dev/null
+++ b/src/js/components/Notifications/Notifications.tsx
@@ -0,0 +1,18 @@
+import toast from "react-hot-toast";
+
+import { NotificationContainer } from './components/NotificationContainer';
+import { NotificationItem } from './components/NotificationItem';
+
+const notify = toast;
+
+// TODO: Properly extend Toast type with new options:
+// id: string;
+// isDismissable?: boolean;
+// onUndo?: () => void;
+// icon?: never;
+// iconTheme?: never;
+// undoMessage?: string;
+
+export type Notification = any;
+
+export { notify, NotificationContainer, NotificationItem };
diff --git a/src/js/components/Notifications/components/NotificationContainer.tsx b/src/js/components/Notifications/components/NotificationContainer.tsx
new file mode 100644
index 0000000000..993421b141
--- /dev/null
+++ b/src/js/components/Notifications/components/NotificationContainer.tsx
@@ -0,0 +1,22 @@
+import {
+ Toaster,
+ ToastPosition,
+ resolveValue,
+} from "react-hot-toast";
+import { NotificationItem } from '../components/NotificationItem';
+
+const ToasterProps = {
+ position: "bottom-right" as ToastPosition,
+ containerStyle: {
+ filter: "drop-shadow(0 0.5rem 0.5rem var(--shadow-color---opacity-25)"
+ },
+ gutter: 8, // 1rem
+};
+
+export const NotificationContainer = () => {
+ return (
+
+ {(t) => {resolveValue(t.message, t)} }
+
+ );
+};
diff --git a/src/js/components/Notifications/components/NotificationItem.tsx b/src/js/components/Notifications/components/NotificationItem.tsx
new file mode 100644
index 0000000000..851c054fa3
--- /dev/null
+++ b/src/js/components/Notifications/components/NotificationItem.tsx
@@ -0,0 +1,142 @@
+import React from "react";
+import styled, { keyframes } from "styled-components";
+import { mergeProps } from "@react-aria/utils";
+
+import { notify, Notification } from '../Notifications';
+
+import { Button } from "@/Button";
+import { Icon } from "@/Icons/Icon";
+import { X } from "@/Icons/X";
+
+const appear = keyframes`
+ from {
+ opacity: 0;
+ transform: scale(0.95);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+`;
+
+const Wrap = styled.div`
+ box-shadow: 0 0 0 1px var(--shadow-color---opacity-10);
+ background: var(--background-plus-2---opacity-med);
+ color: var(---body-text-color---opacity-80);
+ padding: 0.5rem 1rem;
+ border-radius: 1rem;
+ opacity: 0;
+ transition: all 0.25s ease-out;
+ animation: ${appear} 0.25s ease-out;
+ z-index: 1;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ position: relative;
+
+ button:not(& * button):last-child {
+ margin-right: -0.5rem;
+ margin-left: auto;
+ }
+
+ svg:not(& * svg):first-child {
+ margin-left: -0.5rem;
+ }
+
+ &:before {
+ content: "";
+ position: absolute;
+ z-index: -1;
+ inset: 0;
+ border-radius: inherit;
+ background: var(--background-color—-opacity-med);
+
+ @supports (backdrop-filter: blur(10px)) {
+ background: var(--background-color—-opacity-low);
+ backdrop-filter: blur(10px);
+ }
+ }
+
+ &.visible {
+ opacity: 1;
+ }
+
+ &.toast-loading {
+ padding: 0.75rem 1.5rem;
+ }
+
+ &.toast-success {
+ background: var(--confirmation-color---opacity-10);
+ color: var(--confirmation-color);
+ }
+
+ &.toast-warning {
+ background: var(--warning-color---opacity-15);
+ color: var(--warning-color);
+ }
+
+ &.toast-error {
+ background: var(--error-color---opacity-15);
+ color: var(--error-color);
+ }
+`;
+
+export const NotificationItem = (t: Notification) => {
+ const { isDismissable, onUndo, undoMessage, ...rest } = t;
+ const ref = React.useRef(null);
+ const [size, setSize] = React.useState({ width: undefined, height: undefined });
+
+ const setMinSize = () => {
+ if (ref.current) {
+ setSize({ width: ref.current.offsetWidth, height: ref.current.offsetHeight });
+ }
+ };
+
+ const handleUndo = () => {
+ setMinSize();
+ const message = t.undoMessage || "Undone";
+ const resultProps = { id: rest.id, onUndo: undefined, isDismissable: true };
+ const updateNotification = t.type !== 'blank'
+ ? notify[t.type || 'blank'](message, resultProps)
+ : notify(message, resultProps);
+
+ onUndo();
+ updateNotification()
+ }
+
+ const handleDismiss = () => notify.dismiss(t.id);
+
+ return (
+
+ {t.children}
+ {t.onUndo && (
+
+ Undo
+
+ )}
+ {t.isDismissable && (
+
+
+
+
+
+ )}
+
+ );
+};
\ No newline at end of file
diff --git a/src/js/components/Overlay/Backdrop.ts b/src/js/components/Overlay/Backdrop.ts
new file mode 100644
index 0000000000..5db1ea1c30
--- /dev/null
+++ b/src/js/components/Overlay/Backdrop.ts
@@ -0,0 +1,14 @@
+import styled from 'styled-components';
+
+export const Backdrop = styled.div`
+ position: fixed;
+ z-index: var(--zindex-modal);
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ background-color: ${props => props.hidden ? 'none' : 'rgba(0, 0, 0, 0.5)'};
+ display: flex;
+ align-items: center;
+ justify-content: center;
+`;
\ No newline at end of file
diff --git a/src/js/components/Overlay/Overlay.stories.tsx b/src/js/components/Overlay/Overlay.stories.tsx
new file mode 100644
index 0000000000..109df2da99
--- /dev/null
+++ b/src/js/components/Overlay/Overlay.stories.tsx
@@ -0,0 +1,63 @@
+import styled from 'styled-components';
+import { Create } from '@material-ui/icons';
+import { BADGE, Storybook } from '@/utils/storybook';
+
+import { Overlay } from './Overlay';
+import { Menu } from '@/Menu';
+import { Button } from '@/Button';
+
+export default {
+ title: 'Components/Overlay',
+ component: Overlay,
+ argTypes: {},
+ parameters: {
+ layout: 'centered',
+ badges: [BADGE.DEV, BADGE.IN_USE]
+ }
+};
+
+const MessageContent = styled.div`
+ display: flex;
+ place-items: center;
+ place-content: center;
+ flex-direction: column;
+ gap: 1rem;
+ padding: 2rem 1rem;
+
+ svg {
+ font-size: 4rem;
+ margin: auto;
+ opacity: var(--opacity-high);
+ color: var(--link-color);
+ }
+
+ span {
+ color: var(--body-text-color---opacity-med);
+ text-align: center;
+ display: block;
+ }
+`;
+
+const Template = (args) => ;
+
+export const Basic = Template.bind({});
+Basic.args = {
+ children: 'Overlay',
+};
+
+export const Message = Template.bind({});
+Message.args = {
+ children: (
+
+ Write something insightful today
+ ),
+};
+
+export const AroundAMenu = Template.bind({});
+AroundAMenu.args = {
+ children:
+ Menu Item 1
+ Menu Item 2
+ Menu Item 3
+ ,
+};
diff --git a/src/js/components/Overlay/Overlay.ts b/src/js/components/Overlay/Overlay.ts
new file mode 100644
index 0000000000..48ecb32103
--- /dev/null
+++ b/src/js/components/Overlay/Overlay.ts
@@ -0,0 +1,56 @@
+import styled, { css, keyframes } from 'styled-components';
+
+const overlayAppear = keyframes`
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ } to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+`;
+
+/**
+ * A simple container with basic padding, background, shadow, etc.
+ */
+export const Overlay = styled.div`
+ display: inline-flex;
+ color: var(--body-text-color);
+ padding: 0.25rem;
+ min-width: 2em;
+ border-radius: calc(0.25rem + 0.25rem); // Button corner radius + container padding makes "concentric" container radius;
+ z-index: var(--zindex-dropdown);
+ min-height: 2em;
+ animation-fill-mode: both;
+ box-shadow: var(--depth-shadow-16), 0 0 0 1px rgb(0 0 0 / 0.05);
+ background: var(--background-plus-1);
+ position: relative;
+
+ &:focus-visible,
+ &:focus {
+ box-shadow: var(--depth-shadow-16), 0 0 0 2px rgb(0 0 0 / 0.1);
+ outline: none;
+ }
+
+ &.animate-in {
+ animation: ${overlayAppear} 0.125s;
+ }
+
+ ${props => !!props.hasOutline && css`
+ .is-theme-dark & {
+ &:after {
+ content: '';
+ inset: 0;
+ position: absolute;
+ box-shadow: inset 0 0 0 1px var(--body-text-color---opacity-lower);
+ z-index: 99999;
+ pointer-events: none;
+ border-radius: inherit;
+ }
+ }
+ `}
+`;
+
+Overlay.defaultProps = {
+ hasOutline: true,
+}
diff --git a/src/js/components/Overlay/index.ts b/src/js/components/Overlay/index.ts
new file mode 100644
index 0000000000..90c6828ee5
--- /dev/null
+++ b/src/js/components/Overlay/index.ts
@@ -0,0 +1,2 @@
+import { Overlay } from './Overlay';
+export { Overlay };
\ No newline at end of file
diff --git a/src/js/components/Page/Page.tsx b/src/js/components/Page/Page.tsx
deleted file mode 100644
index a09e008f7f..0000000000
--- a/src/js/components/Page/Page.tsx
+++ /dev/null
@@ -1,270 +0,0 @@
-import { Box } from '@chakra-ui/react';
-
-const PAGE_PROPS = {
- as: "article",
- display: "grid",
- flexBasis: "100%",
- gridTemplateAreas: "'header' 'content' 'footer'",
- gridTemplateRows: "auto 1fr auto",
- sx: {
- "--page-padding-v": "6rem",
- "--page-padding-h": "4rem"
- }
-}
-
-const TITLE_PROPS = {
- position: "relative",
- gridArea: "title",
- fontSize: "var(--page-title-font-size, 2rem)",
- overflow: "visible",
- flexGrow: "1",
- margin: "0",
- whiteSpace: "pre-line",
- wordBreak: "break-word",
- fontWeight: "bold",
-}
-
-export const PageContainer = ({ children, uid, type }) => {children}
-
-export const HeaderImage = ({ src }) =>
-
-export const PageHeader = ({ children, image }) =>
- {image && }
- {children}
-
-
-export const PageBody = ({ children }) => {children}
-
-export const PageFooter = ({ children }) => {children}
-
-export const TitleContainer = ({ children }) => {children}
-
-export const DailyNotesPage = ({ isReal, children }) => {children}
-
-export const EditableTitleContainer = ({ children, isEditing, props }) => a": {
- position: "relative",
- zIndex: 2,
- pointerEvents: "all",
- }
- },
- "span": {
- gridArea: "main",
- pointerEvents: "none",
- "a, button": {
- position: "relative",
- zIndex: 2,
- pointerEvents: "all",
- },
- "& > span": {
- position: "relative",
- zIndex: 2,
- }
- },
- "abbr": {
- gridArea: "main",
- zIndex: 4,
- "& > span": {
- position: "relative",
- zIndex: 2,
- }
- },
- "code, pre": {
- fontFamily: "code",
- fontSize: "0.85em",
- },
- ".media-16-9": {
- height: 0,
- width: "calc(100% - 0.25rem)",
- zIndex: 1,
- transformOrigin: "right center",
- transitionDuration: "0.2s",
- transitionTimingFunction: "ease-in-out",
- transitionProperty: "common",
- paddingBottom: "56.25%",
- marginBlock: "0.25rem",
- marginInlineEnd: "0.25rem",
- position: "relative",
- },
- "iframe": {
- border: 0,
- boxShadow: "inset 0 0 0 0.125rem",
- position: "absolute",
- height: "100%",
- width: "100%",
- cursor: "default",
- top: 0,
- right: 0,
- left: 0,
- bottom: 0,
- borderRadius: "0.25rem",
- },
- "img": {
- borderRadius: "0.25rem",
- maxWidth: "calc(100% - 0.25rem)",
- },
- "h1": { fontSize: "xl" },
- "h2": { fontSize: "lg" },
- "h3": { fontSize: "md" },
- "h4": { fontSize: "sm" },
- "h5": { fontSize: "xs" },
- "h6": { fontSize: "xs" },
- "blockquote": {
- marginInline: "0.5em",
- marginBlock: "0.125rem",
- paddingBlock: "calc(0.5em - 0.125rem - 0.125rem)",
- paddingInline: "1.5em",
- borderRadius: "0.25em",
- background: "background.basement",
- borderInlineStart: "1px solid",
- borderColor: "separator.divider",
- color: "foreground.primary",
- },
- "p": {
- paddingBottom: "1em",
- "&last:-child": { paddingBottom: 0 },
- },
- "mark.contents.highlight": {
- padding: "0 0.2em",
- borderRadius: "0.125rem",
- background: "highlight",
- }
- }}
- {...props}>
- {children}
- ;
\ No newline at end of file
diff --git a/src/js/components/PresenceDetails/PresenceDetails.tsx b/src/js/components/PresenceDetails/PresenceDetails.tsx
index 418d0b2c8f..d8abd91e88 100644
--- a/src/js/components/PresenceDetails/PresenceDetails.tsx
+++ b/src/js/components/PresenceDetails/PresenceDetails.tsx
@@ -1,9 +1,160 @@
-import { withErrorBoundary } from 'react-error-boundary';
+import styled, { keyframes } from "styled-components";
import React from "react";
-import { Text, Tooltip, Avatar, AvatarGroup, Menu, MenuDivider, MenuButton, MenuList, MenuGroup, MenuItem, Button, Portal } from '@chakra-ui/react';
+import { RefreshDouble, Lock } from "iconoir-react";
+import { mergeProps } from "@react-aria/utils";
+import {
+ useOverlay,
+ useOverlayTrigger,
+ useOverlayPosition,
+ useModal,
+ OverlayContainer,
+} from "@react-aria/overlays";
+import { useOverlayTriggerState } from "@react-stately/overlays";
+import { FocusScope } from "@react-aria/focus";
+import { useDialog } from "@react-aria/dialog";
+
+import { Button } from "@/Button";
+import { Menu } from "@/Menu";
+import { Overlay } from "@/Overlay";
+import { Backdrop } from "@/Overlay/Backdrop";
+import { Avatar } from "@/Avatar";
import { ProfileSettingsDialog } from "@/ProfileSettingsDialog";
+import { ConnectedGraphConnection } from "@/Icons/ConnectedGraphConnection";
+import { ConnectedGraphHost } from "@/Icons/ConnectedGraphHost";
+import { Icon } from "@/Icons/Icon";
+
+const ConnectionButton = styled(Button)`
+ gap: 0.125rem;
+ transition: all 0s;
+ min-height: 2rem;
+ padding: 0.125em 0.5rem;
+ font-size: var(--font-size--text-xs);
+ border: 1px solid var(--border-color);
+
+ &.connecting {
+ color: var(--link-color);
+ }
+
+ &.reconnecting {
+ color: var(--highlight-color);
+ }
+
+ &.offline {
+ svg {
+ color: var(--body-text-color);
+ }
+ }
+`;
+
+const PresenceOverlay = styled(Overlay)`
+ min-width: 8rem;
+ flex-direction: column;
+ max-height: calc(100vh - 4rem);
+ overflow-y: auto;
+`;
+
+const HostIconWrap = styled(Icon)`
+ --size: 2rem;
+ border-radius: 18%;
+ background: var(--background-minus-2);
+ padding: 0.25rem;
+`;
+
+const HostIcon = () => (
+
+
+
+);
+
+const PersonWrap = styled.div`
+ padding: 0.375rem 0.5rem;
+ display: flex;
+ align-items: center;
+`;
+
+const Profile = styled.div`
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+
+ svg {
+ margin-right: 0.5rem;
+ }
+
+ button {
+ margin-left: auto;
+ font-size: var(--font-size--text-xs);
+ }
+`;
+
+const Host = styled(Profile)`
+ padding-top: 0.25rem;
+ padding-bottom: 0.25rem;
+
+ svg {
+ margin-left: 0.25rem;
+ }
+`;
+
+const rotate = keyframes`
+ from {
+ transform: rotate(0deg);
+ } to {
+ transform: rotate(360deg);
+ }
+`;
+
+const OfflineIcon = styled(Lock)`
+ width: 1.25rem;
+ height: 1.25rem;
+`;
+
+const ActivityIcon = styled(RefreshDouble)`
+ width: 1.25rem;
+ height: 1.25rem;
+ animation: ${rotate} 2s linear infinite;
+ stroke-width: 2;
+ vector-effect: non-scaling-stroke;
+`;
+
+const ConnectedGraphIconWrap = styled(Icon)`
+ --size: 1.5rem;
+`;
+
+const connectionStatusIndicator = {
+ connected: (
+
+
+
+ ),
+ connecting: (
+ <>
+
+ Connecting...
+ >
+ ),
+ reconnecting: (
+ <>
+
+ Reconnecting...
+ >
+ ),
+ offline: (
+ <>
+
+ View Only
+ >
+ ),
+};
+
+const connectionStatusHelpText = {
+ connecting: "Athens is connecting to the host.",
+ connected: "View connection details.",
+ reconnecting: "Athens is attempting to reconnect to the host.",
+ offline: "Athens is not connected.",
+};
export interface PresenceDetailsProps {
hostAddress: HostAddress;
@@ -16,45 +167,50 @@ export interface PresenceDetailsProps {
connectionStatus: ConnectionStatus;
defaultOpen?: boolean;
}
-interface ConnectionButtonProps {
- connectionStatus: ConnectionStatus;
- showablePersons: Person[];
+
+interface PresenceDetailsPopoverProps {
+ children: React.ReactNode;
+ isOpen: boolean;
+ onClose: () => void;
}
-const ConnectionButton = React.forwardRef((props: ConnectionButtonProps, ref) => {
- const { connectionStatus, showablePersons } = props;
- return (
-
-
- {connectionStatus === "connected" && (
- showablePersons.length > 0 && (
-
- {showablePersons.map((member) => (
-
- ))}
-
- )
- )}
-
-
- )
-});
+const PresenceDetailsPopover = React.forwardRef(
+ (
+ { isOpen, onClose, children, ...otherProps }: PresenceDetailsPopoverProps,
+ ref: RefObject
+ ) => {
+ let { overlayProps, underlayProps } = useOverlay(
+ {
+ onClose,
+ isOpen,
+ isDismissable: true,
+ },
+ ref
+ );
+
+ let { modalProps } = useModal();
+ let { dialogProps, titleProps } = useDialog({}, ref);
+ return (
+
+
+
+
+ Connection & Presence
+ {children}
+
+
+
+
+ );
+ }
+);
-export const _PresenceDetails = (props: PresenceDetailsProps) => {
+export const PresenceDetails = (props: PresenceDetailsProps) => {
const {
hostAddress,
currentUser,
@@ -64,113 +220,193 @@ export const _PresenceDetails = (props: PresenceDetailsProps) => {
handlePressMember,
handleUpdateProfile,
connectionStatus,
+ defaultOpen,
} = props;
- const showablePersons = [ ...currentPageMembers, ...differentPageMembers ];
- const [ shouldShowProfileSettings, setShouldShowProfileSettings ] = React.useState(false);
+ const showablePersons = [...currentPageMembers, ...differentPageMembers];
+
+ // State and controllers for the menu
+ let menuState = useOverlayTriggerState({ defaultOpen: defaultOpen });
+
+ let triggerRef = React.useRef();
+ let overlayRef = React.useRef();
+
+ let { triggerProps: presenceMenuTriggerProps, overlayProps: presenceMenuOverlayProps } = useOverlayTrigger(
+ { type: "listbox" },
+ menuState,
+ triggerRef,
+ );
+
+ let { overlayProps: positionProps } = useOverlayPosition({
+ targetRef: triggerRef,
+ overlayRef,
+ placement: "bottom end",
+ offset: 2,
+ isOpen: menuState.isOpen,
+ });
+
+ // State and controllers for the profile settings dialog
+ let profileSettingsState = useOverlayTriggerState({});
return connectionStatus === "local" ? (
<>>
) : (
<>
-
-
-
-
+
+ {connectionStatusIndicator[connectionStatus]}
+
+ {connectionStatus === "connected" && (
+ <>
+ {currentUser && (
+
+ )}
+
+ {showablePersons.length > 0 && (
+
+ {showablePersons.map((member) => (
+
+ ))}
+
+ )}
+ >
+ )}
+
+
+ {menuState.isOpen && (
+
+
<>
{hostAddress && (
- handleCopyHostAddress(hostAddress)}
- display="flex"
- flexDirection="column"
- textAlign="left"
- justifyContent="flex-start"
- alignItems="stretch"
- >
- Copy address
-
- {hostAddress}
-
-
+ <>
+
+
+ {hostAddress}
+ handleCopyHostAddress(hostAddress)}
+ >Copy
+
+
+ >
)}
{currentUser && (
- setShouldShowProfileSettings(true)} icon={ }>Edit appearance
+ <>
+
+
+ You appear as
+
+
+ {currentUser.username}
+
+
+ Edit
+
+
+ >
)}
{currentPageMembers.length > 0 && (
<>
-
-
+
+ On this page
+
{currentPageMembers.map((member) => (
- handlePressMember(member)}
key={member.personId}
- icon={ }
>
- {member.username}
-
+
+ {member.username}
+
))}
-
+
>
)}
{differentPageMembers.length > 0 && (
<>
-
-
- {differentPageMembers.map((member) => (
- handlePressMember(member)}
- key={member.personId}
- icon={ }
- >
- {member.username}
-
- ))}
-
+
+ On other pages
+ {differentPageMembers.map((member) => (
+ handlePressMember(member)}
+ key={member.personId}
+ >
+
+ {member.username}
+
+ ))}
>
)}
>
-
-
-
-
- setShouldShowProfileSettings(false)}
- onUpdatePerson={(person) => {
- handleUpdateProfile(person);
- setShouldShowProfileSettings(false)
- }}
- />
+
+
+ )
+ }
+
+ {currentUser && (
+ <>
+ {
+ handleUpdateProfile(person);
+ profileSettingsState.close();
+ }}
+ />
+ >
+ )}
>
);
};
-export const PresenceDetails = withErrorBoundary(_PresenceDetails, { fallback: <>> });
+PresenceDetails.defaultProps = {
+ defaultOpen: false,
+};
diff --git a/src/js/components/PresenceDetails/index.ts b/src/js/components/PresenceDetails/index.ts
index a02932631b..1f09a57914 100644
--- a/src/js/components/PresenceDetails/index.ts
+++ b/src/js/components/PresenceDetails/index.ts
@@ -1,2 +1,2 @@
-import { PresenceDetails } from "./PresenceDetails";
-export { PresenceDetails };
+import { PresenceDetails, PresenceDetailsProps } from "./PresenceDetails";
+export { PresenceDetails, PresenceDetailsProps };
\ No newline at end of file
diff --git a/src/js/components/ProfileSettingsDialog/ProfileSettingsDialog.stories.tsx b/src/js/components/ProfileSettingsDialog/ProfileSettingsDialog.stories.tsx
new file mode 100644
index 0000000000..5608aab36c
--- /dev/null
+++ b/src/js/components/ProfileSettingsDialog/ProfileSettingsDialog.stories.tsx
@@ -0,0 +1,67 @@
+import React from 'react';
+
+import { useOverlayTriggerState } from "@react-stately/overlays";
+
+import { BADGE, Storybook } from '@/utils/storybook';
+import { ProfileSettingsDialog } from './ProfileSettingsDialog';
+
+import { Avatar } from '../Avatar';
+import { Button } from '@/Button';
+
+export default {
+ title: 'Components/ProfileSettingsDialog',
+ component: ProfileSettingsDialog,
+ argTypes: {},
+ parameters: {
+ layout: 'centered',
+ badges: [BADGE.DEV, BADGE.IN_USE]
+ }
+};
+
+const testPerson = { personId: '123', username: 'John Doe', color: '#0071ed' };
+
+const Template = (args, context) => {
+ let profileSettingsState = useOverlayTriggerState({
+ defaultOpen: args.defaultOpen,
+ onOpenChange: (open) => {
+ console.log('ProfileSettingsDialog open state changed to: ', open);
+ },
+ });
+ const [person, setPerson] = React.useState(testPerson);
+
+ const handleUpdatePerson = React.useCallback((person) => {
+ setPerson(person);
+ profileSettingsState.close();
+ }, []);
+
+ return (
+ <>
+
+
+
+
+ >)
+};
+
+export const Default = Template.bind({});
+Default.args = {
+ defaultOpen: true
+}
diff --git a/src/js/components/ProfileSettingsDialog/ProfileSettingsDialog.tsx b/src/js/components/ProfileSettingsDialog/ProfileSettingsDialog.tsx
index 4e7a6c0bf2..fd0ccdf63f 100644
--- a/src/js/components/ProfileSettingsDialog/ProfileSettingsDialog.tsx
+++ b/src/js/components/ProfileSettingsDialog/ProfileSettingsDialog.tsx
@@ -1,31 +1,58 @@
import React from 'react';
-import { withErrorBoundary } from 'react-error-boundary';
-
-import {
- keyframes,
- Modal,
- ModalOverlay,
- ModalFooter,
- Center,
- Flex,
- Box,
- ButtonGroup,
- ModalHeader,
- ModalCloseButton,
- ModalContent,
- ModalBody,
- Text,
- FormControl,
- FormHelperText,
- Input,
- Avatar,
- Button
-} from '@chakra-ui/react';
-
+import styled, { keyframes } from 'styled-components';
+import { readableColor } from 'polished';
import { HexColorPicker } from "react-colorful";
import { AriaDialogProps } from '@react-types/dialog';
import { OverlayProps } from '@react-aria/overlays';
+import { Button } from '@/Button';
+import { Avatar } from '../Avatar';
+import { Input } from '../Input';
+import { Dialog } from '../Dialog';
+
+const ProfileWrap = styled(Dialog.Body)`
+ width: 26rem;
+
+ h3 {
+ text-align: center;
+ margin: 0;
+ font-weight: 600;
+ }
+
+ hr {
+ margin: 1rem 0;
+ border: 0;
+ border-top: 1px solid var(--border-color);
+ }
+`;
+
+const Actions = styled(Dialog.Actions)`
+ padding-Bottom: 1rem;
+ align-self: center;
+ gap: 1rem;
+ margin: 0;
+
+ button {
+ width: 5em;
+ }
+`;
+
+const AvatarWrap = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 20rem;
+ margin: 1em auto 2em;
+ border-radius: 1rem;
+ padding: 1rem;
+ border: 1px solid var(--border-color);
+ background: var(--background-plus-2);
+
+ > * {
+ filter: drop-shadow(0 0.25rem 0.25rem var(--shadow-color---opacity-10));
+ }
+`;
+
const pulse = keyframes`
from {
transform: translate(-50%, -50%) scale(1) ;
@@ -34,39 +61,42 @@ const pulse = keyframes`
}
`;
-const ColorPickerWrap = ({ children }) => {
- return ( *": {
- borderRadius: "0.5rem",
- height: "100%",
- flex: "0 0 4rem",
- }
- },
- ".react-colorful__saturation": {
- borderBottom: 0
- },
- ".react-colorful__interactive:focus .react-colorful__pointer": {
- animation: `${pulse} 0.5s infinite alternate ease-in-out`
- }
- }}
- >
- {children}
- )
-};
-
-const Inputs = ({ children }) => {
- return (
- {children}
- )
-}
+const ColorPickerWrap = styled.div`
+ .react-colorful {
+ width: 8.5rem;
+ height: 3rem;
+ gap: 1rem;
+ margin: -0.25rem 0 1rem;
+ flex-direction: row;
+
+ > * {
+ border-radius: 0.5rem;
+ height: 100%;
+ flex: 0 0 4rem;
+ }
+ }
+
+ .react-colorful__saturation {
+ border-bottom: 0;
+ }
+
+ .react-colorful__interactive:focus
+ .react-colorful__pointer {
+ animation: ${pulse} 0.5s infinite alternate ease-in-out;
+ }
+`;
+
+const Inputs = styled.div`
+ display: flex;
+ gap: 2rem;
+ align-items: flex-start;
+ justify-content: center;
+`;
+
+const LabelWrapper = styled(Input.LabelWrapper)`
+ gap: 0.25rem;
+`;
+
interface ProfileSettingsDialogProps extends OverlayProps, AriaDialogProps {
person: Person;
@@ -75,16 +105,16 @@ interface ProfileSettingsDialogProps extends OverlayProps, AriaDialogProps {
/**
* Dialog for modifying the current user's username and color
*/
-export const _ProfileSettingsDialog = ({
+export const ProfileSettingsDialog = ({
person,
onClose: handleClose,
onUpdatePerson: handleUpdatePerson,
isOpen,
...rest
}: ProfileSettingsDialogProps) => {
- const [ editingUsername, setEditingUsername ] = React.useState(person.username || '');
- const [ editingColor, setEditingColor ] = React.useState(person.color || '#0071DB');
- const [ isValidUsername, setIsValidUsername ] = React.useState(!!editingUsername);
+ const [editingUsername, setEditingUsername] = React.useState(person.username || '');
+ const [editingColor, setEditingColor] = React.useState(person.color || '#0071DB');
+ const [isValidUsername, setIsValidUsername] = React.useState(!!editingUsername);
const handleChangeUsername = (e: React.ChangeEvent) => {
const attempt = e.target.value.trim();
@@ -93,60 +123,55 @@ export const _ProfileSettingsDialog = ({
}
return (
-
-
-
-
- Change how you appear to others
-
-
-
-
+ How you appear to others
+
+
+
+ {isValidUsername ? editingUsername : person.username}
+
+
+
+
+
+
+
+
+ At least 2 characters
+
+
+
+
+
+ Cancel
+
+ handleUpdatePerson({ ...person, username: editingUsername, color: editingColor })}
>
-
- {isValidUsername ? editingUsername : person.username}
-
-
-
-
-
-
-
- At least 2 characters
-
-
-
-
-
-
- Cancel
- handleUpdatePerson({ ...person, username: editingUsername, color: editingColor })}>Change appearance
-
-
-
-
+ Save
+
+
+
+
)
}
-
-export const ProfileSettingsDialog = withErrorBoundary(_ProfileSettingsDialog, { fallback: null });
diff --git a/src/js/components/Spinner/Spinner.stories.tsx b/src/js/components/Spinner/Spinner.stories.tsx
new file mode 100644
index 0000000000..44f418ef9c
--- /dev/null
+++ b/src/js/components/Spinner/Spinner.stories.tsx
@@ -0,0 +1,51 @@
+import { BADGE, Storybook } from '@/utils/storybook';
+
+import { Spinner, Progress } from '@/Spinner/Spinner';
+import { Indeterminate } from '@/Spinner/components/Indeterminate';
+
+export default {
+ title: 'Components/Spinner',
+ component: Spinner,
+ argTypes: {},
+ parameters: {
+ layout: 'centered',
+ badges: [BADGE.DEV]
+ },
+ decorators: [(Story, args) => Template(Story, args)]
+};
+
+const Template = (Story, args) =>
;
+
+export const Basic = () =>
;
+export const ProgressSpinner = () =>
;
+export const IndeterminateProgressSpinner = () => <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+>;
diff --git a/src/js/components/Spinner/Spinner.tsx b/src/js/components/Spinner/Spinner.tsx
new file mode 100644
index 0000000000..3856d47b63
--- /dev/null
+++ b/src/js/components/Spinner/Spinner.tsx
@@ -0,0 +1,88 @@
+import styled, { keyframes } from "styled-components";
+import { classnames } from "@/utils/classnames";
+import React from "react";
+
+export const spin = keyframes`
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+`;
+
+const appearAndDrop = keyframes`
+ 0% {
+ transform: translateY(-40%);
+ opacity: 0;
+ }
+ 100% {
+ transform: translateY(0);
+ opacity: 1;
+ }
+`;
+
+const Wrap = styled.div`
+ width: ${(props) => props.size};
+ height: ${(props) => props.size};
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ align-self: center;
+ margin: auto;
+ text-align: center;
+ place-items: center;
+ animation: ${appearAndDrop} 0.5s ease;
+ place-content: center;
+
+ &.placement-center {
+ position: absolute;
+ top: calc(50% - ${(props) => props.size} / 2);
+ left: calc(50% - ${(props) => props.size} / 2);
+ }
+`;
+
+export const Progress = styled.div`
+ width: 3em;
+ height: 3em;
+ border-radius: 1000em;
+ border: 1.5px solid var(--background-minus-1);
+ border-top-color: var(--link-color);
+ animation: ${spin} 1s linear infinite;
+`;
+
+const Message = styled.span`
+ animation: ${appearAndDrop} ${(props) => props.messageDelay}s 0.75s
+ ease-in-out;
+ font-size: 14px;
+ animation-fill-mode: both;
+`;
+
+interface SpinnerProps {
+ message?: string | React.ReactNode;
+ placement?: "center" | null;
+ size?: string;
+ messageDelay?: number;
+}
+
+export const Spinner = ({
+ message,
+ placement,
+ size,
+ messageDelay,
+}: SpinnerProps): JSX.Element => (
+
+
+ {message && {message} }
+
+);
+
+Spinner.defaultProps = {
+ message: "Loading...",
+ placement: "center",
+ size: "10rem",
+ messageDelay: 2,
+};
diff --git a/src/js/components/Spinner/components/Indeterminate.tsx b/src/js/components/Spinner/components/Indeterminate.tsx
new file mode 100644
index 0000000000..d2f8d2f56e
--- /dev/null
+++ b/src/js/components/Spinner/components/Indeterminate.tsx
@@ -0,0 +1,24 @@
+import styled from 'styled-components';
+import { spin } from '../Spinner';
+
+const Svg = styled.svg`
+ width: var(--size, 2em);
+ height: var(--size, 2em);
+ animation: ${spin} 3.33s linear infinite;
+`;
+
+export const Indeterminate = (props) =>
+
+
diff --git a/src/js/components/Spinner/index.ts b/src/js/components/Spinner/index.ts
new file mode 100644
index 0000000000..0a1710f571
--- /dev/null
+++ b/src/js/components/Spinner/index.ts
@@ -0,0 +1,2 @@
+import { Spinner } from './Spinner';
+export { Spinner };
diff --git a/src/js/components/StandaloneApp.stories.tsx b/src/js/components/StandaloneApp.stories.tsx
new file mode 100644
index 0000000000..a44d036973
--- /dev/null
+++ b/src/js/components/StandaloneApp.stories.tsx
@@ -0,0 +1,254 @@
+import styled from 'styled-components';
+import { classnames } from '@/utils/classnames';
+import { Storybook } from '@/utils/storybook';
+
+import { useAppState } from '@/utils/useAppState';
+
+import { LeftSidebar } from '@/concept/LeftSidebar';
+import { RightSidebar } from '@/concept/RightSidebar';
+import { AppToolbar } from '@/AppToolbar';
+import { CommandBar } from '@/concept/CommandBar';
+import { AppLayout, MainContent } from '@/concept/App';
+import { Page } from '@/concept/Page';
+import { usePresenceProvider } from '@/concept/Block/hooks/usePresenceProvider';
+
+import { mockPeople } from '@/Avatar/mockData';
+const mockPresence = mockPeople.map((p, index) => ({ ...p, uid: index.toString() }))
+
+import {
+ WithPresence
+} from './concept/Block/Block.stories';
+
+export default {
+ title: 'App/Standalone',
+ component: Window,
+ argTypes: {
+ connectionStatus: {
+ options: ['local', 'connecting', 'connected', 'reconnecting', 'offline'],
+ control: { type: 'radio' },
+ defaultValue: 'local'
+ }
+ },
+ parameters: {
+ layout: 'fullscreen'
+ },
+ decorators: [(Story) =>
]
+};
+
+const WindowWrapper = styled.div`
+ justify-self: stretch;
+ width: 100%;
+ border-radius: 5px;
+ box-shadow: 0 10px 12px rgb(0 0 0 / 0.1);
+ overflow: hidden;
+ position: relative;
+ background: var(--background-color);
+
+ > * {
+ z-index: 1;
+ }
+
+ &.os-windows {
+ border-radius: 4px;
+ }
+
+ &.os-mac {
+ border-radius: 12px;
+
+ &.is-electron {
+ &:before {
+ content: '';
+ width: 12px;
+ height: 12px;
+ position: absolute;
+ border-radius: 100px;
+ left: 20px;
+ top: 19px;
+ background: #888;
+ z-index: 999999;
+ box-shadow: 20px 0 0 0 #888, 40px 0 0 0 #888;
+ }
+
+ &.is-theme-dark {
+ &:after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ border-radius: inherit;
+ pointer-events: none;
+ z-index: 2;
+ box-shadow: inset 0 0 1px #fff, 0 0 1px #000;
+ }
+ }
+ }
+ }
+
+ &.is-win-maximized,
+ &.is-win-fullscreen {
+ border-radius: 0;
+ height: 100vh;
+ width: 100vw;
+ margin: 0;
+ }
+
+ &.is-storybook-docs {
+ height: 700px;
+ }
+`;
+
+const Template = (args, context) => {
+ const {
+ currentUser,
+ setCurrentUser,
+ route,
+ currentPageMembers,
+ differentPageMembers,
+ activeDatabase,
+ setActiveDatabase,
+ inactiveDatabases,
+ isSynced,
+ isElectron,
+ setRoute,
+ hostAddress,
+ isThemeDark,
+ setIsThemeDark,
+ isWinFullscreen,
+ isWinFocused,
+ isWinMaximized,
+ isLeftSidebarOpen,
+ setIsLeftSidebarOpen,
+ isRightSidebarOpen,
+ setIsRightSidebarOpen,
+ setIsSettingsOpen,
+ isCommandBarOpen,
+ setIsCommandBarOpen,
+ isMergeDialogOpen,
+ setIsMergeDialogOpen,
+ isDatabaseDialogOpen,
+ } = useAppState();
+
+ const { PresenceProvider, clearPresence } = usePresenceProvider({ presentPeople: mockPresence });
+
+ return (
+
+
+ setActiveDatabase(database)}
+ handlePressAddDatabase={() => console.log('pressed add database')}
+ handlePressRemoveDatabase={() => console.log('pressed remove database')}
+ handlePressImportDatabase={() => console.log('pressed import database')}
+ handlePressMoveDatabase={() => console.log('pressed move database')}
+ handlePressMember={(person) => console.log(person)}
+ handlePressCommandBar={() => setIsCommandBarOpen(!isCommandBarOpen)}
+ handlePressDailyNotes={() => setRoute('/daily-notes')}
+ handlePressAllPages={() => setRoute('/all-pages')}
+ handlePressGraph={() => setRoute('/graph')}
+ handlePressThemeToggle={() => setIsThemeDark(!isThemeDark)}
+ handlePressMerge={() => setIsMergeDialogOpen(true)}
+ handlePressSettings={() => setIsSettingsOpen(true)}
+ handlePressHistoryBack={() => console.log('pressed go back')}
+ handlePressHistoryForward={() => console.log('pressed go forward')}
+ handlePressLeftSidebarToggle={() => setIsLeftSidebarOpen(!isLeftSidebarOpen)}
+ handlePressRightSidebarToggle={() => setIsRightSidebarOpen(!isRightSidebarOpen)}
+ handlePressMinimize={() => console.log('pressed minimize')}
+ handlePressMaximizeRestore={() => console.log('pressed maximize/restore')}
+ handlePressClose={() => console.log('pressed close')}
+ handlePressHostAddress={(hostAddress) => console.log('pressed', hostAddress)}
+ handleUpdateProfile={(person) => setCurrentUser(person)}
+ />
+ null}
+ shortcuts={[{
+ uid: "4b89dde0-3ccf-481a-875b-d11adfda3f7e",
+ title: "Passer domesticus",
+ order: 1
+ }, {
+ uid: "bd4a892f-c7e5-45d8-bab8-68a8ed9d224f",
+ title: "Spermophilus richardsonii",
+ order: 2
+ }, {
+ uid: "b60fc12e-bf48-415c-a059-a7a4d5ef686e",
+ title: "Leprocaulinus vipera",
+ order: 3
+ }, {
+ uid: "c58d62e5-0e1b-4f30-a156-af8467317c1c",
+ title: "Rangifer tarandus",
+ order: 4
+ }, {
+ uid: "dd099e5d-1f6d-4be7-8bf0-9fc0310ba489",
+ title: "Nycticorax nycticorax",
+ order: 5
+ }]}
+ version="1.0.0"
+ />
+
+ null}
+ handlePressUnlinkedReferencesToggle={() => null}
+ >
+
+
+
+
+
+
+ {/* */}
+ {isCommandBarOpen && ( setIsCommandBarOpen(false)}
+ />)
+ }
+
+ )
+};
+
+export const MacOs = Template.bind({});
+MacOs.args = {
+ os: 'mac',
+ isElectron: true,
+};
+
+export const Windows = Template.bind({});
+Windows.args = {
+ os: 'windows',
+ isElectron: true
+};
+
+export const Linux = Template.bind({});
+Linux.args = {
+ os: 'linux',
+ isElectron: true
+};
diff --git a/src/js/components/Toggle/Toggle.stories.tsx b/src/js/components/Toggle/Toggle.stories.tsx
new file mode 100644
index 0000000000..97a82860f6
--- /dev/null
+++ b/src/js/components/Toggle/Toggle.stories.tsx
@@ -0,0 +1,41 @@
+import { Toggle } from './Toggle';
+import { BADGE, Storybook } from '@/utils/storybook';
+
+export default {
+ title: 'Components/Toggle',
+ component: Toggle,
+ argTypes: {},
+ parameters: {
+ layout: 'centered',
+ badges: [BADGE.DEV, BADGE.IN_USE]
+ },
+ decorators: [(Story) =>
]
+};
+
+const Template = (args) =>
;
+
+export const Default = Template.bind({});
+Default.args = {
+ defaultSelected: true
+};
+
+export const Wide = Template.bind({});
+Wide.args = {
+ toggleShape: {
+ width: 80,
+ height: 36,
+ inset: 1.5
+ }
+};
+
+export const Labeled = Template.bind({});
+Labeled.args = {
+ checkedLabel: 'On',
+ unCheckedLabel: 'Off',
+ style: { fontSize: '2rem' },
+ toggleShape: {
+ width: 120,
+ height: 50,
+ inset: 1.5
+ }
+};
diff --git a/src/js/components/Toggle/Toggle.tsx b/src/js/components/Toggle/Toggle.tsx
new file mode 100644
index 0000000000..ac27f1616a
--- /dev/null
+++ b/src/js/components/Toggle/Toggle.tsx
@@ -0,0 +1,189 @@
+import React from 'react';
+import styled from 'styled-components';
+import { useSwitch } from '@react-aria/switch'
+import { useToggleState } from '@react-stately/toggle';
+import { HoverProps, useHover } from '@react-aria/interactions';
+import { useFocusRing } from '@react-aria/focus';
+import { usePress } from '@react-aria/interactions'
+import { useVisuallyHidden } from '@react-aria/visually-hidden'
+import { AriaSwitchProps } from '@react-types/switch';
+import { mergeProps } from '@react-aria/utils';
+
+import { classnames } from '@/utils/classnames';
+
+const Handle = styled.rect``;
+
+const Track = styled.rect``;
+
+const FocusRing = styled.rect``;
+
+const Svg = styled.svg`
+ height: 1em;
+ flex: 0 0 auto;
+ overflow: visible;
+ &,
+ & * {
+ transform-origin: center;
+ vector-effect: non-scaling-stroke;
+ transition: all 0.1s ease-in-out;
+ }
+`;
+
+const ValueLabel = styled.text``;
+
+const Wrap = styled.label`
+ ${Track} {
+ fill: var(--body-text-color---opacity-low);
+ }
+ ${Handle} {
+ fill: var(--link-color---contrast);
+ }
+ &.is-selected {
+ ${Track} {
+ fill: var(--link-color);
+ }
+ ${Handle} {
+ fill: var(--link-color---contrast);
+ }
+ }
+ &.is-hovered {
+ ${Track} {
+ fill: var(--body-text-color---opacity-med);
+ }
+ ${Handle} {
+ fill: var(--link-color---contrast);
+ }
+ }
+ &.is-selected.is-hovered {
+ ${Track} {
+ fill: var(--link-color---opacity-high);
+ }
+ ${Handle} {
+ fill: var(--link-color---contrast);
+ }
+ }
+ &.is-pressed {
+ ${Track},
+ ${Handle} {
+ transition-duration: 0s;
+ }
+
+ ${Track} {
+ fill: var(--link-color);
+ }
+ ${Handle} {
+ fill: var(--link-color---contrast);
+ }
+ }
+`;
+
+interface ToggleProps extends AriaSwitchProps, HoverProps {
+ toggleShape?: { width: number, height: number, inset: number };
+ children?: React.ReactNode;
+ defaultValue?: boolean;
+ checkedLabel?: string;
+ unCheckedLabel?: string;
+ style: React.CSSProperties;
+ onChange?: (value: boolean) => void;
+}
+
+export const Toggle = (props: ToggleProps) => {
+ const {
+ children,
+ toggleShape,
+ checkedLabel,
+ unCheckedLabel,
+ style
+ } = props;
+
+ let state = useToggleState(props);
+ let ref = React.useRef(null);
+ let { inputProps } = useSwitch(props, state, ref);
+ let { hoverProps, isHovered } = useHover(props);
+ let { pressProps, isPressed } = usePress(props);
+ let { isFocusVisible, focusProps } = useFocusRing();
+ let { visuallyHiddenProps } = useVisuallyHidden();
+
+ return (
+
+
+
+
+
+ {checkedLabel &&
+ {checkedLabel}
+ }
+ {unCheckedLabel && {unCheckedLabel} }
+
+
+ {children}
+
+ )
+}
+
+Toggle.defaultProps = {
+ toggleShape: {
+ width: 36,
+ height: 24,
+ inset: 1.5
+ }
+}
+
+Toggle.Wrap = Wrap;
+Toggle.Svg = Svg;
+Toggle.Handle = Handle;
+Toggle.Track = Track;
+Toggle.ValueLabel = ValueLabel;
diff --git a/src/js/components/Toggle/index.ts b/src/js/components/Toggle/index.ts
new file mode 100644
index 0000000000..f12465ea72
--- /dev/null
+++ b/src/js/components/Toggle/index.ts
@@ -0,0 +1,2 @@
+import { Toggle } from './Toggle';
+export { Toggle };
diff --git a/src/js/components/concept/App/AppLayout.ts b/src/js/components/concept/App/AppLayout.ts
new file mode 100644
index 0000000000..1ef3504c98
--- /dev/null
+++ b/src/js/components/concept/App/AppLayout.ts
@@ -0,0 +1,19 @@
+import styled from 'styled-components';
+
+/**
+ * Provides grid for high-level app layout
+ */
+export const AppLayout = styled.div.attrs({ id: 'app-layout' })`
+ --app-upper-spacing: 2.5rem;
+ display: grid;
+ grid-template-areas: 'app-header app-header app-header'
+ 'left-sidebar main-content secondary-content'
+ 'devtool devtool devtool';
+ grid-template-columns: auto 1fr auto;
+ grid-template-rows: auto 1fr auto;
+ height: 100vh;
+
+ .os-mac & {
+ --app-upper-spacing: calc(2.5rem + 48px);
+ }
+`;
\ No newline at end of file
diff --git a/src/js/components/concept/App/MainContent.ts b/src/js/components/concept/App/MainContent.ts
new file mode 100644
index 0000000000..b6c9c8da0e
--- /dev/null
+++ b/src/js/components/concept/App/MainContent.ts
@@ -0,0 +1,31 @@
+import styled from 'styled-components';
+
+export const MainContent = styled.div`
+ flex: 1 1 100%;
+ margin-left: auto;
+ margin-right: auto;
+ grid-area: main-content;
+ align-items: flex-start;
+ justify-content: stretch;
+ padding-top: var(--app-upper-spacing);
+ display: flex;
+ overflow-y: auto;
+
+ @supports (overflow-y: overlay) {
+ overflow-y: overlay;
+ }
+
+ &::-webkit-scrollbar {
+ background: var(--background-minus-1);
+ width: 0.5rem;
+ height: 0.5rem;
+ }
+
+ &::-webkit-scrollbar-corner {
+ background: var(--background-minus-1);
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: var(--background-minus-2);
+ }
+`;
\ No newline at end of file
diff --git a/src/js/components/concept/App/index.ts b/src/js/components/concept/App/index.ts
new file mode 100644
index 0000000000..d22a199262
--- /dev/null
+++ b/src/js/components/concept/App/index.ts
@@ -0,0 +1,3 @@
+import { MainContent } from './MainContent';
+import { AppLayout } from './AppLayout';
+export { AppLayout, MainContent };
\ No newline at end of file
diff --git a/src/js/components/concept/Badge/Badge.stories.tsx b/src/js/components/concept/Badge/Badge.stories.tsx
new file mode 100644
index 0000000000..1a85167157
--- /dev/null
+++ b/src/js/components/concept/Badge/Badge.stories.tsx
@@ -0,0 +1,76 @@
+import styled from 'styled-components';
+import { Badge } from './Badge';
+import { BADGE, Storybook } from '@/utils/storybook';
+
+import { Home } from '@material-ui/icons';
+
+export default {
+ title: 'Concepts/Badge',
+ component: Badge,
+ argTypes: {},
+ parameters: {
+ layout: 'centered',
+ badges: [BADGE.DEV]
+ },
+ decorators: [(Story) =>
]
+};
+
+const Wrapper = styled.div`
+ display: flex;
+ gap: 2rem;
+ font-size: 2em;
+`;
+
+const MockObject = styled.div`
+ border-radius: 0.25rem;
+ background: var(--body-text-color---opacity-low);
+ width: 2em;
+ height: 2em;
+`;
+
+const Template = (args) =>
;
+
+export const Default = Template.bind({});
+Default.args = {
+ children:
,
+};
+
+export const Content = Template.bind({});
+Content.args = {
+ children:
,
+ badgeContent: '7'
+};
+
+const StyledIcon = styled(Home)`
+ background: var(--background-plus-2);
+ padding: 0.125em;
+ border-radius: 100em;
+ font-size: 2.5rem !important;
+`;
+
+export const Position = () =>
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/js/components/concept/Badge/Badge.tsx b/src/js/components/concept/Badge/Badge.tsx
new file mode 100644
index 0000000000..a97b720c7c
--- /dev/null
+++ b/src/js/components/concept/Badge/Badge.tsx
@@ -0,0 +1,65 @@
+import React, { ReactNode } from 'react';
+import styled from 'styled-components';
+
+const BadgeEl = styled.b`
+ position: absolute;
+ border-radius: 100em;
+ background: var(--badge-background-color, var(--link-color));
+ font-size: var(--font-size--text-xs);
+ color: var(--badge-text-color, #fff);
+ padding: 0.125em 0.35em;
+ line-height: 1;
+
+ &:empty {
+ padding: 0;
+ width: var(--size, 0.5rem);
+ height: var(--size, 0.5rem);
+ }
+
+ &.placement-top-right {
+ top: 0;
+ right: 0;
+ transform: translate(50%, -50%);
+ }
+ &.placement-top-left {
+ top: 0;
+ left: 0;
+ transform: translate(-50%, -50%);
+ }
+ &.placement-bottom-left {
+ bottom: 0;
+ left: 0;
+ transform: translate(-50%, 50%);
+ }
+ &.placement-bottom-right {
+ bottom: 0;
+ right: 0;
+ transform: translate(50%, 50%);
+ }
+`;
+
+const BadgeWrap = styled.span`
+ position: relative;
+ display: inline-flex;
+`;
+
+interface BadgeProps extends React.HTMLAttributes
{
+ badgeContent?: ReactNode,
+ /** Where the badge should appear relative to the content */
+ placement?: 'top-right' | 'top-left' | 'bottom-left' | 'bottom-right',
+}
+
+/**
+ * Wrap the content with a badge
+ */
+export const Badge = ({
+ badgeContent,
+ placement = "top-right",
+ children,
+ ...props
+}: BadgeProps) =>
+ {children}
+
+ {badgeContent}
+
+
diff --git a/src/js/components/concept/Badge/index.ts b/src/js/components/concept/Badge/index.ts
new file mode 100644
index 0000000000..0450f88303
--- /dev/null
+++ b/src/js/components/concept/Badge/index.ts
@@ -0,0 +1,2 @@
+import { Badge } from './Badge';
+export { Badge }
\ No newline at end of file
diff --git a/src/js/components/concept/Block/Block.stories.tsx b/src/js/components/concept/Block/Block.stories.tsx
new file mode 100644
index 0000000000..f9fd9dde88
--- /dev/null
+++ b/src/js/components/concept/Block/Block.stories.tsx
@@ -0,0 +1,134 @@
+import { mockPeople } from '@/Avatar/mockData';
+
+import { Block } from './Block';
+import { BADGE, Storybook } from '@/utils/storybook';
+import { Meter } from '@/concept/Meter';
+import { blockTree } from './mockData';
+import { renderBlocks } from './utils/renderBlocks';
+import { useToggle } from './hooks/useToggle';
+import { usePresence } from './hooks/usePresence';
+import { useChecklist } from './hooks/useChecklist';
+import { useSelection } from './hooks/useSelection';
+import { useBlockState } from './hooks/useBlockState';
+import { usePresenceProvider } from './hooks/usePresenceProvider';
+
+const mockPresence = mockPeople.map((p, index) => ({ ...p, uid: index.toString() }))
+
+export default {
+ title: 'Concepts/Block',
+ blockComponent: Block,
+ argTypes: {},
+ decorators: [(Story) => ],
+ parameters: {
+ badges: [BADGE.DEV]
+ }
+};
+
+const Template = (args) => ;
+
+export const Basic = Template.bind({});
+Basic.args = {
+ ...blockTree.blocks["1"],
+};
+
+export const Editing = Template.bind({});
+Editing.args = {
+ ...blockTree.blocks["1"],
+ isEditing: true,
+};
+
+export const References = Template.bind({});
+References.args = {
+ ...blockTree.blocks["1"],
+ refsCount: 12
+};
+
+export const Selected = Template.bind({});
+Selected.args = {
+ ...blockTree.blocks["1"],
+ isSelected: true,
+};
+
+export const WithToggle = () => {
+ const { blockGraph: withState, setBlockState } = useBlockState(blockTree);
+ const { blockGraph } = useToggle(withState, setBlockState);
+
+ const blocks = renderBlocks({
+ blockGraph: blockGraph,
+ setBlockState: setBlockState,
+ blockComponent:
+ });
+
+ return blocks;
+}
+
+
+export const WithPresence = () => {
+ const { blockGraph: withState, setBlockState: withStateState } = useBlockState(blockTree);
+ const { blockGraph, setBlockState } = usePresence(withState, withStateState);
+
+ const blocks = renderBlocks({
+ blockGraph: blockGraph,
+ setBlockState: setBlockState,
+ blockComponent:
+ });
+
+ return blocks;
+}
+
+WithPresence.decorators = [(Story) => {
+ const { PresenceProvider, clearPresence } = usePresenceProvider({ presentPeople: mockPresence });
+
+ return
+
+ clearPresence()}>Clear Presence
+
+
+
+}];
+
+export const WithChecklist = () => {
+ const { blockGraph: withState, setBlockState } = useBlockState(blockTree);
+ const { blockGraph: withToggle } = useToggle(withState, setBlockState);
+ const { blockGraph, checked, total } = useChecklist(withToggle, setBlockState);
+
+ const blocks = renderBlocks({
+ blockGraph: blockGraph,
+ setBlockState: setBlockState,
+ blockComponent:
+ });
+
+ return <>
+
+
+ {blocks}
+ >;
+}
+
+export const WithSelection = () => {
+ const { blockGraph: withState, setBlockState } = useBlockState(blockTree);
+ const { blockGraph: withToggle } = useToggle(withState, setBlockState);
+ const { blockGraph } = useSelection(withToggle, setBlockState);
+
+ const blocks = renderBlocks({
+ blockGraph: blockGraph,
+ setBlockState: setBlockState,
+ blockComponent:
+ });
+
+ return blocks;
+}
+
+export const MultipleSelected = () => {
+ const { blockGraph: withState, setBlockState } = useBlockState(blockTree);
+ const { blockGraph: withToggle } = useToggle(withState, setBlockState);
+ const { blockGraph } = useSelection(withToggle, setBlockState, true);
+
+ const blocks = renderBlocks({
+ blockGraph: blockGraph,
+ setBlockState: setBlockState,
+ blockComponent:
+ });
+
+ return blocks;
+}
diff --git a/src/js/components/concept/Block/Block.tsx b/src/js/components/concept/Block/Block.tsx
new file mode 100644
index 0000000000..e8844106bf
--- /dev/null
+++ b/src/js/components/concept/Block/Block.tsx
@@ -0,0 +1,214 @@
+import React from "react";
+import styled from "styled-components";
+import { Popper } from "@material-ui/core";
+
+import { DOMRoot } from "@/utils/config";
+import { classnames } from "@/utils/classnames";
+
+import { Avatar } from "@/Avatar";
+import { Anchor } from "@/Block/components/Anchor";
+import { Toggle } from "@/Block/components/Toggle";
+import { Body } from "./components/Body";
+import { Content, ContentProps } from "./components/Content";
+import { Refs } from "./components/Refs";
+import { Container } from "./components/Container";
+
+export interface BlockProps extends Block, ContentProps {
+ /**
+ * Whether this block is in editing mode
+ */
+ isDragging?: boolean;
+ /**
+ * Whether this block is being dragged
+ */
+ isEditing?: boolean;
+ /**
+ * Number of references to this block
+ */
+ refsCount?: number;
+ /**
+ * Whether to display the avatar of a present user
+ */
+ showPresentUser?: boolean;
+ /**
+ *
+ */
+ linkedRef?: string;
+ /**
+ * When toggle is pressed
+ */
+ handlePressToggle?: (uid) => void;
+ /**
+ * When anchor is pressed
+ */
+ handlePressAnchor?: () => void;
+ /**
+ * When mouse is over block
+ */
+ handleMouseEnterBlock?: () => void;
+ /**
+ * When mouse leaves block
+ */
+ handleMouseLeaveBlock?: () => void;
+ /**
+ * When raw content of a block is modified.
+ * Returns the new value of the raw content.
+ */
+ handleContentChange?: (e: any) => void;
+ /**
+ * When the content is clicked or tapped
+ */
+ handlePressContainer?: () => void;
+ /**
+ * When a dragged item is over this block
+ */
+ handleDragOver?: () => void;
+ /**
+ * When a dragged item is no longer over this block
+ */
+ handleDragLeave?: () => void;
+ /**
+ * When a dragged item dropped on this block
+ */
+ handleDrop?: () => void;
+}
+
+export const Block = ({
+ children,
+ rawContent,
+ renderedContent,
+ presentUser,
+ showPresentUser = true,
+ isOpen = true,
+ isSelected,
+ isEditable,
+ isEditing,
+ isLocked,
+ isDragging,
+ linkedRef,
+ refsCount,
+ uid,
+ contentProps,
+ textareaProps,
+ handleContentChange,
+ handleMouseEnterBlock,
+ handleMouseLeaveBlock,
+ handlePressToggle,
+ handlePressAnchor,
+ handlePressContainer,
+ handleDragOver,
+ handleDragLeave,
+ handleDrop,
+}: BlockProps) => {
+ const [showEditableDom, setRenderEditableDom] = React.useState(
+ false
+ );
+ const [
+ avatarAnchorEl,
+ setAvatarAnchorEl,
+ ] = React.useState(null);
+
+ return (
+ <>
+ {
+ e.stopPropagation();
+ handlePressContainer && handlePressContainer(e);
+ }}
+ onDragOver={handleDragOver}
+ onDragLeave={handleDragLeave}
+ onDrop={handleDrop}
+ className={classnames(
+ children && "show-tree-indicator",
+ isOpen ? "is-open" : "is-closed",
+ linkedRef && "is-linked-ref",
+ isLocked && "is-locked",
+ isSelected && "is-selected",
+ presentUser && showPresentUser && "is-presence",
+ isSelected && isDragging && "is-dragging",
+ isEditing && "is-editing"
+ )}
+ >
+ {/* Drop area indicator before */}
+ {
+ handleMouseEnterBlock;
+ isEditable && setRenderEditableDom(true);
+ }}
+ onMouseLeave={() => {
+ handleMouseLeaveBlock;
+ isEditable && setRenderEditableDom(false);
+ }}
+ >
+ {children && !isLocked && (
+
+ )}
+
+ {/* Tooltip el */}
+
+ {refsCount >= 1 && }
+
+ {/* inline search el */}
+ {/* slash menu el */}
+ {isOpen && children}
+ {/* Drop area indicator child */}
+ {/* Drop area indicator after */}
+
+
+ {showPresentUser && presentUser && (
+ <>
+
+
+
+ >
+ )}
+ >
+ );
+};
+
+Block.Anchor = Anchor;
+Block.Container = Container;
+Block.Toggle = Toggle;
+Block.Body = Body;
+Block.Content = Content;
+Block.ListContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+`;
diff --git a/src/js/components/concept/Block/components/Body.ts b/src/js/components/concept/Block/components/Body.ts
new file mode 100644
index 0000000000..d9c3bc5e5f
--- /dev/null
+++ b/src/js/components/concept/Block/components/Body.ts
@@ -0,0 +1,12 @@
+import styled from 'styled-components';
+
+export const Body = styled.div`
+ display: grid;
+ grid-template-areas: 'above above above above'
+ 'toggle bullet content refs'
+ 'below below below below';
+ grid-template-columns: 1em 1em 1fr auto;
+ grid-template-rows: 0 1fr 0;
+ border-radius: 0.5rem;
+ position: relative;
+`;
diff --git a/src/js/components/concept/Block/components/Container.ts b/src/js/components/concept/Block/components/Container.ts
new file mode 100644
index 0000000000..f35c757fd3
--- /dev/null
+++ b/src/js/components/concept/Block/components/Container.ts
@@ -0,0 +1,93 @@
+import styled from 'styled-components';
+
+export const Container = styled.div`
+ display: flex;
+ line-height: var(--line-height, 1.75em);
+ position: relative;
+ border-radius: 0.125rem;
+ justify-content: flex-start;
+ flex-direction: column;
+ flex: 1 1 100%;
+ color: inherit;
+
+ &.show-tree-indicator:before {
+ content: '';
+ position: absolute;
+ width: 1px;
+ left: calc(1.375em + 1px);
+ top: 2em;
+ bottom: 0;
+ transform: translateX(50%);
+ transition: background-color 0.2s ease-in-out;
+ background: var(--user-color, var(--border-color));
+ }
+
+ &.is-presence.show-tree-indicator:before {
+ opacity: var(--opacity-low);
+ transform: translateX(50%) scaleX(2);
+ }
+
+ &:after {
+ content: '';
+ position: absolute;
+ top: 0.75px;
+ right: 0;
+ bottom: 0.75px;
+ left: 0;
+ opacity: 0;
+ pointer-events: none;
+ border-radius: 0.25rem;
+ transition: opacity 0.075s ease;
+ background: var(--link-color---opacity-lower);
+ }
+
+ &.is-selected:after {
+ opacity: 1;
+ }
+
+ .is-selected &.is-selected {
+ &:after {
+ opacity: 0;
+ }
+ }
+
+ .user-avatar {
+ position: absolute;
+ transition: transform 0.3s ease;
+ left: 4px;
+ top: 4px;
+ }
+
+ .block-edit-toggle {
+ position: absolute;
+ appearance: none;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: none;
+ border: none;
+ cursor: text;
+ display: block;
+ z-index: 1;
+ }
+
+ .block-content {
+ grid-area: content;
+ min-height: 1.5em;
+
+ &:hover + .user-avatar {
+ transform: translateX(-2em);
+ }
+ }
+
+ &.is-linked-ref {
+ background-color: var(--background-plus-2);
+ }
+
+ /* Inset child blocks */
+ & & {
+ margin-left: var(--block-child-inset-margin, 2em);
+ grid-area: body;
+ }
+`;
diff --git a/src/js/components/concept/Block/components/Content.tsx b/src/js/components/concept/Block/components/Content.tsx
new file mode 100644
index 0000000000..cc261125a5
--- /dev/null
+++ b/src/js/components/concept/Block/components/Content.tsx
@@ -0,0 +1,253 @@
+import React from 'react';
+import styled from 'styled-components';
+
+const ContentWrap = styled.div`
+ grid-area: content;
+ display: grid;
+ grid-template-areas: "main";
+ place-items: stretch;
+ place-content: stretch;
+ position: relative;
+ overflow: visible;
+ z-index: 2;
+ flex-grow: 1;
+ word-break: break-word;
+
+ .rendered-content,
+ textarea {
+ grid-area: main;
+ cursor: text;
+ font-size: inherit;
+ font-family: inherit;
+ color: inherit;
+ }
+
+ textarea {
+ color: inherit;
+ font-size: inherit;
+ position: relative;
+ display: block;
+ -webkit-appearance: none;
+ resize: none;
+ transform: translate3d(0,0,0);
+ outline: none;
+ background: transparent;
+ caret-color: var(--link-color);
+ min-height: 100%;
+ padding: 0;
+ margin: 0;
+ border: 0;
+ opacity: 0;
+ }
+
+ &.is-editing,
+ &.show-editable-dom {
+ textarea {
+ z-index: 3;
+ line-height: inherit;
+ opacity: 0;
+ }
+ }
+
+ &.is-editing {
+ textarea {
+ opacity: 1;
+ }
+
+ .rendered-content {
+ opacity: 0;
+ }
+ }
+
+ &:not(.is-editing):hover textarea {
+ line-height: inherit;
+ }
+
+ .is-locked > .block-body > & {
+ opacity: 0.5
+ };
+
+ span.text-run {
+ pointer-events: none;
+
+ > a {
+ position: relative;
+ z-index: 2;
+ pointer-events: auto;
+ }
+
+ }
+
+ span {
+ grid-area: main;
+
+ > span {
+ > a {
+ position: relative;
+ z-index: 2;
+ }
+ }
+ }
+
+ abbr {
+ grid-area: main;
+ z-index: 4;
+
+ > span {
+ > a {
+ position: relative;
+ z-index: 2;
+ }
+ }
+ }
+
+ code, pre {
+ font-family: 'IBM Plex Mono';
+ }
+
+ .media-16-9 {
+ height: 0;
+ width: calc(100% - 0.25rem);
+ z-index: 1;
+ transform-origin: right center;
+ transition: all 0.2s ease;
+ padding-bottom: calc(9 / 16 * 100%);
+ margin-block: 0.25rem;
+ margin-inline-end: 0.25rem;
+ position: relative;
+ }
+
+ iframe {
+ border: 0;
+ box-shadow: inset 0 0 0 0.125rem var(background-minus-1);
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ cursor: default;
+ top: 0;
+ right: 0;
+ left: 0;
+ bottom: 0;
+ border-radius: 0.25rem;
+ }
+
+ img {
+ border-radius: 0.25rem;
+ max-width: calc(100% - 0.25rem);
+ }
+
+ h1, h2, h3, h4, h5, h6 {
+ margin: 0;
+ color: var(--body-text-color---opacity-higher);
+ font-weight: 500;
+ }
+
+ h1 {
+ padding: 0;
+ margin-block-start: "-0.1em";
+ }
+
+ h2, h3 {
+ padding: 0;
+ }
+
+ h4 {
+ padding: 0.25em 0;
+ }
+
+ h5 {
+ padding: 1em 0;
+ }
+
+ h6 {
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ padding: 1em 0;
+ }
+
+ p {
+ margin: 0;
+ padding-bottom: 1em;
+ }
+
+ blockquote {
+ margin-block: 0.125rem;
+ margin-inline: 0.5em;
+ padding-block: calc(0.5em - 0.125rem - 0.125rem);
+ padding-inline: 1.5em;
+ border-radius: 0.25em;
+ background: var(--background-minus-1);
+ color: var(--body-text-color---opacity-high);
+
+ p {
+ padding-bottom: 1em;
+
+ &:last-child {
+ padding-bottom: 0;
+ }
+ }
+ }
+
+ mark.content-visibility.highlight {
+ padding: 0 0.2em;
+ border-radius: 0.125rem;
+ background-color: var(--text-highlight-color);
+ }
+
+`;
+
+export interface ContentProps {
+ /** The raw content of the block */
+ rawContent: string;
+ /** The rendered content of the block */
+ renderedContent?: RenderedContent;
+ /** Whether the block is in editing mode */
+ isEditable?: boolean;
+ /** Whether the block is in editing mode */
+ isEditing?: boolean;
+ /** Whether the block has child blocks */
+ isLocked?: boolean;
+ /** Whether the block should render its editable components or just the static content */
+ showEditableDom?: boolean;
+ /** When raw content of a block is modified. Returns the new value of the raw content. */
+ handleContentChange?: (e: any) => void;
+ /** When the content is clicked or tapped */
+ handlePressContent?: () => void;
+ /** Props on the content container */
+ contentProps: React.HTMLAttributes;
+ /** Props on the editable textarea */
+ textareaProps: React.TextareaHTMLAttributes;
+}
+
+export const Content = ({
+ rawContent,
+ renderedContent,
+ isLocked,
+ isEditable,
+ isEditing,
+ showEditableDom,
+ handleContentChange,
+ contentProps,
+ textareaProps,
+}: ContentProps) => (
+
+ {(isEditing || showEditableDom) && ()
+ }
+ {renderedContent || rawContent}
+
+);
+
+Content.ContentWrap = ContentWrap;
\ No newline at end of file
diff --git a/src/js/components/concept/Block/components/Refs.tsx b/src/js/components/concept/Block/components/Refs.tsx
new file mode 100644
index 0000000000..6dcd43053d
--- /dev/null
+++ b/src/js/components/concept/Block/components/Refs.tsx
@@ -0,0 +1,22 @@
+import styled from 'styled-components';
+import { Button } from '@/Button';
+
+export const RefsCount = styled(Button)`
+ grid-area: refs;
+ margin-left: 1em;
+ padding: 0.25rem;
+ z-index: var(--zindex-dropdown);
+ appearance: none;
+ align-self: flex-start;
+ justify-self: flex-start;
+ font-size: 85%;
+`;
+
+export const Refs = ({ refsCount, ...props }) => (
+
+ {refsCount >= 100 ? "99+" : refsCount}
+
+);
\ No newline at end of file
diff --git a/src/js/components/concept/Block/hooks/useBlockState.tsx b/src/js/components/concept/Block/hooks/useBlockState.tsx
new file mode 100644
index 0000000000..a303ceea23
--- /dev/null
+++ b/src/js/components/concept/Block/hooks/useBlockState.tsx
@@ -0,0 +1,6 @@
+import React from 'react';
+
+export const useBlockState = (blockGraph: BlockGraph): { blockGraph: BlockGraph; setBlockState: React.Dispatch>; } => {
+ const [blockGraphState, setBlockState] = React.useState(blockGraph);
+ return { blockGraph: blockGraphState, setBlockState };
+};
diff --git a/src/js/components/concept/Block/hooks/useChecklist.tsx b/src/js/components/concept/Block/hooks/useChecklist.tsx
new file mode 100644
index 0000000000..2d04b20f49
--- /dev/null
+++ b/src/js/components/concept/Block/hooks/useChecklist.tsx
@@ -0,0 +1,27 @@
+
+import { Checkbox } from '@/concept/Checkbox';
+import { modifyBlocks } from '../utils/modifyBlocks';
+import { toggleBlockProperty } from '../utils/toggleBlockProperty';
+
+export const useChecklist = (blockGraph: BlockGraph, setBlockState) => {
+ const total = Object.keys(blockGraph.blocks).length;
+ const checked = Object.keys(blockGraph.blocks).filter(uid => blockGraph.blocks[uid].isChecked).length;
+
+ const graph = modifyBlocks({
+ blockGraph: blockGraph,
+ ApplyProps: (block) => ({
+ isChecked: block.isChecked,
+ renderedContent:
+ <> toggleBlockProperty(block.uid, 'isChecked', setBlockState)}
+ />
+ {block.renderedContent}
+ >,
+ })
+ });
+
+ return { blockGraph: graph, checked, total };
+};
diff --git a/src/js/components/concept/Block/hooks/usePresence.tsx b/src/js/components/concept/Block/hooks/usePresence.tsx
new file mode 100644
index 0000000000..e5903ae48f
--- /dev/null
+++ b/src/js/components/concept/Block/hooks/usePresence.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import { modifyBlocks } from '../utils/modifyBlocks';
+import { toggleBlockProperty } from '../utils/toggleBlockProperty';
+import { PresenceContext } from './usePresenceProvider';
+
+export const usePresence = (blockGraph: BlockGraph, setBlockState) => {
+ const { presence } = React.useContext(PresenceContext);
+
+ const resultGraph = modifyBlocks({
+ blockGraph: blockGraph,
+ ApplyProps: (block) => ({
+ ...block,
+ presentUser: presence.find((p: PersonPresence) => p.uid === block.uid),
+ handlePressToggle: (uid: UID) => toggleBlockProperty(uid, 'isOpen', setBlockState),
+ }),
+ });
+
+ return { blockGraph: resultGraph, setBlockState };
+};
diff --git a/src/js/components/concept/Block/hooks/usePresenceProvider.tsx b/src/js/components/concept/Block/hooks/usePresenceProvider.tsx
new file mode 100644
index 0000000000..320563a5ef
--- /dev/null
+++ b/src/js/components/concept/Block/hooks/usePresenceProvider.tsx
@@ -0,0 +1,44 @@
+import React from 'react';
+// import { mockPeople } from '../../../Avatar/mockData';
+
+export const PresenceContext = React.createContext(null);
+
+interface usePresenceProviderProps {
+ presentPeople: PersonPresence[];
+}
+
+export const usePresenceProvider = ({ presentPeople }: usePresenceProviderProps) => {
+ const [presence, setPresence] = React.useState(presentPeople);
+
+ // const randomPerson = () => mockPeople[Math.floor(Math.random() * mockPeople.length)];
+ const numberOfBlocks = 6
+
+ const clearPresence = () => setPresence([]);
+ const fillPresence = () => setPresence(presence.slice(0, numberOfBlocks + 1));
+ const removePresence = () => setPresence(presence.slice(0, presence.length - 1));
+ // const addPresence = () => setPresence([...presence, { ...randomPerson(), uid: Math.ceil(Math.random() * numberOfBlocks).toString() }]);
+
+ const context = {
+ presence,
+ clearPresence,
+ fillPresence,
+ removePresence,
+ // addPresence,
+ };
+
+ const PresenceProvider = ({ children }) =>
+
+ {children}
+ ;
+
+ return {
+ PresenceProvider: PresenceProvider,
+ PresenceContext: PresenceContext,
+ presence,
+ setPresence,
+ clearPresence,
+ removePresence,
+ // addPresence
+ }
+}
+
diff --git a/src/js/components/concept/Block/hooks/useSelection.tsx b/src/js/components/concept/Block/hooks/useSelection.tsx
new file mode 100644
index 0000000000..ae5799dcaa
--- /dev/null
+++ b/src/js/components/concept/Block/hooks/useSelection.tsx
@@ -0,0 +1,15 @@
+import { modifyBlocks } from '../utils/modifyBlocks';
+import { toggleBlockProperty } from '../utils/toggleBlockProperty';
+
+export const useSelection = (blockGraph: BlockGraph, setBlockState, defaultSelected = undefined) => {
+
+ const graph = modifyBlocks({
+ blockGraph: blockGraph,
+ ApplyProps: (block) => ({
+ isSelected: block.isSelected ? block.isSelected : defaultSelected,
+ handlePressContainer: () => toggleBlockProperty(block.uid, 'isSelected', setBlockState),
+ }),
+ });
+
+ return { blockGraph: graph };
+};
diff --git a/src/js/components/concept/Block/hooks/useToggle.tsx b/src/js/components/concept/Block/hooks/useToggle.tsx
new file mode 100644
index 0000000000..eb28368c45
--- /dev/null
+++ b/src/js/components/concept/Block/hooks/useToggle.tsx
@@ -0,0 +1,14 @@
+import { modifyBlocks } from '../utils/modifyBlocks';
+import { toggleBlockProperty } from '../utils/toggleBlockProperty';
+
+export const useToggle = (blockGraph: BlockGraph, setBlockState) => {
+ const graph = modifyBlocks({
+ blockGraph: blockGraph,
+ ApplyProps: (block) => ({
+ isOpen: block.isOpen,
+ handlePressToggle: (uid: UID) => toggleBlockProperty(uid, 'isOpen', setBlockState),
+ }),
+ });
+
+ return { blockGraph: graph };
+};
diff --git a/src/js/components/concept/Block/index.ts b/src/js/components/concept/Block/index.ts
new file mode 100644
index 0000000000..deb1cd5d1a
--- /dev/null
+++ b/src/js/components/concept/Block/index.ts
@@ -0,0 +1,2 @@
+import { Block } from './Block';
+export { Block };
\ No newline at end of file
diff --git a/src/js/components/concept/Block/mockData.tsx b/src/js/components/concept/Block/mockData.tsx
new file mode 100644
index 0000000000..5ef6f9dd6e
--- /dev/null
+++ b/src/js/components/concept/Block/mockData.tsx
@@ -0,0 +1,59 @@
+export const blockTree = {
+ tree: [
+ {
+ uid: "1",
+ children: [
+ {
+ uid: "2",
+ children: [{
+ uid: "3",
+ }],
+ },
+ {
+ uid: "4",
+ children: [{
+ uid: "5",
+ }],
+ },
+ ],
+ }, { uid: "6" }
+ ],
+ blocks: {
+ "1": {
+ uid: "1",
+ isOpen: true,
+ rawContent: "**Lorem** ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
+ renderedContent: <>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.>,
+ },
+ "2": {
+ uid: "2",
+ isOpen: true,
+ rawContent: "**Donec** id elit non mi porta gravida at eget metus. Maecenas sed diam eget risus varius blandit sit amet non magna. Cras justo odio, dapibus ac facilisis in, egestas eget quam.",
+ renderedContent: <>Donec id elit non mi porta gravida at eget metus. Maecenas sed diam eget risus varius blandit sit amet non magna. Cras justo odio, dapibus ac facilisis in, egestas eget quam.>,
+ },
+ "3": {
+ uid: "3",
+ isOpen: true,
+ rawContent: "**Consectetur** adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
+ renderedContent: <>Consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.>
+ },
+ "4": {
+ uid: "4",
+ isOpen: true,
+ rawContent: "**Donec** id elit non mi porta gravida at eget metus. Maecenas sed diam eget risus varius blandit sit amet non magna. Cras justo odio, dapibus ac facilisis in, egestas eget quam.",
+ renderedContent: <>Donec id elit non mi porta gravida at eget metus. Maecenas sed diam eget risus varius blandit sit amet non magna. Cras justo odio, dapibus ac facilisis in, egestas eget quam.>,
+ },
+ "5": {
+ uid: "5",
+ isOpen: true,
+ rawContent: "**Consectetur** adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
+ renderedContent: <>Consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.>
+ },
+ "6": {
+ uid: "6",
+ isOpen: true,
+ rawContent: "**Consectetur** adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
+ renderedContent: <>Consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.>
+ }
+ }
+};
diff --git a/src/js/components/concept/Block/utils/modifyBlocks.ts b/src/js/components/concept/Block/utils/modifyBlocks.ts
new file mode 100644
index 0000000000..50eea39345
--- /dev/null
+++ b/src/js/components/concept/Block/utils/modifyBlocks.ts
@@ -0,0 +1,21 @@
+import { BlockProps } from '../Block';
+
+interface modifyBlocksProps {
+ blockGraph: BlockGraph;
+ ApplyProps?(block: BlockProps): any;
+}
+
+export const modifyBlocks = ({
+ blockGraph,
+ ApplyProps = () => null,
+}: modifyBlocksProps): BlockGraph => ({
+ tree: blockGraph.tree,
+ blocks: {
+ ...Object.keys(blockGraph.blocks).map((uid) => {
+ return ({
+ ...blockGraph.blocks[uid],
+ ...ApplyProps(blockGraph.blocks[uid]),
+ })
+ })
+ }
+})
diff --git a/src/js/components/concept/Block/utils/renderBlocks.ts b/src/js/components/concept/Block/utils/renderBlocks.ts
new file mode 100644
index 0000000000..62d26fe23f
--- /dev/null
+++ b/src/js/components/concept/Block/utils/renderBlocks.ts
@@ -0,0 +1,92 @@
+import React from 'react';
+import { BlockProps } from '../Block';
+
+interface renderBlocksProps {
+ blockGraph: BlockGraph;
+ setBlockState?: Function;
+ ApplyProps?(block: BlockProps): BlockProps;
+ blockComponent?: any;
+ lengthLimit?: number;
+ depthLimit?: number;
+ incCurrentLength?(): void;
+ incCurrentDepth?(): void;
+ getCurrentLength?(): number;
+ getCurrentDepth?(): number;
+}
+
+export const renderBlocks = ({
+ blockGraph,
+ setBlockState,
+ ApplyProps = () => null,
+ blockComponent,
+ lengthLimit = Infinity,
+ depthLimit = Infinity,
+}: renderBlocksProps) => {
+ let currentLength = 0;
+ let currentDepth = 0;
+ const incCurrentLength = () => currentLength++;
+ const incCurrentDepth = () => currentDepth++;
+ const getCurrentLength = () => currentLength;
+ const getCurrentDepth = () => currentDepth;
+
+ return renderBlocksFn({
+ blockGraph,
+ setBlockState,
+ ApplyProps,
+ blockComponent,
+ lengthLimit,
+ depthLimit,
+ incCurrentLength,
+ incCurrentDepth,
+ getCurrentLength,
+ getCurrentDepth,
+ });
+}
+
+const renderBlocksFn = ({
+ blockGraph,
+ setBlockState,
+ ApplyProps,
+ blockComponent,
+ lengthLimit,
+ depthLimit,
+ incCurrentLength,
+ incCurrentDepth,
+ getCurrentLength,
+ getCurrentDepth,
+}: renderBlocksProps) => {
+ return blockGraph.tree.map((block) => {
+ incCurrentLength();
+
+ if (lengthLimit >= getCurrentLength()) {
+ incCurrentDepth();
+
+ return (
+ React.cloneElement(blockComponent, {
+ key: block.uid,
+ uid: block.uid,
+ ...blockGraph.blocks[block.uid],
+ ...ApplyProps(block),
+ ApplyProps: ApplyProps,
+ children: block.children && (lengthLimit >= getCurrentLength() && depthLimit >= getCurrentDepth()) && renderBlocksFn({
+ blockGraph: {
+ tree: block.children,
+ blocks: blockGraph.blocks
+ },
+ setBlockState: setBlockState,
+ ApplyProps: ApplyProps,
+ blockComponent: blockComponent,
+ lengthLimit: lengthLimit,
+ depthLimit: depthLimit,
+ incCurrentLength,
+ incCurrentDepth,
+ getCurrentLength,
+ getCurrentDepth,
+ }),
+ },
+ ))
+ } else {
+ return
+ }
+ });
+};
diff --git a/src/js/components/concept/Block/utils/toggleBlockProperty.ts b/src/js/components/concept/Block/utils/toggleBlockProperty.ts
new file mode 100644
index 0000000000..ea5cbb450f
--- /dev/null
+++ b/src/js/components/concept/Block/utils/toggleBlockProperty.ts
@@ -0,0 +1,15 @@
+
+export const toggleBlockProperty = (uid, property, setBlockState) => {
+ setBlockState(prevState => {
+ return ({
+ ...prevState,
+ blocks: {
+ ...prevState.blocks,
+ [uid]: {
+ ...prevState.blocks[uid],
+ [property]: !prevState.blocks[uid][property]
+ }
+ }
+ })
+ })
+}
\ No newline at end of file
diff --git a/src/js/components/concept/Breadcrumbs/Breadcrumbs.stories.tsx b/src/js/components/concept/Breadcrumbs/Breadcrumbs.stories.tsx
new file mode 100644
index 0000000000..3d124d50e1
--- /dev/null
+++ b/src/js/components/concept/Breadcrumbs/Breadcrumbs.stories.tsx
@@ -0,0 +1,53 @@
+import React from 'react';
+import { BADGE, Storybook } from '@/utils/storybook';
+import { Breadcrumbs } from './Breadcrumbs';
+
+export default {
+ title: 'Concepts/Breadcrumbs',
+ component: Breadcrumbs,
+ argTypes: {},
+ decorators: [(Story) => {Story()} ],
+ parameters: {
+ badges: [BADGE.DEV]
+ }
+};
+
+export const Basic = () => {
+ return (
+
+ Item 1
+ Item 2
+ Item 3
+
+ );
+};
+
+export const SingleItem = () => {
+ return (
+
+ Item
+
+ );
+};
+
+export const LotsOfItems = () => {
+ const items = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.".split(' ');
+
+ return (
+
+ {items.map((item, index) => (
+ {item}
+ ))}
+
+ );
+};
+
+export const Children = () => {
+ return (
+
+ Heading
+ Heading
+ Heading
+
+ );
+};
\ No newline at end of file
diff --git a/src/js/components/concept/Breadcrumbs/Breadcrumbs.tsx b/src/js/components/concept/Breadcrumbs/Breadcrumbs.tsx
new file mode 100644
index 0000000000..098c444727
--- /dev/null
+++ b/src/js/components/concept/Breadcrumbs/Breadcrumbs.tsx
@@ -0,0 +1,65 @@
+import React from 'react';
+import styled from 'styled-components';
+
+const BreadcrumbsWrap = styled.ol`
+ display: flex;
+ align-items: center;
+ gap: 0.1rem;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+
+ &:hover {
+ a {
+ opacity: 0.7;
+
+ &:hover {
+ opacity: 1;
+ }
+ }
+ }
+`;
+
+const ItemWrap = styled.a`
+ text-decoration: none;
+ color: inherit;
+ transition: opacity 0.2s ease-in-out;
+
+ * {
+ pointer-events: none;
+ }
+`;
+
+const SeparatorSVG = styled.svg`
+ width: 1em;
+ height: 1em;
+ color: var(--body-text-color---opacity-med);
+ flex: 0 0 auto;
+`;
+
+const Separator = () =>
+
+
+
+interface BreadcrumbsProps extends React.OlHTMLAttributes {
+
+}
+/**
+ * Show a list of breadcrumbs indicating an item's larger context.
+ */
+export const Breadcrumbs = ({ children, ...props }: BreadcrumbsProps) => {
+
+ return (
+
+ {typeof children === 'string'
+ ? {children}
+ : React.Children.map(children, (item, index) => (<>
+ {index !== 0 && ( )}
+ {React.cloneElement(item, {
+ key: index.toString() + item,
+ })}
+ >))}
+
+ );
+};
+Breadcrumbs.Item = ItemWrap;
\ No newline at end of file
diff --git a/src/js/components/concept/Breadcrumbs/index.ts b/src/js/components/concept/Breadcrumbs/index.ts
new file mode 100644
index 0000000000..dc165fc6a8
--- /dev/null
+++ b/src/js/components/concept/Breadcrumbs/index.ts
@@ -0,0 +1,2 @@
+import { Breadcrumbs } from './Breadcrumbs';
+export { Breadcrumbs };
\ No newline at end of file
diff --git a/src/js/components/concept/Checkbox/Checkbox.stories.tsx b/src/js/components/concept/Checkbox/Checkbox.stories.tsx
new file mode 100644
index 0000000000..aaaca32c2c
--- /dev/null
+++ b/src/js/components/concept/Checkbox/Checkbox.stories.tsx
@@ -0,0 +1,46 @@
+import React from 'react';
+import { BADGE, Storybook } from '@/utils/storybook';
+
+import { Checkbox } from './Checkbox';
+
+import { Meter } from '@/concept/Meter';
+
+export default {
+ title: 'Concepts/Checkbox',
+ component: Checkbox,
+ argTypes: {},
+ parameters: {
+ layout: 'centered',
+ badges: [BADGE.DEV]
+ },
+ decorators: [(Story) => ]
+};
+
+export const Basic = () => Build Athens ;
+export const Indeterminate = () => Build Athens ;
+export const DefaultSelected = () => Build Athens ;
+export const Emphasized = () => Build Athens ;
+export const Disabled = () => Build Athens ;
+export const Circle = () => Build Athens ;
+export const StrikethroughWhenChecked = () => Build Athens ;
+
+export const Checklist = () => {
+ const [checked1, setChecked1] = React.useState(false);
+ const [checked2, setChecked2] = React.useState(false);
+ const [checked3, setChecked3] = React.useState(false);
+ const [numChecked, setNumChecked] = React.useState(0)
+
+ React.useEffect(() => {
+ setNumChecked([checked1, checked2, checked3].filter(Boolean).length);
+ }, [checked1, checked2, checked3])
+
+ return (<>
+
+
+ Read How To Take Good Notes
+ Build Athens
+ Take a nap
+
+ >
+ )
+};
\ No newline at end of file
diff --git a/src/js/components/concept/Checkbox/Checkbox.tsx b/src/js/components/concept/Checkbox/Checkbox.tsx
new file mode 100644
index 0000000000..2cf28417cb
--- /dev/null
+++ b/src/js/components/concept/Checkbox/Checkbox.tsx
@@ -0,0 +1,183 @@
+import React from 'react';
+import styled, { keyframes } from 'styled-components';
+import { classnames } from '@/utils/classnames';
+
+import { useCheckbox } from '@react-aria/checkbox'
+import { useToggleState } from '@react-stately/toggle';
+
+const HiddenInput = styled.input`
+ visibility: hidden;
+ position: absolute;
+`;
+
+const CheckboxWrap = styled.svg`
+ height: var(--size, 1em);
+ width: var(--size, 1em);
+ vertical-align: calc(var(--size, 1em) * -0.125);
+ transition: transform 0.2s ease-in-out;
+`;
+
+const Label = styled.label`
+ &:hover {
+ color: var(--body-text-color---opacity-high);
+ }
+
+ &:active {
+ user-select: none;
+ color: var(--body-text-color---opacity-med);
+
+ ${CheckboxWrap} {
+ transition: transform 0.05s ease-in-out;
+ transform: scale(1.1);
+ }
+ }
+
+ &.is-disabled {
+ color: var(--body-text-color---opacity-high);
+ }
+
+ &.has-children {
+ ${CheckboxWrap} {
+ margin-inline-end: 0.25em;
+ }
+
+ &.is-checked {
+ color: var(--body-text-color---opacity-med);
+
+ &.should-strikethrough-when-checked {
+ text-decoration: line-through;
+ }
+ }
+ }
+`;
+
+const MarkAppear = keyframes`
+ from {
+ opacity: 0;
+ transform: scale(0.5);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+`;
+
+const Field = styled.rect`
+ stroke: var(--field-color, var(--link-color));
+ stroke-width: var(--stroke-width, 8%);
+ x: 4%;
+ y: 4%;
+ width: calc(100% - 8%);
+ height: calc(100% - 8%);
+ rx: var(--rx, 12%);
+ fill: transparent;
+ transition: all 0.2s ease-in-out;
+
+ .is-emphasized & {
+ --field-color: var(--confirmation-color);
+ }
+
+ .is-indeterminate &,
+ .is-checked &{
+ x: 0;
+ y: 0;
+ width: 100%;
+ height: 100%;
+ rx: var(--rx, 14%);
+ stroke-width: 0;
+ fill: var(--field-color, var(--link-color));
+ }
+
+ .is-disabled & {
+ stroke: none;
+ fill: var(--body-text-color---opacity-low);
+ x: 0;
+ y: 0;
+ width: 100%;
+ height: 100%;
+ }
+
+ .style-circle & {
+ rx: 100%;
+
+ .is-indeterminate &,
+ .is-checked &{
+ rx: 100%;
+ }
+ }
+
+`;
+
+const Mark = styled.path`
+ stroke: var(--mark-color, #fff);
+ stroke-width: 3;
+ stroke-linecap: round;
+ fill: none;
+ transform-origin: 50% 50%;
+ animation: ${MarkAppear} 0.2s ease-in-out;
+`;
+
+
+interface CheckboxProps {
+ onChange?: (isSelected: boolean) => void;
+ isReadOnly?: boolean;
+ defaultSelected?: boolean;
+ isSelected?: boolean;
+ isIndeterminate?: boolean;
+ isEmphasized?: boolean;
+ isDisabled?: boolean;
+ children?: React.ReactNode;
+ styleCircle?: React.ReactNode;
+ shouldStrikethroughWhenChecked?: boolean;
+ labelProps?: React.LabelHTMLAttributes;
+}
+
+const Checkbox = (props: CheckboxProps) => {
+ let {
+ isIndeterminate = false,
+ isEmphasized = false,
+ isDisabled = false,
+ shouldStrikethroughWhenChecked = false,
+ children,
+ labelProps,
+ } = props;
+ let inputRef = React.useRef(null);
+ let { inputProps } = useCheckbox(props, useToggleState(props), inputRef);
+
+ return (
+
+
+
+
+ {isIndeterminate ? (
+
+ ) : (
+ inputProps.checked && (
+
+ )
+ )}
+
+ {children}
+
+ );
+}
+
+let _Checkbox = React.forwardRef(Checkbox);
+export { _Checkbox as Checkbox };
+
+Checkbox.Label = Label;
+Checkbox.CheckboxWrap = Checkbox;
\ No newline at end of file
diff --git a/src/js/components/concept/Checkbox/index.ts b/src/js/components/concept/Checkbox/index.ts
new file mode 100644
index 0000000000..9947fbc8e4
--- /dev/null
+++ b/src/js/components/concept/Checkbox/index.ts
@@ -0,0 +1,2 @@
+import { Checkbox } from './Checkbox';
+export { Checkbox };
\ No newline at end of file
diff --git a/src/js/components/concept/CommandBar/CommandBar.stories.tsx b/src/js/components/concept/CommandBar/CommandBar.stories.tsx
new file mode 100644
index 0000000000..635e69f4cd
--- /dev/null
+++ b/src/js/components/concept/CommandBar/CommandBar.stories.tsx
@@ -0,0 +1,48 @@
+import { CommandBar } from './CommandBar';
+import { BADGE } from '@/utils/storybook';
+
+export default {
+ title: 'Concepts/CommandBar',
+ component: CommandBar,
+ argTypes: {},
+ parameters: {
+ badges: [BADGE.DEV]
+ },
+};
+
+const Template = (args) => ;
+
+export const Empty = Template.bind({});
+Empty.args = {
+ isOpen: true,
+};
+
+export const FilledWithNoResults = Template.bind({});
+FilledWithNoResults.args = {
+ defaultQuery: 'animal',
+};
+
+export const ManyResults = Template.bind({});
+ManyResults.args = {
+ defaultQuery: 'animal',
+ results: [{
+ title: "Chital",
+ isSelected: true,
+ preview: "Pellentesque eget nunc. Donec quis orci eget orci vehicula condimentum. Curabitur in libero ut massa volutpat convallis. Morbi odio odio, elementum eu, interdum eu, tincidunt in, leo. Maecenas pulvinar lobortis est. Phasellus sit amet erat. Nulla tempus."
+ }, {
+ title: "Fox, north american red",
+ preview: "Suspendisse potenti. Nullam porttitor lacus at turpis. Donec posuere metus vitae ipsum. Aliquam non mauris. Morbi non lectus. Aliquam sit amet diam in magna bibendum imperdiet. Nullam orci pede, venenatis non, sodales sed, tincidunt eu, felis. Fusce posuere felis sed lacus. Morbi sem mauris, laoreet ut, rhoncus aliquet, pulvinar sed, nisl."
+ }, {
+ title: "Hawk, red-tailed",
+ preview: "Phasellus in felis. Donec semper sapien a libero. Nam dui."
+ }, {
+ title: "Long-tailed skua",
+ preview: "Cras pellentesque volutpat dui."
+ }, {
+ title: "Wolf spider",
+ preview: "Vivamus in felis eu sapien cursus vestibulum. Proin eu mi. Nulla ac enim."
+ }, {
+ title: "Amazon parrot (unidentified)",
+ preview: "Vivamus tortor. Duis mattis egestas metus. Aenean fermentum. Donec ut mauris eget massa tempor convallis. Nulla neque libero, convallis eget, eleifend luctus, ultricies eu, nibh. Quisque id justo sit amet sapien dignissim vestibulum."
+ }]
+};
diff --git a/src/js/components/concept/CommandBar/CommandBar.tsx b/src/js/components/concept/CommandBar/CommandBar.tsx
new file mode 100644
index 0000000000..710838f71e
--- /dev/null
+++ b/src/js/components/concept/CommandBar/CommandBar.tsx
@@ -0,0 +1,230 @@
+import React from 'react';
+import styled from 'styled-components';
+
+import { DOMRoot } from '@/utils/config';
+
+import { Close } from '@material-ui/icons';
+import { Modal } from '@material-ui/core';
+import { Result } from './components/Result';
+
+const Container = styled.div`
+ width: 49rem;
+ display: flex;
+ background: var(--background-plus-1);
+ overflow: hidden;
+ box-shadow: var(--depth-shadow-64), 0 0 0 1px var(--shadow-color---opacity-lower);
+ max-width: calc(100vw - 1rem);
+ border-radius: 0.25rem;
+ flex-direction: column;
+ max-height: 60vh;
+ margin: 4rem auto;
+ position: relative;
+`;
+
+const Input = styled.input`
+ background: var(--background-plus-2);
+ width: 100%;
+ border: 0;
+ padding: 1.5rem 4rem 1.5rem 1.5rem;
+ font-size: 2.375rem;
+ font-weight: 300;
+ line-height: 1.3;
+ letter-spacing: -0.03em;
+ border-radius: 0.25rem 0.25rem 0 0;
+ color: var(--body-text-color);
+ caret-color: var(--link-color);
+ cursor: text;
+ font-family: inherit;
+
+ &:focus {
+ outline: none;
+ }
+
+ &::placeholder {
+ color: var(--body-text-color---opacity-low);
+ }
+
+ &::-webkit-search-cancel-button {
+ display: none;
+ }
+`;
+
+const Header = styled.header`
+ position: relative;
+`;
+
+const SearchCancelButton = styled.button`
+ background: none;
+ color: inherit;
+ position: absolute;
+ transition: opacity 0.1s ease, background 0.1s ease;
+ cursor: pointer;
+ border: 0;
+ right: 1rem;
+ place-items: center;
+ place-content: center;
+ height: 2.5rem;
+ width: 2.5rem;
+ border-radius: 1000px;
+ display: flex;
+ transform: translate(0%, -50%);
+ top: 50%;
+
+ &:hover,
+ &:focus {
+ background: var(--background-plus-1);
+ }
+`;
+
+const ResultsList = styled.ol`
+ background: var(--background-color);
+ overflow-y: auto;
+ max-height: 100%;
+ padding: 0;
+ list-style: none;
+ margin: 0;
+`;
+
+const ResultsHeading = styled.header`
+ padding: 0.25rem 1.5rem;
+ background: var(--background-plus-2);
+ display: flex;
+ position: sticky;
+ z-index: 100;
+ margin: 0;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ align-items: center;
+ top: 0;
+ justify-content: space-between;
+ box-shadow: 0 1px 0 0 var(--border-color);
+ border-top: 1px solid var(--border-color);
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.03em;
+ font-size: 0.75em;
+`;
+
+const Hint = styled.span`
+ color: inherit;
+ letter-spacing: auto;
+ font-size: unset;
+ text-transform: none;
+ opacity: var(--opacity-med);
+ font-weight: 500;
+
+ kbd {
+ font-family: inherit;
+ background: var(--background-color);
+ border-radius: 0.25rem;
+ color: var(--body-text-color);
+ font-size: 85%;
+ font-weight: 600;
+ padding: 0.125rem 0.5rem;
+ text-transform: uppercase;
+ letter-spacing: 0.03em;
+ }
+`;
+
+export interface CommandBarProps {
+ /**
+ * Items returned based on the search query
+ */
+ results?: Result[];
+ /**
+ * Prefill the search input with a query
+ */
+ defaultQuery?: string;
+ /**
+ * Whether the search input field should be autofocused
+ */
+ autoFocus?: true;
+ /**
+ * Whether to render the command bar portaled modal.
+ * If false, the command bar will be rendered in place.
+ */
+ isModal?: boolean;
+ /**
+ * Element to attach Portal to
+ **/
+ container?: Element;
+ handleChooseResult?(result: Result): void;
+ handleQueryChange?(query: string): void;
+ handlePressQueryClear?(): void;
+ handleCloseCommandBar?(): void;
+}
+
+/**
+ * Finds and creates pages. Command bar.
+ */
+export const CommandBar = ({
+ results,
+ defaultQuery,
+ autoFocus,
+ isModal = true,
+ container,
+ handleChooseResult,
+ handlePressQueryClear,
+ handleQueryChange,
+ handleCloseCommandBar,
+}: CommandBarProps) => {
+ const [query, setQuery] = React.useState(defaultQuery);
+
+ const content = (
+
+
+ {query && (
+
+
+ {results ? results.length : 0} Results
+ Press shift + enter to open in sidebar
+
+ {results ? results.map((result) => (
+
+ )) : (
+ Create Page: {query}>}
+ handleChooseResult={handleChooseResult}
+ isSelected={true}
+ action="create"
+ />
+ )}
+
+ )}
+
+ )
+
+ return isModal ? (
+
+ {content}
+
+ ) : (content)
+
+}
+
diff --git a/src/js/components/concept/CommandBar/components/Result.tsx b/src/js/components/concept/CommandBar/components/Result.tsx
new file mode 100644
index 0000000000..9635c035f1
--- /dev/null
+++ b/src/js/components/concept/CommandBar/components/Result.tsx
@@ -0,0 +1,89 @@
+import React, { ReactNode } from 'react';
+import styled from 'styled-components';
+import { Create, ArrowForward } from '@material-ui/icons';
+
+const ResultWrap = styled.li`
+ display: flex;
+ padding: 0.75rem 1.5rem;
+ background: var(--background-plus-1);
+ color: var(--body-text-color);
+ cursor: default;
+ gap: 0.5rem;
+ transition: background 0.05s ease, color 0.05s ease;
+
+ .title {
+ font-size: 1rem;
+ margin: 0;
+ font-weight: 500;
+ flex: 0 0 auto;
+ }
+
+ .preview {
+ white-space: nowrap;
+ opacity: var(--opacity-med);
+ margin: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ flex: 1 1 100%;
+ line-clamp: 1;
+ display: inline-flex;
+ }
+
+ &.is-selected,
+ &:hover {
+ background: var(--link-color);
+ color: #fff;
+ }
+`;
+
+const LinkLeader = styled.span`
+ margin-left: auto;
+ height: 1em;
+ opacity: 0;
+
+ .is-selected > &,
+ li:hover > & {
+ opacity: 1;
+ }
+`;
+
+export interface Result {
+ /**
+ * The title of the result.
+ */
+ title: React.ReactNode;
+ /**
+ * Truncated sample of the result's main text
+ */
+ preview?: React.ReactNode;
+ /**
+ * Whether the result is selected in the list of results
+ */
+ isSelected: boolean,
+ /**
+ * The type of action to take when choosing the result
+ */
+ action: 'create' | 'open'
+ handleChooseResult: (result: Result) => void;
+}
+
+/**
+ * The result of a search.
+*/
+export const Result = ({
+ title,
+ preview,
+ isSelected,
+ handleChooseResult,
+ action
+}: Result) =>
+
+ {title}
+ {preview && {preview}
}
+
+ {action === 'create' ? : }
+
+ ;
diff --git a/src/js/components/concept/CommandBar/index.ts b/src/js/components/concept/CommandBar/index.ts
new file mode 100644
index 0000000000..d7c6147ebd
--- /dev/null
+++ b/src/js/components/concept/CommandBar/index.ts
@@ -0,0 +1,2 @@
+import { CommandBar } from './CommandBar';
+export { CommandBar };
\ No newline at end of file
diff --git a/src/js/components/concept/DailyNotes/DailyNotes.stories.tsx b/src/js/components/concept/DailyNotes/DailyNotes.stories.tsx
new file mode 100644
index 0000000000..5c53f485cd
--- /dev/null
+++ b/src/js/components/concept/DailyNotes/DailyNotes.stories.tsx
@@ -0,0 +1,28 @@
+import { DailyNotes } from './DailyNotes';
+import { DailyNotes as DailyNotesSidebarCalendar } from './SidebarCalendar/DailyNotes';
+import { BADGE, Storybook } from '@/utils/storybook';
+
+export default {
+ title: 'Routes/DailyNotes',
+ component: DailyNotes,
+ argTypes: {},
+ parameters: {
+ badges: [BADGE.DEV]
+ },
+};
+
+const Template = (args) => ;
+
+export const Basic = Template.bind({});
+Basic.args = {
+ children: 'DailyNotes',
+};
+
+export const SidebarCalendar = () => {
+ return ;
+};
+SidebarCalendar.story = {
+ parameters: {
+ badges: [BADGE.CONCEPT]
+ },
+};
\ No newline at end of file
diff --git a/src/js/components/concept/DailyNotes/DailyNotes.tsx b/src/js/components/concept/DailyNotes/DailyNotes.tsx
new file mode 100644
index 0000000000..d838a1408d
--- /dev/null
+++ b/src/js/components/concept/DailyNotes/DailyNotes.tsx
@@ -0,0 +1,51 @@
+import styled from 'styled-components';
+import { DateTime } from 'luxon';
+import { Page } from '@/concept/Page';
+import { BlockTree } from '@/concept/Block/Block.stories';
+
+const dateFormat = { month: 'long', day: 'numeric' };
+
+const DailyNotesWrap = styled.div`
+ min-height: calc(100vh + 1px);
+ display: flex;
+ padding: 1.25rem 0;
+ align-items: 0;
+ flex: 1 1 100%;
+ flex-direction: column;
+`;
+
+const DailyNotesPageWrap = styled.div`
+ box-shadow: var(--depth-shadow-16);
+ align-self: stretch;
+ justify-self: stretch;
+ margin: 1.25rem 2.5rem;
+ padding: 1rem 2rem;
+ transition-duration: 0s;
+ border-radius: 0.5rem;
+ min-height: calc(100vh - 10rem);
+`;
+
+/**
+ * Current daily note, list of daily notes
+ */
+export const DailyNotes = ({ }) => {
+
+ const today = DateTime.now();
+
+ return (
+
+
+ null}
+ handlePressAddShortcut={() => null}
+ handlePressShowLocalGraph={() => null}
+ handlePressDelete={() => null}
+ children={ }
+ />
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/js/components/concept/DailyNotes/Next/DailyNotes.tsx b/src/js/components/concept/DailyNotes/Next/DailyNotes.tsx
new file mode 100644
index 0000000000..8ada0cf685
--- /dev/null
+++ b/src/js/components/concept/DailyNotes/Next/DailyNotes.tsx
@@ -0,0 +1,108 @@
+import React from 'react';
+import styled from 'styled-components';
+import { classnames } from '@/utils/classnames';
+import { preferredDateFormat } from '@/utils/config';
+import { DateTime, toISODate } from 'luxon';
+import { Page } from '@/concept/Page';
+import { BlockTree } from '@/concept/Block/Block.stories';
+import { useInView } from 'react-intersection-observer';
+import DayPicker from 'react-day-picker';
+import 'react-day-picker/lib/style.css';
+
+const DailyNotesWrap = styled.div`
+ display: flex;
+`;
+
+const DailyNotesPageWrap = styled.div`
+ box-shadow: var(--depth-shadow-8);
+ align-self: stretch;
+ justify-self: stretch;
+ margin: 1.25rem 2.5rem;
+ padding: 1rem 2rem;
+ transition-duration: 0s;
+ border-radius: 0.5rem;
+
+ &.is-today {
+ background: var(--background-plus-1)
+ }
+`;
+
+const Sidebar = styled.div`
+ flex: 0 0 200px;
+`;
+
+const Pages = styled.div`
+ min-height: calc(100vh + 1px);
+ display: flex;
+ padding: 1.25rem 0;
+ align-items: 0;
+ flex: 1 1 100%;
+ flex-direction: column;
+`;
+
+/**
+ * Current daily note, list of daily notes
+ */
+
+
+const DailyNotesPage = ({
+ date,
+ isToday,
+ hasContent
+}) => {
+ const [isLinkedReferencesOpen, setIsLinkedReferencesOpen] = React.useState(true);
+ const [isUnlinkedReferencesOpen, setIsUnlinkedReferencesOpen] = React.useState(true);
+ const handlePressLinkedReferencesToggle = () => setIsLinkedReferencesOpen(!isLinkedReferencesOpen);
+ const handlePressUnlinkedReferencesToggle = () => setIsUnlinkedReferencesOpen(!isUnlinkedReferencesOpen);
+
+ const { ref, inView, entry } = useInView({
+ threshold: 0,
+ });
+
+ return (
+
+ null}
+ handlePressAddShortcut={() => null}
+ handlePressShowLocalGraph={() => null}
+ handlePressDelete={() => null}
+ handlePressLinkedReferencesToggle={handlePressLinkedReferencesToggle}
+ handlePressUnlinkedReferencesToggle={handlePressUnlinkedReferencesToggle}
+ isLinkedReferencesOpen={isLinkedReferencesOpen}
+ isUnlinkedReferencesOpen={isUnlinkedReferencesOpen}
+ children={{hasContent ? : <>>} }
+ />
+
+ );
+};
+
+const onDayClick = (day, modifiers, dayPickerInput) => { }
+
+export const DailyNotes = ({ }) => {
+ const [currentPage, setCurrentPage] = React.useState('today');
+
+ const modifiers = {
+ highlighted: new Date(2021, 9, 19),
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/js/components/concept/DailyNotes/SidebarCalendar/DailyNotes.tsx b/src/js/components/concept/DailyNotes/SidebarCalendar/DailyNotes.tsx
new file mode 100644
index 0000000000..af1be6c5dd
--- /dev/null
+++ b/src/js/components/concept/DailyNotes/SidebarCalendar/DailyNotes.tsx
@@ -0,0 +1,108 @@
+import React from 'react';
+import styled from 'styled-components';
+import { classnames } from '@/utils/classnames';
+import { preferredDateFormat } from '@/utils/config';
+import { DateTime } from 'luxon';
+import { Page } from '@/concept/Page';
+import { BlockTree } from '@/concept/Block/Block.stories';
+import { useInView } from 'react-intersection-observer';
+import DayPicker from 'react-day-picker';
+import 'react-day-picker/lib/style.css';
+
+const DailyNotesWrap = styled.div`
+ display: flex;
+`;
+
+const DailyNotesPageWrap = styled.div`
+ box-shadow: var(--depth-shadow-8);
+ align-self: stretch;
+ justify-self: stretch;
+ margin: 1.25rem 2.5rem;
+ padding: 1rem 2rem;
+ transition-duration: 0s;
+ border-radius: 0.5rem;
+
+ &.is-today {
+ background: var(--background-plus-1)
+ }
+`;
+
+const Sidebar = styled.div`
+ flex: 0 0 200px;
+`;
+
+const Pages = styled.div`
+ min-height: calc(100vh + 1px);
+ display: flex;
+ padding: 1.25rem 0;
+ align-items: 0;
+ flex: 1 1 100%;
+ flex-direction: column;
+`;
+
+/**
+ * Current daily note, list of daily notes
+ */
+
+
+const DailyNotesPage = ({
+ date,
+ isToday,
+ hasContent
+}) => {
+ const [isLinkedReferencesOpen, setIsLinkedReferencesOpen] = React.useState(true);
+ const [isUnlinkedReferencesOpen, setIsUnlinkedReferencesOpen] = React.useState(true);
+ const handlePressLinkedReferencesToggle = () => setIsLinkedReferencesOpen(!isLinkedReferencesOpen);
+ const handlePressUnlinkedReferencesToggle = () => setIsUnlinkedReferencesOpen(!isUnlinkedReferencesOpen);
+
+ const { ref, inView, entry } = useInView({
+ threshold: 0,
+ });
+
+ return (
+
+ null}
+ handlePressAddShortcut={() => null}
+ handlePressShowLocalGraph={() => null}
+ handlePressDelete={() => null}
+ handlePressLinkedReferencesToggle={handlePressLinkedReferencesToggle}
+ handlePressUnlinkedReferencesToggle={handlePressUnlinkedReferencesToggle}
+ isLinkedReferencesOpen={isLinkedReferencesOpen}
+ isUnlinkedReferencesOpen={isUnlinkedReferencesOpen}
+ children={{hasContent ? : <>>} }
+ />
+
+ );
+};
+
+const onDayClick = (day, modifiers, dayPickerInput) => { }
+
+export const DailyNotes = ({ }) => {
+ const [currentPage, setCurrentPage] = React.useState('today');
+
+ const modifiers = {
+ highlighted: new Date(2021, 9, 19),
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/js/components/concept/DailyNotes/index.ts b/src/js/components/concept/DailyNotes/index.ts
new file mode 100644
index 0000000000..6a7547a5fc
--- /dev/null
+++ b/src/js/components/concept/DailyNotes/index.ts
@@ -0,0 +1,2 @@
+import { DailyNotes } from './DailyNotes';
+export { DailyNotes };
\ No newline at end of file
diff --git a/src/js/components/concept/DatabaseIcon/DatabaseIcon.stories.tsx b/src/js/components/concept/DatabaseIcon/DatabaseIcon.stories.tsx
new file mode 100644
index 0000000000..26a77fe594
--- /dev/null
+++ b/src/js/components/concept/DatabaseIcon/DatabaseIcon.stories.tsx
@@ -0,0 +1,64 @@
+import styled from 'styled-components';
+import { BADGE, Storybook } from '@/utils/storybook';
+
+import { DatabaseIcon } from './DatabaseIcon';
+
+export default {
+ title: 'Concepts/DatabaseIcon',
+ component: DatabaseIcon,
+ argTypes: {
+ color: { control: { type: 'color' } },
+ },
+ parameters: {
+ layout: "centered",
+ badges: [BADGE.DEV, BADGE.IN_USE]
+ },
+ decorators: [(Story) => ]
+};
+
+const Template = (args) => ;
+
+export const Default = Template.bind({});
+Default.args = {
+ name: 'Untitled Database'
+};
+
+const Grid = styled.div`
+ display: grid;
+ margin: auto;
+ gap: 1em;
+ grid-template-columns: repeat(6, 1fr);
+`;
+
+export const Sizes = () => <>
+
+
+
+
+
+
+
+
+
+
+
+
+>
+Sizes.decorators = [(Story) => ];
+
+export const Styles = () => <>
+
+
+
+
+
+
+
+
+
+
+
+
+>;
+Styles.decorators = [(Story) => ];
diff --git a/src/js/components/concept/DatabaseIcon/DatabaseIcon.tsx b/src/js/components/concept/DatabaseIcon/DatabaseIcon.tsx
new file mode 100644
index 0000000000..bcad835b16
--- /dev/null
+++ b/src/js/components/concept/DatabaseIcon/DatabaseIcon.tsx
@@ -0,0 +1,61 @@
+import { CSSProperties } from 'react';
+import styled from 'styled-components';
+import { readableColor } from 'polished';
+
+const IconWrap = styled.svg`
+ font-size: var(--size, 1.5em);
+ height: 1em;
+ width: 1em;
+
+ text {
+ text-transform: uppercase;
+ font-weight: bold;
+ user-select: none;
+ font-family: var(--font-family-serif);
+ }
+`;
+
+export interface DatabaseIcon {
+ icon?: string;
+ name: string;
+ color?: CSSProperties["color"];
+ size?: CSSProperties["width"];
+}
+
+/**
+ * Icon representing a database
+ */
+export const DatabaseIcon = ({
+ icon,
+ name,
+ color,
+ size
+}: DatabaseIcon): React.ReactElement => {
+ // Validate is a valid emoji icon
+ const isEmojiIcon = icon && /\p{Emoji}/u.test(icon);
+
+ return (
+
+
+ {isEmojiIcon ? icon.trim() : name.trim().charAt(0)}
+ )
+};
+
+DatabaseIcon.Wrap = IconWrap;
\ No newline at end of file
diff --git a/src/js/components/concept/DatabaseIcon/index.ts b/src/js/components/concept/DatabaseIcon/index.ts
new file mode 100644
index 0000000000..a2b029bf97
--- /dev/null
+++ b/src/js/components/concept/DatabaseIcon/index.ts
@@ -0,0 +1,2 @@
+import { DatabaseIcon } from './DatabaseIcon';
+export { DatabaseIcon };
\ No newline at end of file
diff --git a/src/js/components/concept/DatabaseMenu/DatabaseMenu.stories.tsx b/src/js/components/concept/DatabaseMenu/DatabaseMenu.stories.tsx
new file mode 100644
index 0000000000..2d24848e42
--- /dev/null
+++ b/src/js/components/concept/DatabaseMenu/DatabaseMenu.stories.tsx
@@ -0,0 +1,35 @@
+
+import { BADGE, Storybook } from '@/utils/storybook';
+import { mockDatabases } from './mockData';
+
+import { DatabaseMenu } from './DatabaseMenu';
+
+export default {
+ title: 'Concepts/DatabaseMenu',
+ component: DatabaseMenu,
+ argTypes: {},
+ parameters: {
+ badges: [BADGE.DEV, BADGE.IN_USE]
+ },
+ decorators: [(Story) => ]
+};
+
+const Template = (args) => null}
+ handlePressAddDatabase={() => null}
+ handlePressRemoveDatabase={() => null}
+ handlePressImportDatabase={() => null}
+ activeDatabase={mockDatabases[0]}
+ synced={true}
+ {...args} />;
+
+export const Default = Template.bind({});
+Default.args = {
+ inactiveDatabases: mockDatabases.slice(1, 4)
+};
+
+export const LotsOfDBs = Template.bind({});
+LotsOfDBs.args = {
+ inactiveDatabases: mockDatabases
+};
+
diff --git a/src/js/components/concept/DatabaseMenu/DatabaseMenu.tsx b/src/js/components/concept/DatabaseMenu/DatabaseMenu.tsx
new file mode 100644
index 0000000000..548f15a873
--- /dev/null
+++ b/src/js/components/concept/DatabaseMenu/DatabaseMenu.tsx
@@ -0,0 +1,196 @@
+import React from 'react';
+import styled from 'styled-components';
+import { DOMRoot } from '@/utils/config';
+
+import { AddCircle } from '@material-ui/icons';
+import { Modal, Popper } from '@material-ui/core';
+
+import { Button } from '@/Button';
+import { Overlay } from '@/Overlay';
+import { Badge } from '@/concept/Badge';
+import { Menu } from '@/Menu';
+
+import { DatabaseIcon } from '@/concept/DatabaseIcon';
+import { DatabaseMenuItem } from './components/DatabaseMenuItem';
+
+const DatabaseMenuOverlay = styled(Overlay)`
+ width: 16rem;
+ max-height: 90vh;
+ overflow-y: auto;
+ padding: 0;
+
+ svg {
+ --size: 2em;
+ }
+
+ @supports (overflow-y: overlay) {
+ overflow-y: overlay;
+ }
+`;
+
+const ActiveDatabase = styled.div`
+ display: flex;
+ padding: 0.25rem 1rem 0.25rem 0.5rem;
+ gap: 0.25rem;
+ flex: 0 0 auto;
+ overflow: hidden;
+
+ svg {
+ flex: 0 0 2.5rem;
+ margin-left: -0.325rem;
+ --size: 2em;
+ }
+
+ main {
+ display: flex;
+ flex: 1 1 100%;
+ /* Width is specified so that descendant text will get ellipses properly
+ without resorting to overflow: hidden on this element. */
+ width: calc(100% - 2rem);
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: stretch;
+ }
+
+ h3 {
+ margin: 0;
+ }
+
+ .tools {
+ display: flex;
+ margin-top: 0.5rem;
+ font-size: var(--font-size--text-sm);
+
+ button {
+ padding: 0.25rem 0.5rem;
+ }
+ }
+`;
+
+const Name = styled.h4`
+ font-weight: normal;
+ font-size: var(--font-size--text-sm);
+ color: inherit;
+ margin: 0;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+`;
+
+const Path = styled.span`
+ display: block;
+ color: var(--body-text-color---opacity-med);
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-size: 12px;
+ white-space: nowrap;
+`;
+
+const ItemsMenu = styled(Menu)`
+ padding: 0.25rem;
+ overflow-y: auto;
+`;
+
+export interface DatabaseMenuProps {
+ activeDatabase: Database
+ inactiveDatabases: Database[]
+ isSynced: boolean
+ handleChooseDatabase: (database: Database) => void
+ handlePressAddDatabase: () => void
+ handlePressRemoveDatabase: (database: Database) => void
+ handlePressImportDatabase: (database: Database) => void
+ handlePressMoveDatabase: (database: Database) => void
+}
+
+/**
+ * Menu for switching to and joining graphs.
+ */
+export const DatabaseMenu = ({
+ handlePressAddDatabase,
+ handleChooseDatabase,
+ handlePressImportDatabase,
+ handlePressRemoveDatabase,
+ handlePressMoveDatabase,
+ activeDatabase,
+ inactiveDatabases,
+ isSynced
+}: DatabaseMenuProps) => {
+ const [isMenuOpen, setIsMenuOpen] = React.useState(false);
+ const [menuAnchor, setMenuAnchor] = React.useState(null);
+
+ const handlePressDatabaseMenu = (e) => {
+ setMenuAnchor(e.currentTarget);
+ setIsMenuOpen(true);
+ };
+
+ const handleCloseDatabaseMenu = () => {
+ setMenuAnchor(null);
+ setIsMenuOpen(false);
+ };
+
+ return (
+ <>
+
+ {isSynced ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+ {activeDatabase.name}
+ {activeDatabase.id}
+
+ {activeDatabase["is-remote"] ? (
+ <>
+ handlePressImportDatabase(activeDatabase)}>Import
+ handlePressRemoveDatabase(activeDatabase)}>Remove
+ >
+ ) : (
+ <>
+ handlePressMoveDatabase(activeDatabase)}>Import
+ handlePressRemoveDatabase(activeDatabase)}>Remove
+ >
+ )}
+
+
+
+ {inactiveDatabases.length > 0 && (
+ <>
+
+ {inactiveDatabases.map(database => )}
+ >
+ )}
+
+ Add Database
+
+
+
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/src/js/components/concept/DatabaseMenu/components/DatabaseMenuItem.tsx b/src/js/components/concept/DatabaseMenu/components/DatabaseMenuItem.tsx
new file mode 100644
index 0000000000..b4d893376f
--- /dev/null
+++ b/src/js/components/concept/DatabaseMenu/components/DatabaseMenuItem.tsx
@@ -0,0 +1,70 @@
+import styled from 'styled-components';
+
+import { Link } from '@material-ui/icons';
+
+import { Button } from '@/Button';
+import { DatabaseIcon } from '@/concept/DatabaseIcon';
+
+const MenuItem = styled(Button)`
+ display: flex;
+ padding: 0.25rem 1rem 0.25rem 0.5rem;
+ flex: 0 0 auto;
+ gap: 0.25rem;
+ overflow: hidden;
+
+ > svg {
+ flex: 0 0 2rem;
+ }
+
+ main {
+ display: flex;
+ flex: 1 1 100%;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: stretch;
+ overflow: hidden;
+ }
+`;
+
+const Name = styled.h4`
+ font-weight: normal;
+ font-size: var(--font-size--text-sm);
+ color: inherit;
+ margin: 0;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+`;
+
+const Path = styled.span`
+ display: flex;
+ color: var(--body-text-color---opacity-med);
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-size: 12px;
+ white-space: nowrap;
+
+ svg {
+ margin-right: 0.25rem;
+ }
+`;
+
+interface DatabaseMenuItemProps {
+ database: Database;
+ handleChooseDatabase: (database: Database) => void;
+}
+
+export const DatabaseMenuItem = ({
+ database,
+ handleChooseDatabase,
+}: DatabaseMenuItemProps) => handleChooseDatabase(database)}
+>
+
+
+ {database.name}
+ {database["is-remote"] && } {database.id}
+
+
\ No newline at end of file
diff --git a/src/js/components/concept/DatabaseMenu/index.ts b/src/js/components/concept/DatabaseMenu/index.ts
new file mode 100644
index 0000000000..bccc237a1c
--- /dev/null
+++ b/src/js/components/concept/DatabaseMenu/index.ts
@@ -0,0 +1,2 @@
+import { DatabaseMenu, DatabaseMenuProps } from './DatabaseMenu';
+export { DatabaseMenu, DatabaseMenuProps };
\ No newline at end of file
diff --git a/src/js/components/concept/DatabaseMenu/mockData.ts b/src/js/components/concept/DatabaseMenu/mockData.ts
new file mode 100644
index 0000000000..a09d6aaedc
--- /dev/null
+++ b/src/js/components/concept/DatabaseMenu/mockData.ts
@@ -0,0 +1,196 @@
+
+export const synced = false;
+
+export const mockDatabases = [{
+ name: "Duke Energy Corporation",
+ id: "/penatibus/et/magnis/dis/parturient.js",
+ color: "#96c622",
+ icon: "📔",
+ isRemote: true
+}, {
+ name: "Lumber Liquidators Holdings, Inc",
+ id: "/morbi/non/lectus.aspx",
+ color: "#3ab348",
+ icon: "🎒",
+ isRemote: true
+}, {
+ name: "RELX N.V.",
+ id: "/libero/non/mattis/pulvinar/nulla/pede.js",
+ color: "#0215d1",
+ icon: "📚",
+ isRemote: true
+}, {
+ name: "Laredo Petroleum, Inc.",
+ id: "/vestibulum/sit.jsp",
+ color: "#209adc",
+ icon: "💻",
+ isRemote: false
+}, {
+ name: "Turquoise Hill Resources Ltd.",
+ id: "/morbi/vestibulum/velit/id/pretium/iaculis.jsp",
+ color: "#8c3a6a",
+ icon: null,
+ isRemote: false
+}, {
+ name: "Timken Company (The)",
+ id: "/egestas/metus/aenean/fermentum/donec.json",
+ color: "#0c1050",
+ icon: "📖",
+ isRemote: false
+}, {
+ name: "Thor Industries, Inc.",
+ id: "/dui/nec/nisi/volutpat/eleifend.jpg",
+ color: "#7bd0b1",
+ icon: null,
+ isRemote: false
+}, {
+ name: "Enterprise Bancorp Inc",
+ id: "/sociis/natoque/penatibus/et/magnis/dis/parturient.js",
+ color: "#f7b0aa",
+ icon: "💻",
+ isRemote: true
+}, {
+ name: "Qwest Corporation",
+ id: "/dapibus/dolor/vel/est.xml",
+ color: "#092888",
+ icon: null,
+ isRemote: false
+}, {
+ name: "Capitala Finance Corp.",
+ id: "/sed.aspx",
+ color: "#0583ff",
+ icon: "📘",
+ isRemote: true
+}, {
+ name: "uniQure N.V.",
+ id: "/natoque/penatibus.js",
+ color: "#aa3dd0",
+ icon: null,
+ isRemote: false
+}, {
+ name: "HDFC Bank Limited",
+ id: "/consequat/dui/nec/nisi/volutpat/eleifend/donec.js",
+ color: "#2ca775",
+ icon: "📓",
+ isRemote: false
+}, {
+ name: "Choice Hotels International, Inc.",
+ id: "/nulla/suscipit/ligula/in/lacus/curabitur.aspx",
+ color: "#660751",
+ icon: "💻",
+ isRemote: true
+}, {
+ name: "PennantPark Investment Corporation",
+ id: "/nec/euismod/scelerisque/quam.xml",
+ color: "#ec2f42",
+ icon: "🔖",
+ isRemote: true
+}, {
+ name: "AMN Healthcare Services Inc",
+ id: "/velit/nec/nisi/vulputate/nonummy/maecenas/tincidunt.aspx",
+ color: "#4eb180",
+ icon: "📓",
+ isRemote: true
+}, {
+ name: "Gabelli Utility Trust (The)",
+ id: "/amet.jsp",
+ color: "#60ebea",
+ icon: "💁",
+ isRemote: true
+}, {
+ name: "Champions Oncology, Inc.",
+ id: "/sem/fusce/consequat/nulla/nisl/nunc/nisl.jsp",
+ color: "#69e3a1",
+ icon: "💁",
+ isRemote: true
+}, {
+ name: "OncoGenex Pharmaceuticals Inc.",
+ id: "/leo/odio/condimentum/id/luctus/nec/molestie.jpg",
+ color: "#d937f2",
+ icon: "🔖",
+ isRemote: false
+}, {
+ name: "Ollie's Bargain Outlet Holdings, Inc.",
+ id: "/cras/non/velit.aspx",
+ color: "#0703b7",
+ icon: "📗",
+ isRemote: true
+}, {
+ name: "Viking Therapeutics, Inc.",
+ id: "/volutpat/dui/maecenas/tristique.xml",
+ color: "#939adf",
+ icon: "🎒",
+ isRemote: false
+}, {
+ name: "American Financial Group, Inc.",
+ id: "/vivamus/vestibulum/sagittis/sapien/cum.html",
+ color: "#3f38ec",
+ icon: "💁",
+ isRemote: false
+}, {
+ name: "Harmony Merger Corp.",
+ id: "/justo/in/hac/habitasse/platea.aspx",
+ color: "#43f9f1",
+ icon: "📗",
+ isRemote: false
+}, {
+ name: "Boulevard Acquisition Corp. II",
+ id: "/odio/elementum/eu/interdum/eu.js",
+ color: "#b161e8",
+ icon: "😎",
+ isRemote: false
+}, {
+ name: "KNOT Offshore Partners LP",
+ id: "/dolor/morbi/vel/lectus/in/quam/fringilla.xml",
+ color: "#f79c0e",
+ icon: "📖",
+ isRemote: false
+}, {
+ name: "Buckle, Inc. (The)",
+ id: "/velit/id/pretium/iaculis/diam.xml",
+ color: "#683f8e",
+ icon: null,
+ isRemote: true
+}, {
+ name: "NeoGenomics, Inc.",
+ id: "/volutpat/quam/pede/lobortis/ligula.aspx",
+ color: "#1c7dbf",
+ icon: "📓",
+ isRemote: false
+}, {
+ name: "J. Alexander's Holdings, Inc.",
+ id: "/sagittis/nam/congue.png",
+ color: "#0240e7",
+ icon: "🔖",
+ isRemote: false
+}, {
+ name: "Haynes International, Inc.",
+ id: "/massa/quis.xml",
+ color: "#a7cbf9",
+ icon: "💁",
+ isRemote: false
+}, {
+ name: "Surmodics, Inc.",
+ id: "/convallis/eget/eleifend/luctus/ultricies/eu.jpg",
+ color: "#c8c9e0",
+ icon: "📖",
+ isRemote: false
+}, {
+ name: "First Trust RBA Quality Income ETF",
+ id: "/nisi/at/nibh/in.html",
+ color: "#ab583c",
+ icon: "📖",
+ isRemote: true
+}, {
+ name: "Colonial Municipal Income Trust",
+ id: "/cursus.jpg",
+ color: "#781854",
+ icon: null,
+ isRemote: false
+}, {
+ name: "Bridgepoint Education, Inc.",
+ id: "/pellentesque/at/nulla/suspendisse/potenti/cras/in.png",
+ color: "#c04dd0",
+ icon: "📖",
+ isRemote: false
+}]
\ No newline at end of file
diff --git a/src/js/components/concept/Embed/Embed.stories.tsx b/src/js/components/concept/Embed/Embed.stories.tsx
new file mode 100644
index 0000000000..7aeda36c40
--- /dev/null
+++ b/src/js/components/concept/Embed/Embed.stories.tsx
@@ -0,0 +1,43 @@
+import { BADGE, Storybook } from '@/utils/storybook';
+
+import { Embed } from './Embed';
+import { Block } from '@/concept/Block';
+import { block } from '@/concept/Block/mockData';
+
+export default {
+ title: 'Concepts/Embed',
+ component: Embed,
+ argTypes: {},
+ decorators: [(Story) => ],
+ parameters: {
+ badges: [BADGE.DEV]
+ }
+};
+
+export const Youtube = () => ;
+export const Image = () => ;
+export const InABlock = () => <>
+
+ }
+ />
+
+>;
+
+export const InABlock2 = () => <>
+
+ }
+ />
+ }
+ />
+
+>;
+
diff --git a/src/js/components/concept/Embed/Embed.tsx b/src/js/components/concept/Embed/Embed.tsx
new file mode 100644
index 0000000000..03f6968007
--- /dev/null
+++ b/src/js/components/concept/Embed/Embed.tsx
@@ -0,0 +1,63 @@
+import styled from 'styled-components';
+import { classnames } from '@/utils/classnames';
+
+const EmbedWrap = styled.div`
+ border-radius: 0.25rem;
+ overflow: hidden;
+ display: flex;
+ width: 100%;
+ height: max-content;
+ background: var(--background-minus-2);
+ padding-block: 1px;
+
+ &.video-16-9 {
+ padding-bottom: calc((9 / 16) * 100%);
+ height: 0;
+ background: #000;
+ position: relative;
+
+ iframe {
+ position: absolute;
+ inset: 0;
+ border: none;
+ }
+ }
+
+ img {
+ width: 100%;
+ height: auto;
+ }
+`;
+
+type EmbedType = "youtube" | "image";
+
+interface EmpbedProps {
+ type: EmbedType,
+ url: string,
+ caption?: React.ReactNode,
+}
+
+export const Embed = ({
+ type,
+ caption,
+ url,
+}: EmpbedProps) => {
+ const isVideo = type === "youtube";
+
+ return
+
+ {type === "image" && }
+ {type === "youtube" && }
+
+}
diff --git a/src/js/components/concept/Embed/index.ts b/src/js/components/concept/Embed/index.ts
new file mode 100644
index 0000000000..5a1d30ddfc
--- /dev/null
+++ b/src/js/components/concept/Embed/index.ts
@@ -0,0 +1,2 @@
+import { Embed } from './Embed';
+export { Embed }
\ No newline at end of file
diff --git a/src/js/components/concept/LeftSidebar/LeftSidebar.stories.tsx b/src/js/components/concept/LeftSidebar/LeftSidebar.stories.tsx
new file mode 100644
index 0000000000..b1eb114e87
--- /dev/null
+++ b/src/js/components/concept/LeftSidebar/LeftSidebar.stories.tsx
@@ -0,0 +1,139 @@
+import styled from 'styled-components';
+import { BADGE, Storybook } from '@/utils/storybook';
+
+import { LeftSidebar } from './LeftSidebar';
+import { AppLayout } from '@/concept/App';
+
+export default {
+ title: 'Sections/LeftSidebar',
+ component: LeftSidebar,
+ argTypes: {},
+ parameters: {
+ badges: [BADGE.DEV]
+ }
+};
+
+const Template = (args) =>
+
+ ;
+
+export const Basic = Template.bind({});
+Basic.args = {
+ isLeftSidebarOpen: true,
+ shortcuts: [{
+ uid: "4b89dde0-3ccf-481a-875b-d11adfda3f7e",
+ title: "Passer domesticus",
+ order: 1
+ },],
+ version: '1.0.0'
+};
+
+export const ManyShortcuts = Template.bind({});
+ManyShortcuts.args = {
+ isLeftSidebarOpen: true,
+ shortcuts: [
+ {
+ uid: "4b89dde0-3ccf-481a-875b-d11adfda3f7e",
+ title: "Passer domesticus",
+ order: 1
+ }, {
+ uid: "bd4a892f-c7e5-45d8-bab8-68a8ed9d224f",
+ title: "Spermophilus richardsonii",
+ order: 2
+ }, {
+ uid: "b60fc12e-bf48-415c-a059-a7a4d5ef686e",
+ title: "Leprocaulinus vipera",
+ order: 3
+ }, {
+ uid: "c58d62e5-0e1b-4f30-a156-af8467317c1c",
+ title: "Rangifer tarandus",
+ order: 4
+ }, {
+ uid: "dd099e5d-1f6d-4be7-8bf0-9fc0310ba489",
+ title: "Nycticorax nycticorax",
+ order: 5
+ }, {
+ uid: "819f946d-9fbe-43e4-bd6c-6d9a6577d06e",
+ title: "Hystrix cristata",
+ order: 6
+ }, {
+ uid: "1ad4b569-252a-4778-a267-6d3d53bd3fb7",
+ title: "Paraxerus cepapi",
+ order: 7
+ }, {
+ uid: "ac370365-498c-4730-94d7-076756407062",
+ title: "Neophron percnopterus",
+ order: 8
+ }, {
+ uid: "f3471fcc-abc2-4ad5-bdc6-85d60d6b482e",
+ title: "Nyctanassa violacea",
+ order: 9
+ }, {
+ uid: "c527bd52-f128-4395-8d30-667ec360d2f7",
+ title: "Dasypus septemcincus",
+ order: 10
+ }, {
+ uid: "5fe97b52-3e11-4a08-b315-40974fa78b8b",
+ title: "Dusicyon thous",
+ order: 11
+ }, {
+ uid: "bc9a9677-50f6-428b-aa2c-964fca8319fe",
+ title: "Macropus robustus",
+ order: 12
+ }, {
+ uid: "1df27ac7-850e-48a5-b350-5fee2464d326",
+ title: "Macropus agilis",
+ order: 13
+ }, {
+ uid: "3023ecbf-bd11-4985-a66b-e3e7a18d1a71",
+ title: "Spermophilus armatus",
+ order: 14
+ }, {
+ uid: "3af40f86-c773-436f-9de6-b1c1fda94972",
+ title: "Podargus strigoides",
+ order: 15
+ }, {
+ uid: "e4f8385a-65e6-4d06-985c-e6fc14386b97",
+ title: "Laniaurius atrococcineus",
+ order: 16
+ }, {
+ uid: "f0d64494-71ff-403d-8edf-3b21ca77e782",
+ title: "Geochelone elegans",
+ order: 17
+ }, {
+ uid: "c1565410-6d71-4984-8456-5e3e90751b7a",
+ title: "Pteronura brasiliensis",
+ order: 18
+ }, {
+ uid: "506d0bc7-23f2-4b31-a0cd-64cdbcd3b5dc",
+ title: "Hystrix indica",
+ order: 19
+ }, {
+ uid: "95b0e93f-bf81-46bc-a667-57ca1b6bdac8",
+ title: "Theropithecus gelada",
+ order: 20
+ }, {
+ uid: "67b65377-6129-4240-92bc-d566ac471cee",
+ title: "Libellula quadrimaculata",
+ order: 21
+ }, {
+ uid: "69992624-1211-4514-a6b6-121f4a2bab9e",
+ title: "Procyon lotor",
+ order: 22
+ }, {
+ uid: "6f7b4a22-62ff-4af2-b635-2b79dc48e290",
+ title: "Numida meleagris",
+ order: 23
+ }, {
+ uid: "f3e79018-39ce-4d21-979a-468706501fa8",
+ title: "Pycnonotus nigricans",
+ order: 24
+ }, {
+ uid: "36b6e034-87e9-4b91-87e7-4663d8792d37",
+ title: "Macropus parryi",
+ order: 25
+ }
+ ],
+ version: '1.0.0'
+};
+
diff --git a/src/js/components/concept/LeftSidebar/LeftSidebar.tsx b/src/js/components/concept/LeftSidebar/LeftSidebar.tsx
new file mode 100644
index 0000000000..3a03823f94
--- /dev/null
+++ b/src/js/components/concept/LeftSidebar/LeftSidebar.tsx
@@ -0,0 +1,46 @@
+import React from 'react';
+import { classnames } from '@/utils/classnames';
+
+import { Shortcut } from './components/Shortcut';
+import { Logo } from './components/Logo';
+import { ShortcutsList } from './components/ShortcutsList';
+import { Sidebar } from './components/Sidebar';
+import { Version } from './components/Version';
+
+export type Shortcut = {
+ title: string;
+ uid: string;
+ order: number;
+}
+
+export interface LeftSidebarProps {
+ isLeftSidebarOpen: boolean;
+ version: string;
+ shortcuts: Shortcut[];
+ handlePressShortcut(): void;
+}
+
+export const LeftSidebar = ({ isLeftSidebarOpen, version, shortcuts, handlePressShortcut }: LeftSidebarProps) => {
+
+ return (
+
+
+
+
+ Shortcuts
+ {shortcuts && shortcuts.map((s) => (
+
+ ))}
+
+
+
+ Athens
+ {version && {version} }
+
+
+
+ )
+};
\ No newline at end of file
diff --git a/src/js/components/concept/LeftSidebar/components/Logo.tsx b/src/js/components/concept/LeftSidebar/components/Logo.tsx
new file mode 100644
index 0000000000..87f7f2d932
--- /dev/null
+++ b/src/js/components/concept/LeftSidebar/components/Logo.tsx
@@ -0,0 +1,17 @@
+import styled from 'styled-components';
+
+export const Logo = styled.a`
+ font-family: 'IBM Plex Serif';
+ font-size: 18px;
+ opacity: var(--opacity-med);
+ letter-spacing: -0.05em;
+ font-weight: bold;
+ text-decoration: none;
+ justify-self: flex-end;
+ color: var(--header-text-color);
+ transition: opacity 0.05s ease;
+
+ &:hover {
+ opacity: 1;
+ }
+`;
diff --git a/src/js/components/concept/LeftSidebar/components/Shortcut.tsx b/src/js/components/concept/LeftSidebar/components/Shortcut.tsx
new file mode 100644
index 0000000000..577a1794fb
--- /dev/null
+++ b/src/js/components/concept/LeftSidebar/components/Shortcut.tsx
@@ -0,0 +1,36 @@
+import styled from 'styled-components';
+
+const ShortcutWrap = styled.li`
+ display: contents;
+
+ a {
+ color: var(--link-color);
+ text-decoration: none;
+ cursor: pointer;
+ display: flex;
+ flex: 0 0 auto;
+ padding: 0.25rem 0;
+ }
+`;
+
+export interface ShortcutProps {
+ /**
+ * The index of the shortcut in the list of shortcuts
+ */
+ order: number;
+ /**
+ * The title of the shortcut
+ */
+ title: string;
+ /**
+ * The UID of the shortcut
+ */
+ uid: string;
+ handlePressShortcut(): void;
+}
+
+export const Shortcut = ({ order, title, uid, handlePressShortcut }: ShortcutProps) => {
+ return (
+ {title}
+ )
+}
\ No newline at end of file
diff --git a/src/js/components/concept/LeftSidebar/components/ShortcutsList.tsx b/src/js/components/concept/LeftSidebar/components/ShortcutsList.tsx
new file mode 100644
index 0000000000..ecacefc048
--- /dev/null
+++ b/src/js/components/concept/LeftSidebar/components/ShortcutsList.tsx
@@ -0,0 +1,23 @@
+import styled from 'styled-components';
+
+export const ShortcutsList = styled.ol`
+ flex: 1 1 100%;
+ display: flex;
+ list-style: none;
+ flex-direction: column;
+ padding: 0 2rem;
+ margin: 0 0 2rem;
+ overflow-y: auto;
+
+ @supports (overflow-y: overlay) {
+ overflow-y: overlay;
+ }
+
+ .heading {
+ flex: 0 0 auto;
+ opacity: var(--opacity-med);
+ line-height: 1;
+ margin: 0 0 0.25rem;
+ font-size: inherit;
+ }
+`;
diff --git a/src/js/components/concept/LeftSidebar/components/Sidebar.tsx b/src/js/components/concept/LeftSidebar/components/Sidebar.tsx
new file mode 100644
index 0000000000..1d6540c45f
--- /dev/null
+++ b/src/js/components/concept/LeftSidebar/components/Sidebar.tsx
@@ -0,0 +1,65 @@
+import styled from 'styled-components';
+
+export const Sidebar = styled.section`
+ width: 0;
+ grid-area: left-sidebar;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ overflow-x: hidden;
+ overflow-y: auto;
+ transition: width 0.5s ease;
+
+ @supports (overflow-y: overlay) {
+ overflow-y: overlay;
+ }
+
+ .top-line {
+ margin-bottom: 2.5rem;
+ display: flex;
+ flex: 0 0 auto;
+ justify-content: space-between;
+ }
+
+ .footer {
+ flex: 0 0 auto;
+ margin: auto 2rem 0;
+ align-self: stretch;
+ align-items: baseline;
+ display: flex;
+ grid-gap: 0.25rem;
+ }
+
+ .small-icon {
+ font-size: 16px;
+ }
+ .large-icon {
+ font-size: 22px;
+ }
+
+ .container {
+ width: 18rem;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ padding: calc(var(--app-upper-spacing) + 2rem) 0 1rem;
+ transition: opacity 0.5s ease;
+
+ .is-left-sidebar-open & {
+ opacity: 1;
+ }
+
+ .is-left-sidebar-closed & {
+ opacity: 0;
+ }
+
+ }
+
+ &.is-left-sidebar-open {
+ width: 18rem;
+ }
+
+ &.is-left-sidebar-closed {
+ width: 0;
+ }
+`;
diff --git a/src/js/components/concept/LeftSidebar/components/Version.tsx b/src/js/components/concept/LeftSidebar/components/Version.tsx
new file mode 100644
index 0000000000..0fc6c4bcf2
--- /dev/null
+++ b/src/js/components/concept/LeftSidebar/components/Version.tsx
@@ -0,0 +1,20 @@
+import styled from 'styled-components';
+
+export const Version = styled.a`
+ display: block;
+ font-size: 0.75em;
+ font-weight: 500;
+ line-height: 1;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ margin: 0.2em 0 0.2em auto;
+ color: var(--header-text-color);
+ transition: opacity 0.08s ease-in-out;
+ text-decoration: none;
+ opacity: 0.3;
+ font-size: clamp(12px, 100%, 14px);
+
+ &:hover {
+ opacity: 1;
+ }
+`;
diff --git a/src/js/components/concept/LeftSidebar/index.ts b/src/js/components/concept/LeftSidebar/index.ts
new file mode 100644
index 0000000000..da36ce5f6e
--- /dev/null
+++ b/src/js/components/concept/LeftSidebar/index.ts
@@ -0,0 +1,2 @@
+import { LeftSidebar } from "./LeftSidebar";
+export { LeftSidebar };
\ No newline at end of file
diff --git a/src/js/components/concept/Meter/Meter.stories.tsx b/src/js/components/concept/Meter/Meter.stories.tsx
new file mode 100644
index 0000000000..c02706bbd9
--- /dev/null
+++ b/src/js/components/concept/Meter/Meter.stories.tsx
@@ -0,0 +1,15 @@
+import { Meter } from './Meter';
+import { BADGE, Storybook } from '@/utils/storybook';
+
+export default {
+ title: 'Concepts/Meter',
+ component: Meter,
+ argTypes: {},
+ parameters: {
+ layout: 'centered',
+ badges: [BADGE.CONCEPT]
+ },
+ decorators: [(Story) => ]
+};
+
+export const Default = (args) => ( );
\ No newline at end of file
diff --git a/src/js/components/concept/Meter/Meter.tsx b/src/js/components/concept/Meter/Meter.tsx
new file mode 100644
index 0000000000..c8664124a1
--- /dev/null
+++ b/src/js/components/concept/Meter/Meter.tsx
@@ -0,0 +1,85 @@
+import React from 'react';
+import styled from 'styled-components';
+import { useMeter } from '@react-aria/meter'
+
+import { classnames } from '@/utils/classnames'
+
+export const MeterWrap = styled.div`
+ display: inline-flex;
+ flex-direction: column;
+ width: 200px;
+`;
+
+export const Bar = styled.div`
+ height: 0.5rem;
+ width: 100%;
+ background: var(--body-text-color---opacity-low);
+ border-radius: 100em;
+ overflow: hidden;
+`;
+
+export const Fill = styled.div`
+ height: inherit;
+ background: var(--link-color);
+
+ .should-change-smoothly & {
+ transition: width 0.1s ease;
+ }
+`;
+
+const Label = styled.span`
+ .is-label-hidden & {
+ visibility: hidden;
+ position: absolute;
+ }
+`;
+
+const ValueLabel = styled.span``;
+
+const Header = styled.div`
+ display: flex;
+ justify-content: space-between;
+`;
+
+interface MeterProps {
+ value: number
+ minValue?: number
+ maxValue?: number
+ label: React.ReactNode
+ showLabel?: boolean
+ showValueLabel?: boolean
+ shouldChangeSmoothly?: boolean
+ meterStyle?: React.CSSProperties
+}
+
+export const Meter = ({
+ label,
+ showLabel = !!label,
+ showValueLabel = !!label,
+ value,
+ minValue = 0,
+ maxValue = 100,
+ meterStyle,
+ shouldChangeSmoothly = true,
+}: MeterProps) => {
+
+ let { meterProps, labelProps } = useMeter({ label, showValueLabel, value, minValue, maxValue });
+
+ let percentage = (value - minValue) / (maxValue - minValue);
+ let barWidth = `${Math.round(percentage * 100)}%`;
+
+ return (
+
+
+ {label && {label} }
+ {showValueLabel && {meterProps['aria-valuetext']} }
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/js/components/concept/Meter/index.ts b/src/js/components/concept/Meter/index.ts
new file mode 100644
index 0000000000..9aede1927e
--- /dev/null
+++ b/src/js/components/concept/Meter/index.ts
@@ -0,0 +1,2 @@
+import { Meter } from './Meter';
+export { Meter };
\ No newline at end of file
diff --git a/src/js/components/concept/Page/Page.stories.tsx b/src/js/components/concept/Page/Page.stories.tsx
new file mode 100644
index 0000000000..7d0d6cb859
--- /dev/null
+++ b/src/js/components/concept/Page/Page.stories.tsx
@@ -0,0 +1,92 @@
+import React from 'react';
+
+import { Page } from '.';
+import { BADGE, Storybook } from '@/utils/storybook';
+import { DateTime } from 'luxon';
+import { preferredDateFormat } from '@/utils/config';
+
+import {
+ WithToggle,
+ WithSelection,
+ WithChecklist,
+ WithPresence
+} from '../Block/Block.stories';
+
+export default {
+ title: 'Concepts/Page',
+ component: Page,
+ argTypes: {},
+ parameters: {
+ badges: [BADGE.DEV]
+ },
+ decorators: [(Story) => ]
+};
+
+
+// Stories
+
+const Template = (args) => {
+ const [isLinkedReferencesOpen, setIsLinkedReferencesOpen] = React.useState(false);
+ const [isUnlinkedReferencesOpen, setIsUnlinkedReferencesOpen] = React.useState(false);
+ const handlePressLinkedReferencesToggle = () => setIsLinkedReferencesOpen(!isLinkedReferencesOpen);
+ const handlePressUnlinkedReferencesToggle = () => setIsUnlinkedReferencesOpen(!isUnlinkedReferencesOpen);
+ return
+};
+
+export const NodePage = Template.bind({});
+NodePage.args = {
+ title: "Node Page",
+ isDailyNote: false,
+ children: ,
+}
+
+export const BlockPage = Template.bind({});
+BlockPage.args = {
+ isDailyNote: false,
+ title: 'Block Page',
+ children:
+};
+
+export const PageWithPresence = Template.bind({});
+PageWithPresence.args = {
+ isDailyNote: false,
+ title: 'Block Page',
+ children:
+};
+
+export const PageWithChecklist = Template.bind({});
+PageWithChecklist.args = {
+ isDailyNote: false,
+ title: 'Node Page With Tasks',
+ children:
+};
+
+export const PageWithSelection = Template.bind({});
+PageWithSelection.args = {
+ isDailyNote: false,
+ title: 'Node Page With Selection',
+ children:
+};
+
+export const PageWithLongTitle = Template.bind({});
+PageWithLongTitle.args = {
+ isDailyNote: false,
+ title: 'Lorem ipsum dolor sit amet donec consectetur',
+ children:
+};
+
+export const DailyNote = Template.bind({});
+DailyNote.args = {
+ isDailyNote: true,
+ hasShortcut: false,
+ children: ,
+ title: DateTime.now().toLocaleString(preferredDateFormat),
+ uid: DateTime.now().toISODate()
+};
diff --git a/src/js/components/concept/Page/Page.tsx b/src/js/components/concept/Page/Page.tsx
new file mode 100644
index 0000000000..9897880c31
--- /dev/null
+++ b/src/js/components/concept/Page/Page.tsx
@@ -0,0 +1,218 @@
+import React from 'react';
+import styled from 'styled-components';
+
+import { mockPeople } from '@/Avatar/mockData';
+import { DOMRoot } from '@/utils/config';
+
+import { Today, MoreHoriz, Delete, Bookmark, BubbleChart } from '@material-ui/icons';
+import { Popper, Modal } from "@material-ui/core";
+
+import { Button } from '@/Button';
+import { Overlay } from '@/Overlay';
+import { Menu } from '@/Menu';
+import { Block } from '@/concept/Block';
+
+import { EmptyMessage } from './components/EmptyMessage';
+import { References, ReferencesProps } from './components/References';
+import { usePresenceProvider } from '@/concept/Block/hooks/usePresenceProvider';
+
+const mockPresence = mockPeople.map((p, index) => ({ ...p, uid: index.toString() }))
+
+const PageWrap = styled.article`
+ padding: 1rem;
+ flex-basis: 100%;
+ align-self: stretch;
+ width: 100%;
+ max-width: 60em;
+ margin-left: auto;
+ margin-right: auto;
+`;
+
+const PageHeader = styled.header`
+ position: relative;
+ padding: 0 3rem;
+`;
+
+const Title = styled.h1`
+ font-size: 2.5rem;
+ position: relative;
+ overflow: visible;
+ flex-grow: 1;
+ margin: 0.1em 0;
+ white-space: pre-line;
+ word-break: break-word;
+ line-height: 1.1em;
+
+ textarea {
+ padding: 0;
+ margin: 0;
+ width: 100%;
+ min-height: 100%;
+ font-weight: inherit;
+ letter-spacing: inherit;
+ font-size: inherit;
+ appearance: none;
+ cursor: text;
+ resize: none;
+ transform: translate3d(0,0,0);
+ color: inherit;
+ caret-color: var(--link-color);
+ background: transparent;
+ line-height: inherit;
+ border: 0;
+ font-family: inherit;
+ visibility: hidden;
+ position: absolute;
+
+ &::webkit-scrollbar {
+ display: none;
+ }
+
+ &:focus,
+ &.is-editing {
+ outline: none;
+ visibility: visible;
+ position: relative;
+ }
+
+ abbr {
+ z-index: 4;
+ }
+
+ &.is-editing + span {
+ visibility: hidden;
+ position: absolute;
+ }
+ }
+`;
+
+const PageMenuToggle = styled(Button)`
+ float: left;
+ border-radius: 1000px;
+ margin-left: -2.5rem;
+ margin-top: 0.5rem;
+ width: 2rem;
+ height: 2rem;
+ color: var(--body-text-color---opacity-high);
+ vertical-align: bottom;
+`;
+
+
+const BlocksContainer = styled(Block.ListContainer)`
+ padding-left: 1rem;
+ padding-right: 1rem;
+ display: flex;
+ flex-direction: column;
+`;
+
+interface PageProps extends ReferencesProps {
+ children?: React.ReactNode,
+ /**
+ * Whether the page is a Daily Note
+ */
+ isDailyNote: boolean,
+ /**
+ * Whether the page has a corresponding shortcut
+ */
+ hasShortcut?: boolean,
+ /**
+ * The title of the page
+ */
+ title: React.ReactNode,
+ /**
+ * Whether the page can be edited
+ */
+ isEditable?: boolean,
+ /**
+ * The unique identifier of the page
+ */
+ uid: string,
+ handlePressRemoveShortcut(): void,
+ handlePressAddShortcut(): void,
+ handlePressShowLocalGraph(): void,
+ handlePressDelete(): void,
+}
+
+/**
+ * Display whole page content
+ */
+export const Page = ({
+ isDailyNote,
+ hasShortcut,
+ isEditable = true,
+ children,
+ title,
+ uid,
+ isLinkedReferencesOpen,
+ isUnlinkedReferencesOpen,
+ handlePressLinkedReferencesToggle,
+ handlePressUnlinkedReferencesToggle,
+ handlePressRemoveShortcut,
+ handlePressAddShortcut,
+ handlePressShowLocalGraph,
+ handlePressDelete,
+}: PageProps) => {
+ const [isPageMenuOpen, setIsPageMenuOpen] = React.useState(false);
+ const [pageMenuAnchor, setPageMenuAnchor] = React.useState(null);
+ const { PresenceProvider, clearPresence } = usePresenceProvider({ presentPeople: mockPresence });
+
+ const handlePressMenuToggle = (e) => {
+ setPageMenuAnchor(e.currentTarget);
+ setIsPageMenuOpen(true);
+ };
+
+ const handleClosePageMenu = () => {
+ setPageMenuAnchor(null);
+ setIsPageMenuOpen(false);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {hasShortcut
+ ? Remove Shortcut
+ : Add Shortcut }
+ Show Local Graph
+
+ Delete Page
+
+
+
+
+ {title} {isDailyNote && }
+
+
+ {children ? children : isEditable ? : }
+
+ {/* */}
+ );
+};
+
+Page.BlocksContainer = BlocksContainer;
\ No newline at end of file
diff --git a/src/js/components/concept/Page/components/EmptyMessage.tsx b/src/js/components/concept/Page/components/EmptyMessage.tsx
new file mode 100644
index 0000000000..5f7c340268
--- /dev/null
+++ b/src/js/components/concept/Page/components/EmptyMessage.tsx
@@ -0,0 +1,7 @@
+import styled from 'styled-components';
+
+const Message = styled.span`
+ color: var(--body-text-color---opacity-med);
+`;
+
+export const EmptyMessage = () => This page has no content. ;
\ No newline at end of file
diff --git a/src/js/components/concept/Page/components/References/References.tsx b/src/js/components/concept/Page/components/References/References.tsx
new file mode 100644
index 0000000000..1da3cfaf00
--- /dev/null
+++ b/src/js/components/concept/Page/components/References/References.tsx
@@ -0,0 +1,165 @@
+import React from 'react';
+import styled from 'styled-components';
+
+import { ChevronRight } from '@material-ui/icons';
+
+import { Button } from '@/Button';
+import { blockTree } from '@/concept/Block/mockData';
+import { renderBlocks } from '@/concept/Block/utils/renderBlocks';
+import { Reference } from './components/Reference';
+
+
+const Section = styled.section``;
+
+const Count = styled.span`
+ border-radius: 100em;
+ background: var(--background-minus-2);
+ color: var(--body-text-color---opacity-high);
+ font-weight: bold;
+ padding: 0.125rem 0.25rem;
+ min-width: 1.25rem;
+ font-size: var(--font-size--text-xs);
+ text-align: center;
+ margin-left: 0.5rem;
+`;
+
+const SectionHeading = styled.h4`
+ font-weight: normal;
+ display: flex;
+ margin: 0;
+ align-items: center;
+
+ button {
+ margin-right: 0.25rem;
+ font-size: 1rem;
+ }
+`;
+
+const GroupTitle = styled.h5`
+ font-size: var(--font-size--text-lg);
+ color: var(--link-color);
+ margin: 0;
+ font-weight: 500;
+
+ a:hover {
+ cursor: pointer;
+ text-decoration: underline;
+ }
+`;
+
+const ReferencesList = styled.div`
+ font-size: 14px;
+`;
+
+const Group = styled.div`
+ background: var(--background-minus-2---opacity-med);
+ border-radius: 0.25rem;
+ padding: 1rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+`;
+
+const GroupItems = styled.ol`
+ display: contents;
+`;
+
+const SectionToggleButton = styled(Button).attrs(props => ({
+ "aria-pressed": props.isOpen
+}))`
+ border-radius: 100em;
+ padding: 0rem 0.5rem;
+ margin-left: -0.5rem;
+
+ &:hover {
+ background: var(--background-minus-1);
+ }
+ padding: 0.1rem 0.5rem;
+
+ &:active {
+ background: var(--background-minus-2);
+ }
+
+ svg {
+ transition: transform 0.1s ease-in-out;
+ width: 0.75em;
+ height: 0.75em;
+ margin: -0.25rem 0.25rem -0.25rem -0.25rem;
+ }
+
+ &[aria-pressed="true"] {
+ backdrop-filter: none;
+
+ svg {
+ transform: rotate(90deg);
+ }
+ }
+`;
+
+const ReferencesWrapper = styled.div`
+ padding: 1rem 3rem;
+ border-top: 1px solid var(--border-color);
+ margin-top: 1rem;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+`;
+
+
+const ReferenceSection = ({ label, isOpen, handlePressToggle, references }) => {
+ return (
+
+
+ {label} {!isOpen && (2 )}
+
+
+ {isOpen && (
+
+
+ Athens UX Priorities
+
+ {renderBlocks({
+ blockGraph: blockTree,
+ blockComponent:
+ })}
+
+
+ )}
+ )
+}
+
+
+export interface ReferencesProps {
+ linkedRefs: any;
+ unlinkedRefs: any;
+ isLinkedReferencesOpen: boolean;
+ isUnlinkedReferencesOpen: boolean;
+ handlePressLinkedReferencesToggle: () => void;
+ handlePressUnlinkedReferencesToggle: () => void;
+}
+
+export const References = ({
+ linkedRefs,
+ unlinkedRefs,
+ isLinkedReferencesOpen,
+ isUnlinkedReferencesOpen,
+ handlePressLinkedReferencesToggle,
+ handlePressUnlinkedReferencesToggle,
+}) => {
+ return (
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/js/components/concept/Page/components/References/components/Reference.tsx b/src/js/components/concept/Page/components/References/components/Reference.tsx
new file mode 100644
index 0000000000..4e29470f1b
--- /dev/null
+++ b/src/js/components/concept/Page/components/References/components/Reference.tsx
@@ -0,0 +1,53 @@
+import styled from 'styled-components';
+
+import { Breadcrumbs } from '@/concept/Breadcrumbs';
+
+import { Block } from '@/concept/Block';
+
+const ReferenceWrap = styled.li`
+ list-style: none;
+ padding-top: 0.25rem;
+ padding-bottom: 0.25rem;
+`;
+
+interface ReferenceBlockProps extends Block {
+ isOpen,
+ uid,
+ renderedContent,
+ handlePressToggle()
+ rawContent,
+}
+
+export const Reference = ({
+ uid,
+ isOpen,
+ renderedContent,
+ handlePressToggle,
+ rawContent
+}: ReferenceBlockProps) => {
+ return (
+
+ page name
+ page name
+ page name
+
+
+
+ null}
+ isClosedWithChildren={false}
+ />
+ null}
+ isClosedWithChildren={false}
+ />
+
+
+
+ );
+};
diff --git a/src/js/components/concept/Page/components/References/index.ts b/src/js/components/concept/Page/components/References/index.ts
new file mode 100644
index 0000000000..5460f08a4f
--- /dev/null
+++ b/src/js/components/concept/Page/components/References/index.ts
@@ -0,0 +1,2 @@
+import { References, ReferencesProps } from './References';
+export { References, ReferencesProps };
\ No newline at end of file
diff --git a/src/js/components/concept/Page/index.ts b/src/js/components/concept/Page/index.ts
new file mode 100644
index 0000000000..535fe74775
--- /dev/null
+++ b/src/js/components/concept/Page/index.ts
@@ -0,0 +1,2 @@
+import { Page } from './Page';
+export { Page };
\ No newline at end of file
diff --git a/src/js/components/concept/Preview/Preview.stories.tsx b/src/js/components/concept/Preview/Preview.stories.tsx
new file mode 100644
index 0000000000..429914e5fd
--- /dev/null
+++ b/src/js/components/concept/Preview/Preview.stories.tsx
@@ -0,0 +1,48 @@
+import React from 'react';
+import { BADGE, Storybook } from '@/utils/storybook';
+import { blockTree } from '@/concept/Block/mockData';
+
+import { Preview } from './Preview';
+import { Link } from '@/Link';
+import { renderBlocks } from '@/concept/Block/utils/renderBlocks';
+
+export default {
+ title: 'concepts/Preview',
+ component: Preview,
+ argTypes: {},
+ parameters: {
+ layout: 'fullscreen',
+ badges: [BADGE.CONCEPT]
+ },
+ decorators: [(Story) => ]
+};
+
+export const Basic = () => {
+ const [isPreviewOpen, setIsPreviewOpen] = React.useState(false);
+ const [anchorEl, setAnchorEl] = React.useState(null);
+
+ return (<>
+ setIsPreviewOpen(true)}
+ onMouseOut={() => setIsPreviewOpen(false)}
+ href="#"
+ >
+ link
+
+
+
+ {(
+ renderBlocks({
+ tree: blockTree.tree,
+ content: blockTree.blocks,
+ lengthLimit: 3,
+ blockComponent:
+ })
+ )}
+
+ >)
+}
\ No newline at end of file
diff --git a/src/js/components/concept/Preview/Preview.tsx b/src/js/components/concept/Preview/Preview.tsx
new file mode 100644
index 0000000000..71cc47a1da
--- /dev/null
+++ b/src/js/components/concept/Preview/Preview.tsx
@@ -0,0 +1,79 @@
+import styled from 'styled-components';
+
+import { DOMRoot } from '@/utils/config';
+import { Block } from '@/concept/Block';
+
+import { Overlay } from '@/Overlay';
+import { Popper } from '@material-ui/core';
+
+export const Preview = ({
+ isPreviewOpen,
+ children,
+ anchorEl,
+}) =>
+
+ {children}
+
+ ;
+
+Preview.Container = styled(Overlay)`
+ pointer-events: none;
+ padding: 0;
+ font-size: var(--font-size--text-sm);
+ overflow: hidden;
+ width: 20em;
+ line-height: 1.2;
+`;
+
+Preview.Title = styled.h1`
+ font-size: var(--font-size--text-base);
+ margin: 0;
+ padding: 0 1rem;
+ font-size: inherit;
+`;
+
+Preview.Body = styled.main`
+ padding: 0 1rem 1rem;
+ font-size: inherit;
+`;
+
+Preview.Media = styled.img`
+ width: 100%;
+ max-height: 8rem;
+ object-fit: cover;
+ margin-right: 1rem;
+ margin-bottom: 1rem;
+`;
+
+Preview.BlockWrap = styled.div`
+ overflow: hidden;
+`;
+
+Preview.BlockContent = styled.span`
+ display: flex;
+ line-clamp: 1;
+ -webkit-line-clamp: 1;
+ white-space: nowrap;
+ display: -webkit-box;
+ overflow: hidden;
+ text-overflow: ellipsis;
+`;
+
+Preview.MiniBlock = ({ renderedContent, children }) => {
+ return (
+
+ {renderedContent}
+ {children}
+ )
+}
\ No newline at end of file
diff --git a/src/js/components/concept/Preview/index.ts b/src/js/components/concept/Preview/index.ts
new file mode 100644
index 0000000000..82a00f7c94
--- /dev/null
+++ b/src/js/components/concept/Preview/index.ts
@@ -0,0 +1,2 @@
+import { Preview } from './Preview';
+export { Preview };
\ No newline at end of file
diff --git a/src/js/components/concept/Preview/mockData.ts b/src/js/components/concept/Preview/mockData.ts
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/js/components/concept/RightSidebar/RightSidebar.stories.tsx b/src/js/components/concept/RightSidebar/RightSidebar.stories.tsx
new file mode 100644
index 0000000000..76049f33fd
--- /dev/null
+++ b/src/js/components/concept/RightSidebar/RightSidebar.stories.tsx
@@ -0,0 +1,40 @@
+import { RightSidebar } from './RightSidebar';
+import { BADGE } from '@/utils/storybook';
+import { AppLayout } from '@/concept/App';
+
+export default {
+ title: 'Sections/RightSidebar',
+ component: RightSidebar,
+ argTypes: {},
+ parameters: {
+ badges: [BADGE.DEV]
+ }
+};
+
+const Template = (args) => ;
+
+export const Basic = Template.bind({});
+Basic.args = {
+ isRightSidebarOpen: true,
+ items: [{
+ title: 'Item 1',
+ isOpen: true,
+ type: 'block',
+ body: Lorem ipsum dolor sit amet ,
+ handlePressItemToggle: () => null,
+ handlePressItemClose: () => null,
+ }, {
+ title: 'Item 2',
+ isOpen: true,
+ type: 'block',
+ body: Lorem ipsum dolor sit amet ,
+ handlePressItemToggle: () => null,
+ handlePressItemClose: () => null,
+ }]
+};
+
+export const Empty = Template.bind({});
+Empty.args = {
+ isRightSidebarOpen: true,
+};
+
diff --git a/src/js/components/concept/RightSidebar/RightSidebar.tsx b/src/js/components/concept/RightSidebar/RightSidebar.tsx
new file mode 100644
index 0000000000..e1a20eed7b
--- /dev/null
+++ b/src/js/components/concept/RightSidebar/RightSidebar.tsx
@@ -0,0 +1,151 @@
+import React from "react";
+import styled from 'styled-components';
+import { classnames } from '@/utils/classnames';
+import { EmptyMessage } from "./components/EmptyMessage";
+import { RightSidebarItem, Item } from "./components/RightSidebarItem";
+
+const Sidebar = styled.aside`
+ justify-self: stretch;
+ overflow: hidden;
+ width: 0;
+ grid-area: secondary-content;
+ display: flex;
+ justify-content: space-between;
+ padding-top: 2.75rem;
+ transition-property: width, border, background;
+ transition-duration: 0.35s;
+ transition-timing-function: ease-out;
+ box-shadow: 0 -100px 0 var(--background-minus-1), inset 1px 0 var(--background-minus-1);
+
+ svg {
+ color: var(--body-text-color---opacity-high);
+ }
+
+ &.is-right-sidebar-closed {
+ width: 0;
+ }
+
+ &.is-right-sidebar-open {
+ width: var(--width);
+ }
+
+ &::-webkit-scrollbar {
+ background: var(--background-minus-1);
+ width: 0.5rem;
+ height: 0.5rem;
+ }
+ &::-webkit-scrollbar-corner {
+ background: var(--background-minus-1);
+ }
+ &::-webkit-scrollbar-thumb {
+ background: var(--background-plus-1);
+ border-radius: 0.5rem;
+ }
+
+ > .content {
+ display: flex;
+ flex: 1 1 var(--width);
+ flex-direction: column;
+ margin-left: 0;
+ overflow-y: auto;
+
+ @supports (overflow-y: overlay) {
+ overflow-y: overlay;
+ }
+
+ .is-right-sidebar-closed & {
+ margin-left: calc(var(--width) * -1);
+ opacity: 0;
+ }
+
+ .is-right-sidebar-open & {
+ opacity: 1;
+ }
+ }
+`;
+
+const DragHandle = styled.div`
+ cursor: col-resize;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ width: 1px;
+ z-index: var(--z-index-fixed);
+ background: var(--border-color);
+
+ &:after {
+ content: '';
+ position: absolute;
+ background: var(--link-color);
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: -4px;
+ opacity: 0;
+ }
+
+ &:hover:after {
+ opacity: 0.5;
+ }
+
+ .is-resizing &:after {
+ opacity: 1;
+ }
+`;
+
+
+export interface RightSidebarProps extends React.HTMLAttributes {
+ /**
+ * Whether Right Sidebar is open
+ */
+ isRightSidebarOpen: boolean;
+ /**
+ * Whether Right Sidebar is being resized
+ */
+ isResizing?: boolean;
+ /**
+ * Width of Right Sidebar in viewport width units
+ */
+ width?: number;
+ /**
+ * Width of Right Sidebar in viewport width units
+ */
+ items?: RightSidebarItem[];
+ handleGrabDragHandle?(): void;
+ handlePressItemClose?(): void;
+ handlePressItemToggle?(): void;
+}
+
+export const RightSidebar = ({
+ isRightSidebarOpen,
+ isResizing = false,
+ items,
+ width = 300,
+ handleGrabDragHandle,
+ handlePressItemClose,
+ handlePressItemToggle
+}: RightSidebarProps) => {
+ return (
+
+
+ {items
+ ? items.map((item) => )
+ : }
+
+ )
+}
\ No newline at end of file
diff --git a/src/js/components/concept/RightSidebar/components/EmptyMessage.tsx b/src/js/components/concept/RightSidebar/components/EmptyMessage.tsx
new file mode 100644
index 0000000000..578ea3d966
--- /dev/null
+++ b/src/js/components/concept/RightSidebar/components/EmptyMessage.tsx
@@ -0,0 +1,37 @@
+import styled from "styled-components";
+import { VerticalSplit } from "@material-ui/icons";
+
+const Message = styled.div`
+ align-self: center;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ font-size: 80%;
+ border-radius: 0.5rem;
+ line-height: 1.3;
+ margin: auto auto;
+ display: flex;
+ color: var(--body-text-color---opacity-med);
+
+ svg {
+ opacity: var(--opacity-low);
+ font-size: 1000%;
+ }
+
+ kbd {
+ border: 1px solid;
+ text-transform: uppercase;
+ border-radius: 0.2em;
+ padding-left: 0.1em;
+ padding-right: 0.1em;
+ }
+
+ p {
+ max-width: 13em;
+ }
+`;
+
+export const EmptyMessage = () =>
+
+ Hold shift when clicking a page link to view the page in the sidebar.
+ ;
diff --git a/src/js/components/concept/RightSidebar/components/RightSidebarItem.tsx b/src/js/components/concept/RightSidebar/components/RightSidebarItem.tsx
new file mode 100644
index 0000000000..ee3f804f84
--- /dev/null
+++ b/src/js/components/concept/RightSidebar/components/RightSidebarItem.tsx
@@ -0,0 +1,162 @@
+import React from "react";
+import styled from 'styled-components';
+import { Button } from '@/Button'
+import { Close, ChevronRight, BubbleChart, Description, FiberManualRecord } from "@material-ui/icons";
+
+const ItemWrap = styled.article`
+ display: flex;
+ flex: 0 0 auto;
+ flex-direction: column;
+`;
+
+const Toggle = styled(Button)`
+ margin: auto 0.5rem auto 0;
+ flex: 0 0 auto;
+ width: 1.75rem;
+ height: 1.75rem;
+ padding: 0;
+ cursor: pointer;
+ border-radius: 1000px;
+ place-content: center;
+
+ svg {
+ transition: transform 0.1s ease-out;
+ margin: 0;
+ }
+
+ .is-item-open & {
+ svg {
+ transform: rotate(90deg);
+ }
+ }
+`;
+
+const Container = styled.div`
+ line-height: 1.5rem;
+ z-index: 1;
+ padding: 0 0 1.25rem;
+ font-size: 95%;
+ position: relative;
+ background: inherit;
+
+ h1 {
+ font-size: 1.5em;
+ display: --webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 1;
+ line-clamp: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .node-page,
+ .block-page {
+ margin-top: 0;
+ }
+`;
+
+const Header = styled.header`
+ font-size: 100%;
+ align-items: center;
+ z-index: 2;
+ box-shadow: 0 -1px 0 0 var(--border-color);
+ display: flex;
+ flex: 0 0 auto;
+ padding: 0.25rem 1rem;
+ position: sticky;
+ background: var(--background-color);
+ top: 0;
+ bottom: 0;
+
+ h2 {
+ font-size: inherit;
+ flex: 1 1 100%;
+ line-height: 1;
+ margin: 0;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ font-weight: normal;
+ max-width: 100%;
+ overflow: hidden;
+ align-items: center;
+ color: var(--body-text-color);
+
+ svg {
+ opacity: var(--opacity-med);
+ display: inline;
+ vertical-align: -4px;
+ margin-right: 0.2em;
+ }
+ }
+
+ .controls {
+ display: flex;
+ flex: 0 0 auto;
+ align-items: stretch;
+ transition: opacity 0.3s ease-out;
+ opacity: 0.5;
+
+ :hover & {
+ opacity: 1;
+ }
+ }
+
+ svg {
+ font-size: 18px;
+ }
+
+ hr {
+ width: 1px;
+ background: var(--background-minus-1);
+ border: 0;
+ margin: 0.25rem;
+ flex: 0 0 1px;
+ height: 1em;
+ justify-self: stretch;
+ }
+
+ .is-item-open & {
+ h2 {
+ font-weight: 500;
+ }
+ }
+
+`;
+
+export type RightSidebarItem = {
+ isOpen: boolean;
+ body: React.ReactNode;
+ title: React.ReactNode;
+ type: 'block' | 'page' | 'graph';
+ handlePressItemToggle: () => void;
+ handlePressItemClose: () => void;
+};
+
+export const Item = ({
+ isOpen,
+ title,
+ body,
+ type,
+ handlePressItemToggle,
+ handlePressItemClose,
+}) => {
+
+ let icon;
+ switch (type) {
+ case 'block':
+ icon = ;
+ case 'page':
+ icon = ;
+ case 'graph':
+ icon = ;
+ }
+
+ return (
+
+ {isOpen && {body} }
+ )
+};
diff --git a/src/js/components/concept/RightSidebar/index.ts b/src/js/components/concept/RightSidebar/index.ts
new file mode 100644
index 0000000000..03e2ab657e
--- /dev/null
+++ b/src/js/components/concept/RightSidebar/index.ts
@@ -0,0 +1,2 @@
+import { RightSidebar } from './RightSidebar';
+export { RightSidebar }
\ No newline at end of file
diff --git a/src/js/components/concept/Settings/Settings.stories.tsx b/src/js/components/concept/Settings/Settings.stories.tsx
new file mode 100644
index 0000000000..0e0e0c5a3f
--- /dev/null
+++ b/src/js/components/concept/Settings/Settings.stories.tsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import { BADGE, Storybook } from '@/utils/storybook';
+
+import { Settings } from './Settings';
+import { Button } from '@/Button';
+
+export default {
+ title: 'Routes/Settings',
+ component: Settings,
+ argTypes: {},
+ parameters: {
+ layout: 'centered',
+ badges: [BADGE.DEV]
+ },
+};
+
+export const Basic = () => {
+ const [isSettingsOpen, setIsSettingsOpen] = React.useState(true);
+ const [openCollectiveEmail, setOpenCollectiveEmail] = React.useState(null);
+ const [openCollectiveEmailSubmitted, setOpenCollectiveEmailSubmitted] = React.useState(!!openCollectiveEmail);
+
+ const handleResetEmail = () => setOpenCollectiveEmail(null);
+ const handleChangeEmail = (e) => setOpenCollectiveEmail(e.target.value);
+ const handleSubmitEmail = () => setOpenCollectiveEmailSubmitted(true);
+
+ return (
+ isSettingsOpen ? setIsSettingsOpen(false)}
+ /> : setIsSettingsOpen(true)}>Open Settings
+ );
+};
\ No newline at end of file
diff --git a/src/js/components/concept/Settings/Settings.tsx b/src/js/components/concept/Settings/Settings.tsx
new file mode 100644
index 0000000000..106b27df75
--- /dev/null
+++ b/src/js/components/concept/Settings/Settings.tsx
@@ -0,0 +1,139 @@
+import styled from 'styled-components';
+import { Modal } from '@material-ui/core';
+import { Close, Block, CheckBox } from '@material-ui/icons';
+
+import { DOMRoot } from '@/utils/config';
+
+import { Input } from '@/Input';
+import { Overlay } from '@/Overlay';
+import { Button } from '@/Button';
+
+import { OpenCollectiveSettings } from './components/OpenCollectiveSettings';
+
+const SettingsWrap = styled(Overlay)`
+ width: 100%;
+ max-width: 900px;
+ display: flex;
+ margin: 4rem auto;
+ padding: 0 2rem;
+`;
+
+const SettingsHeader = styled.header`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+`;
+
+export const Settings = ({
+ openCollectiveEmail = "jeff@athens.org",
+ isUpdatingEmail = false,
+ handleUpdateEmail,
+ handleCloseSettings,
+}) => {
+ return (
+
+
+
+ Settings
+
+
+
+
+ {/*
+ {openCollectiveEmail}>) : (<> Not set>)}
+ // body={<>
+ //
+ // Submit
+ // Reset
+ // >}
+ // help={{openCollectiveEmail !== '' ? "You are using the free version of Athens. You are hosting your own data. Please be careful!" : "Thank you for supporting Athens! Backups are coming soon."}
}
+ />
+
+
+ //
+ // Submit
+ // Reset
+ // >}
+ // help={{openCollectiveEmail !== '' ? "You are using the free version of Athens. You are hosting your own data. Please be careful!" : "Thank you for supporting Athens! Backups are coming soon."}
}
+ />
+
+
+ //
+ // Submit
+ // Reset
+ // >}
+ // help={{openCollectiveEmail !== '' ? "You are using the free version of Athens. You are hosting your own data. Please be careful!" : "Thank you for supporting Athens! Backups are coming soon."}
}
+ />
+
+
+ //
+ // Submit
+ // Reset
+ // >}
+ // help={{openCollectiveEmail !== '' ? "You are using the free version of Athens. You are hosting your own data. Please be careful!" : "Thank you for supporting Athens! Backups are coming soon."}
}
+ />
+ */}
+
+
+
+ )
+};
\ No newline at end of file
diff --git a/src/js/components/concept/Settings/components/OpenCollectiveSettings.tsx b/src/js/components/concept/Settings/components/OpenCollectiveSettings.tsx
new file mode 100644
index 0000000000..ac93caa12c
--- /dev/null
+++ b/src/js/components/concept/Settings/components/OpenCollectiveSettings.tsx
@@ -0,0 +1,82 @@
+import React from 'react';
+import styled from 'styled-components';
+
+import { Check, Mail } from '@material-ui/icons';
+
+import { Button } from '@/Button';
+import { Input } from '@/Input';
+import * as Setting from './Setting';
+
+const Body = styled(Setting.Body)`
+ align-items: flex-start;
+ flex-direction: column;
+ display: flex;
+ gap: 0.5rem;
+`;
+
+const InputGroup = styled.div`
+ display: flex;
+`;
+
+export const OpenCollectiveSettings = ({
+ openCollectiveEmail,
+ handleUpdateEmail,
+ isUpdatingEmail,
+}) => {
+ const [fieldValue, setFieldValue] = React.useState(openCollectiveEmail);
+ const [hasEmailChanged, setHasEmailChanged] = React.useState(false);
+ const emailInputRef = React.useRef(null);
+
+ const handleResetField = () => {
+ setFieldValue(openCollectiveEmail);
+ emailInputRef.current.value = openCollectiveEmail;
+ }
+
+ React.useEffect(() => {
+ setHasEmailChanged(openCollectiveEmail !== fieldValue);
+ }, [openCollectiveEmail, fieldValue, setHasEmailChanged]);
+
+ return (
+
+
+ OpenCollective Email
+ {!!openCollectiveEmail
+ ? (<> {openCollectiveEmail}>)
+ : 'No email set.'}
+
+
+
+
+
+
+ Reset
+ setFieldValue(e.target.value)}
+ style={{ paddingRight: '4em' }}
+ />
+
+ handleUpdateEmail(fieldValue)}
+ >Save
+
+
+ {!!openCollectiveEmail ? "Thank you for supporting Athens! Backups are coming soon." : "You are using the free version of Athens. You are hosting your own data. Please be careful!"}
+
+
+
+ )
+};
\ No newline at end of file
diff --git a/src/js/components/concept/Settings/components/Setting.ts b/src/js/components/concept/Settings/components/Setting.ts
new file mode 100644
index 0000000000..2ea26a4fdb
--- /dev/null
+++ b/src/js/components/concept/Settings/components/Setting.ts
@@ -0,0 +1,63 @@
+import styled from 'styled-components';
+
+export const Wrap = styled.div`
+ border-top: 1px solid var(--border-color);
+ padding: 2rem 0;
+ line-height: 1.25;
+ display: grid;
+ grid-template-columns: 10rem 1fr;
+ grid-template-areas: 'header body';
+
+ &.disabled {
+ opacity: 0.5;
+ }
+`;
+
+export const Label = styled.label`
+ display: flex;
+ align-items: center;
+ font-weight: bold;
+ gap: 0.5rem;
+`
+
+export const Header = styled.header`
+ grid-area: header;
+ padding-bottom: 1rem;
+`;
+
+export const Title = styled.h3`
+ margin: 0;
+`;
+
+export const Body = styled.main`
+ grid-area: body;
+`;
+
+export const Glance = styled.span`
+ font-weight: normal;
+ opacity: var(--opacity-high);
+ font-size: 0.8em;
+ gap: 0.25em;
+
+ svg {
+ vertical-align: -0.25rem;
+ font-size: 1.5em;
+ }
+`;
+
+export const Details = styled.aside`
+ grid-area: details;
+ font-size: 0.8em;
+ padding-top: 0.5rem;
+
+ p {
+ margin: 0.25rem 0;
+
+ &:first-child {
+ margin-top: 0;
+ }
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+`;
\ No newline at end of file
diff --git a/src/js/components/concept/Settings/index.ts b/src/js/components/concept/Settings/index.ts
new file mode 100644
index 0000000000..5908d4cfb3
--- /dev/null
+++ b/src/js/components/concept/Settings/index.ts
@@ -0,0 +1,2 @@
+import { Settings } from './Settings';
+export { Settings };
\ No newline at end of file
diff --git a/src/js/components/concept/Welcome/Welcome.stories.tsx b/src/js/components/concept/Welcome/Welcome.stories.tsx
new file mode 100644
index 0000000000..985c0b1adf
--- /dev/null
+++ b/src/js/components/concept/Welcome/Welcome.stories.tsx
@@ -0,0 +1,58 @@
+import { Welcome } from './Welcome';
+import { BADGE, Storybook } from '@/utils/storybook';
+
+import { mockDatabases } from './mockData';
+
+export default {
+ title: 'Concepts/Welcome',
+ component: Welcome,
+ argTypes: {},
+ parameters: {
+ layout: 'fullscreen',
+ badges: [BADGE.DEV]
+ },
+ decorators: [(Story) => ]
+};
+
+const Template = (args) => ;
+
+export const Default = Template.bind({});
+Default.args = {
+ databases: mockDatabases.slice(0, 3),
+ currentDatabaseId: mockDatabases[0].id,
+};
+
+export const ManyDatabases = Template.bind({});
+ManyDatabases.args = {
+ databases: mockDatabases,
+ currentDatabaseId: mockDatabases[0].id,
+};
+
+export const AddDatabase = Template.bind({});
+AddDatabase.args = {
+ defaultView: 'add-database',
+ databases: mockDatabases,
+ currentDatabaseId: mockDatabases[0].id,
+};
+
+export const JoinDatabase = Template.bind({});
+JoinDatabase.args = {
+ defaultView: 'join-database',
+ databases: mockDatabases,
+ currentDatabaseId: mockDatabases[0].id,
+};
+
+export const NoRecentDatabases = Template.bind({});
+NoRecentDatabases.args = {
+ databases: mockDatabases.slice(0, 1),
+ currentDatabaseId: mockDatabases[0].id,
+};
+
+export const NoDatabases = Template.bind({});
+NoDatabases.args = {
+ defaultView: 'databases-list'
+};
+
+export const FirstRun = Template.bind({});
+FirstRun.args = {
+};
diff --git a/src/js/components/concept/Welcome/Welcome.tsx b/src/js/components/concept/Welcome/Welcome.tsx
new file mode 100644
index 0000000000..f0ba0647b1
--- /dev/null
+++ b/src/js/components/concept/Welcome/Welcome.tsx
@@ -0,0 +1,270 @@
+import React from "react";
+import styled, { keyframes } from "styled-components";
+
+import { AddBox, Wifi } from "@material-ui/icons";
+import { Slide, Fade } from "@material-ui/core";
+
+import { Button, ButtonWrap } from "@/Button";
+import { Overlay } from "@/Overlay";
+import { Input } from "@/Input";
+import { DatabasesList } from "./components/DatabasesList";
+import { AddDatabase } from "./components/AddDatabase";
+import { JoinDatabase } from "./components/JoinDatabase";
+
+const WelcomeWrap = styled(Overlay)`
+ height: min(90vh, 32rem);
+ width: min(90vw, 40rem);
+ display: flex;
+ padding: 0;
+ overflow: hidden;
+ flex-direction: row;
+ user-select: none;
+ margin: auto;
+ background: transparent;
+ border-radius: 1rem;
+`;
+
+const Sidebar = styled.aside`
+ display: flex;
+ flex-direction: column;
+ flex: 0 0 12em;
+ background: var(--background-plus-1);
+ border-top-left-radius: inherit;
+ border-bottom-left-radius: inherit;
+ --webkit-overflow-scrolling: touch;
+
+ @supports (backdrop-filter: blur(20px)) {
+ background: var(--background-color---opacity-high);
+ backdrop-filter: blur(20px);
+ }
+
+ header {
+ padding: 2rem 1rem;
+ display: flex;
+ flex-direction: column;
+ }
+`;
+
+export const Main = styled.main`
+ --main-padding-sides: 1rem;
+ flex: 1 1 100%;
+ background: var(--background-color);
+ display: grid;
+ grid-template-areas: "main";
+ overflow-y: auto;
+`;
+
+const Logo = styled.span`
+ padding: 0 1rem;
+ font-family: var(--font-family-serif);
+ color: var(--body-text-color---opacity-med);
+ font-weight: bold;
+ font-size: 2rem;
+`;
+
+const Version = styled.span`
+ color: var(--body-text-color---opacity-med);
+ font-size: var(--font-size--text-sm);
+ padding: 0 1rem;
+`;
+
+export const PageWrapper = styled(Overlay).attrs({
+ hasOutline: false,
+})`
+ grid-area: main;
+ display: flex;
+ margin: 1rem;
+ align-self: stretch;
+ justify-self: stretch;
+ flex-direction: column;
+ align-items: stretch;
+ min-height: calc(100% - 2rem);
+ height: max-content;
+`;
+
+const WelcomeActions = styled.div`
+ display: grid;
+ grid-auto-flow: row;
+ margin-top: auto;
+
+ ${ButtonWrap} {
+ justify-content: flex-start;
+ padding: 1rem 2rem;
+ border-radius: 0;
+ color: var(--body-text-color);
+ gap: 1.5ch;
+
+ svg {
+ position: relative;
+ margin-left: -0.75rem;
+ padding: 0.35rem;
+ display: inline-block;
+ width: 2rem;
+ height: 2rem;
+ background: var(--link-color);
+ color: var(--link-color---contrast);
+ border-radius: 10em;
+ }
+ }
+`;
+
+export const Actions = styled.div`
+ display: grid;
+ grid-auto-columns: 1fr;
+ grid-auto-flow: column;
+ gap: 1rem;
+ align-items: center;
+ justify-content: center;
+ flex: 0 0 auto;
+ padding: 1rem 0;
+ margin: auto auto 0;
+`;
+
+export const Header = styled.div`
+ flex: 0 0 auto;
+ display: flex;
+ align-items: center;
+ margin-bottom: auto;
+ padding: 0.5rem;
+ justify-content: flex-end;
+
+ ${ButtonWrap} {
+ color: var(--body-text-color---opacity-high);
+ font-size: var(--font-size--text-sm);
+ }
+`;
+
+export const pulseInputOutline = keyframes`
+ from {
+ box-shadow: 0 0 0 2px var(--link-color---opacity-med);
+ } to {
+ box-shadow: 0 0 0 2px var(--link-color);
+ }
+`;
+
+export const TextField = styled(Input)`
+ text-align: center;
+ border-radius: 100em;
+ transition: all 0.2s ease-in-out;
+
+ &:focus {
+ outline: none;
+ animation: ${pulseInputOutline} 1s ease-in-out alternate infinite;
+ }
+`;
+
+export const Heading = styled.h2`
+ font-size: 1.5rem;
+ color: var(--body-text-color---opacity-high);
+`;
+
+type WelcomeView = "databases-list" | "join-database" | "add-database";
+
+interface WelcomeProps {
+ defaultView: WelcomeView;
+ databases: Database[];
+ currentDatabaseId: string;
+ onLogin: (login) => void;
+ onCreateDatabase: (database: Database) => void;
+ onChooseDatabase: (database: Database) => void;
+ onDeleteDatabase: (database: Database) => void;
+ onRenameDatabase: (database: Database) => void;
+ onRemoveDatabase: (database: Database) => void;
+}
+
+export const Welcome = (props: WelcomeProps) => {
+ const {
+ defaultView = "databases-list",
+ currentDatabaseId,
+ databases,
+ onLogin: handleLogin,
+ onCreateDatabase: handleCreateDatabase,
+ onChooseDatabase: handleChooseDatabase,
+ onDeleteDatabase: handleDeleteDatabase,
+ onRemoveDatabase: handleRemoveDatabase,
+ onRenameDatabase: handleRenameDatabase,
+ } = props;
+
+ const [view, setView] = React.useState(defaultView);
+ const currentDatabase = databases?.find((db) => db.id === currentDatabaseId);
+ const recentDatabases = databases?.filter(
+ (db) => db.id !== currentDatabaseId
+ );
+
+ // Switch to Add Database if no databases exist and view hasn't been manually set
+ React.useEffect(() => {
+ if (!recentDatabases && !currentDatabaseId) {
+ setView("add-database");
+ }
+ }, [currentDatabaseId, recentDatabases]);
+
+ return (
+
+
+
+
+
+ setView("add-database")}
+ >
+
+ Add
+
+ setView("join-database")}
+ >
+
+ Join
+
+
+
+
+
+ setView("add-database")}
+ onNavToJoin={() => setView("join-database")}
+ />
+
+
+ setView("databases-list")}
+ onAddFromFile={(database) => console.log("add from file", database)}
+ onCreateDatabase={handleCreateDatabase}
+ />
+
+
+ setView("databases-list")}
+ onLogin={handleLogin}
+ />
+
+
+
+ );
+};
diff --git a/src/js/components/concept/Welcome/components/AddDatabase.tsx b/src/js/components/concept/Welcome/components/AddDatabase.tsx
new file mode 100644
index 0000000000..c761871753
--- /dev/null
+++ b/src/js/components/concept/Welcome/components/AddDatabase.tsx
@@ -0,0 +1,423 @@
+import React from "react";
+import styled, { css, keyframes } from "styled-components";
+
+import "emoji-picker-element";
+import { HexColorPicker } from "react-colorful";
+import { Popper, Modal } from "@material-ui/core";
+import { Cancel, AddFolder, Folder } from "iconoir-react";
+
+import { DOMRoot } from "@/utils/config";
+import { DatabaseIcon } from "@/concept/DatabaseIcon";
+import { Button } from "@/Button";
+import { Overlay } from "@/Overlay";
+import { useMenu } from "@/Menu/hooks/useMenu";
+import { Input } from "@/Input";
+
+import { Heading, Header, Actions, PageWrapper } from "../Welcome";
+
+const pulseInputOutline = keyframes`
+ from {
+ box-shadow: 0 0 0 2px var(--link-color---opacity-med);
+ } to {
+ box-shadow: 0 0 0 2px var(--link-color);
+ }
+`;
+
+const pulseColorCaret = keyframes`
+ from {
+ transform: translate(-50%, -50%) scale(1) ;
+ } to {
+ transform: translate(-50%, -50%) scale(1.08);
+ }
+`;
+
+const DatabaseNameField = styled(Input)`
+ text-align: center;
+ border-radius: 100em;
+ transition: all 0.2s ease-in-out;
+
+ &:focus {
+ outline: none;
+ animation: ${pulseInputOutline} 1s ease-in-out alternate infinite;
+ }
+`;
+
+const Preview = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 1rem;
+ gap: 0.5rem;
+ flex: 0 0 auto;
+ margin-top: auto;
+
+ ${DatabaseIcon.Wrap} {
+ --size: 5em;
+ border-radius: 18%;
+ box-shadow: 0 0.5rem 1.5rem -0.5rem var(--shadow-color);
+ }
+`;
+
+const EmojiPickerOverlay = styled(Overlay)`
+ flex-direction: column;
+
+ emoji-picker {
+ --background: var(--background-plus-1);
+ --border-color: var(--body-text-color---opacity-low);
+ --indicator-color: var(--link-color);
+ --input-border-color: transparent;
+ --input-font-color: var(--body-text-color);
+ --input-placeholder-color: var(--body-text-color---opacity-med);
+ --outline-color: transparent;
+ --category-font-color: var(--body-text-color);
+ --button-active-background: var(--body-text-color---opacity-med);
+ --button-hover-background: var(--body-text-color---opacity-10);
+ }
+`;
+
+const PathInput = styled.input.attrs({
+ type: "file",
+ webkitdirectory: true,
+ directory: true,
+})`
+ visibility: hidden;
+ position: absolute;
+`;
+
+const PathPlaceholder = styled.span`
+ opacity: 0.5;
+ text-decoration: underline;
+`;
+
+const PathPicker = styled.label`
+ display: grid;
+ padding: 0.25rem 1rem;
+ grid-template-areas: "main";
+ place-content: center;
+ place-items: center;
+ cursor: pointer;
+ transition: background 0.2s ease-in-out;
+
+ &:hover {
+ border-radius: 100em;
+ background-color: var(--background-plus-2---opacity-med);
+ }
+
+ > svg {
+ grid-area: main;
+ margin: auto auto auto 0;
+ pointer-events: none;
+ font-size: var(--font-size--text-xs);
+ color: var(--body-text-color---opacity-med);
+ }
+
+ span {
+ padding-left: 1.5rem;
+ grid-area: main;
+ font-size: 0.8rem;
+ }
+`;
+
+const Extras = styled.fieldset`
+ display: flex;
+ align-items: stretch;
+ margin: 1rem auto auto;
+ justify-content: center;
+ gap: 1rem;
+ padding: 1rem 1rem 0.5rem;
+ background: var(--background-color);
+ border: 1px solid var(--border-color);
+ border-radius: 1rem;
+`;
+
+const IconButton = styled(Button)`
+ height: 3rem;
+ width: 3rem;
+ border-radius: 0.5rem;
+ padding: 0;
+ place-content: center;
+ place-items: center;
+ background: var(--background-plus-1);
+ border: 1px solid var(--border-color);
+ font-size: 2em;
+ will-change: transform;
+
+ span {
+ opacity: 0.5;
+ flex: 0 0 auto;
+ filter: grayscale(100%);
+ transform: scale(0.9);
+ transition: filter 0.1s ease-in-out, opacity 0.1s ease-in-out,
+ transform 0.15s ease-in;
+ }
+
+ &:hover,
+ &:focus,
+ &.hasIcon {
+ span {
+ opacity: 1;
+ filter: none;
+ transform: scale(1);
+ }
+ }
+`;
+
+const Path = styled.span`
+ font-size: 0.8rem;
+ grid-area: main;
+`;
+
+const ColorPickerWrap = styled.div`
+ flex: 1 1 100%;
+
+ .react-colorful {
+ width: 9rem;
+ height: 100%;
+ gap: 1rem;
+ margin: 0;
+ flex-direction: row;
+
+ > * {
+ border-radius: 0.5rem;
+ height: 100%;
+ flex: 0 0 4rem;
+ }
+ }
+
+ .react-colorful__saturation {
+ border-bottom: 0;
+ }
+
+ .react-colorful__interactive:focus .react-colorful__pointer {
+ animation: ${pulseColorCaret} 0.5s infinite alternate ease-in-out;
+ }
+`;
+
+const ControlWrap = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.125rem;
+`;
+
+const ControlLabel = styled.span`
+ font-size: var(--font-size--text-xs);
+ color: var(--body-text-color---opacity-high);
+`;
+
+const EmptyDatabase = {
+ id: "",
+ name: "New Database",
+ isRemote: false,
+ icon: "",
+ color: "#006cef",
+};
+
+const RemoveEmojiButton = styled(Button)`
+ justify-items: center;
+ gap: 0;
+
+ span {
+ flex: 0 0 auto;
+ }
+`;
+
+const Picker = ({ setDatabaseIcon, closeMenu }) => {
+ const ref = React.useRef(null);
+
+ React.useEffect(() => {
+ ref.current.addEventListener("emoji-click", (event) => {
+ setDatabaseIcon(event.detail.emoji.unicode);
+ closeMenu();
+ });
+ ref.current.skinToneEmoji = "👍";
+ }, []);
+
+ React.useEffect(() => {
+ const picker = document.getElementsByTagName("emoji-picker")[0];
+ const style = document.createElement("style");
+ style.textContent = css`
+ .picker {
+ border: 0;
+ }
+ input.search {
+ border: 0;
+ background: var(--background-color);
+ font-family: var(--font-family-default);
+ color: var(--body-text-color---opacity-high);
+ padding-left: 0.5rem;
+ border-radius: 0.25rem;
+ }
+ input.search:focus {
+ border-radius: 0.25rem;
+ outline: none;
+ }
+
+ .indicator {
+ background-color: var(--link-color);
+ }
+ `;
+ picker.shadowRoot.appendChild(style)!;
+ }, []);
+
+ return React.createElement("emoji-picker", { ref });
+};
+
+export interface AddDatabaseProps {
+ onAddFromFile: (database: Database) => void;
+ onCreateDatabase: (database: Database) => void;
+ onGoBack: () => void;
+}
+
+export const AddDatabase = React.forwardRef(
+ (props: AddDatabaseProps, ref): JSX.Element => {
+ const {
+ onAddFromFile: handleAddFromFile,
+ onCreateDatabase: handleCreateDatabase,
+ onGoBack: handleGoBack,
+ } = props;
+ const [database, setDatabase] = React.useState(EmptyDatabase);
+ const { triggerProps, menuProps, closeMenu } = useMenu();
+
+ const setDatabaseIcon = (emoji) =>
+ setDatabase({
+ ...database,
+ icon: emoji,
+ });
+
+ const handleClearDatabaseIcon = () =>
+ setDatabase({
+ ...database,
+ icon: null,
+ });
+
+ const handleChangeDatabaseColor = (color) => {
+ setDatabase({
+ ...database,
+ color: color,
+ });
+ };
+
+ const handleChangeDatabasePath = (e) => {
+ const path = e.target.value;
+ setDatabase({
+ ...database,
+ id: path,
+ });
+ };
+
+ return (
+
+
+ handleAddFromFile(database)}>
+
+ Add From File
+
+
+
+ Create a New Database
+
+
+ setDatabase({ ...database, name: e.target.value.trim() })
+ }
+ />
+
+
+
+ {database.id ? (
+ {database.id}
+ ) : (
+ Choose a location
+ )}
+
+
+
+
+ {database.icon ? database.icon : "📚️"}
+
+ Icon
+
+
+
+
+
+ Color
+
+
+
+
+
+ Cancel
+
+ handleCreateDatabase(database)}
+ >
+ Create
+
+
+
+ {menuProps.isOpen && (
+
+
+
+
+ {
+ handleClearDatabaseIcon(), closeMenu();
+ }}
+ >
+
+ Clear
+
+
+
+
+ )}
+
+ );
+ }
+);
diff --git a/src/js/components/concept/Welcome/components/DatabasesList.tsx b/src/js/components/concept/Welcome/components/DatabasesList.tsx
new file mode 100644
index 0000000000..dd443c0f37
--- /dev/null
+++ b/src/js/components/concept/Welcome/components/DatabasesList.tsx
@@ -0,0 +1,199 @@
+import React from "react";
+import styled from "styled-components";
+
+import { Item } from "./Item";
+import { SkeletonItem } from "./SkeletonItem";
+
+const Wrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ grid-area: main;
+ background: inherit;
+ position: relative;
+ z-index: 0;
+ padding: 1rem;
+`;
+
+const Heading = styled.h2`
+ font-size: var(--font-size--text-sm);
+ letter-spacing: 0.03em;
+ margin-top: 1rem;
+ padding: 0.25rem calc(var(--main-padding-sides) + 0.375rem);
+ color: var(--body-text-color---opacity-med);
+ background: var(--background-color---opacity-high);
+ position: sticky;
+ top: 0rem;
+ margin-left: 1px;
+ margin-right: var(--main-padding-sides);
+ backdrop-filter: blur(12px);
+ border-top-right-radius: inherit;
+ z-index: 9999;
+`;
+
+const Section = styled.section`
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ position: relative;
+`;
+
+const List = styled.ol`
+ padding: 0 var(--main-padding-sides);
+ margin: 0;
+ list-style: none;
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ gap: 0.5rem;
+`;
+
+const Message = styled.div`
+ border-radius: 1rem;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ place-content: center;
+ background: var(--background-plus-2---opacity-10);
+ z-index: 10;
+ position: absolute;
+ padding: 2rem 1rem;
+ text-align: center;
+ color: var(--body-text-color---opacity-med);
+ backdrop-filter: blur(12px);
+ width: 16rem;
+ gap: 1rem;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+
+ p {
+ margin: 0;
+ line-height: 1.4;
+ }
+`;
+
+const TextButton = styled.button`
+ display: inline;
+ appearance: none;
+ background: none;
+ border: none;
+ padding: 0;
+ font-family: inherit;
+ font-size: inherit;
+ font-weight: inherit;
+ line-height: inherit;
+ color: var(--link-color);
+ cursor: pointer;
+
+ &:hover {
+ text-decoration: underline;
+ }
+`;
+
+const NoDatabasesMessage = ({ onNavToAdd, onNavToJoin }) =>
+
+ No graphs found.
+ You probably want to Add or Join a graph.
+ ;
+
+const NoRecentDatabasesMessage = () =>
+
+ Create new graphs to keep notes completely separated.
+ Join shared graphs to collaborate with your team.
+ ;
+
+interface DatabasesListProps {
+ currentDatabase: Database;
+ recentDatabases: Database[];
+ onNavToAdd: () => void;
+ onNavToJoin: () => void;
+ onDeleteDatabase: (database: Database) => void;
+ onRenameDatabase: (database: Database) => void;
+ onChooseDatabase: (database: Database) => void;
+ onRemoveDatabase: (database: Database) => void;
+}
+
+export const DatabasesList = React.forwardRef(
+ (props: DatabasesListProps, ref): JSX.Element => {
+ const {
+ currentDatabase,
+ recentDatabases,
+ onNavToAdd,
+ onNavToJoin,
+ onDeleteDatabase: handleDeleteDatabase,
+ onRenameDatabase: handleRenameDatabase,
+ onRemoveDatabase: handleRemoveDatabase,
+ onChooseDatabase: handleChooseDatabase,
+ } = props;
+
+ return (
+
+ {!currentDatabase && !recentDatabases?.length ? (
+
+ ) : (
+ <>
+ {currentDatabase && (
+
+ )}
+ {recentDatabases?.length > 0 ? (
+
+ Recent
+
+ {recentDatabases.map((db) => (
+
+ ))}
+
+
+ ) : (
+
+ Recent
+
+
+
+
+
+
+
+
+
+
+ )}
+ >
+ )}
+
+ );
+ }
+);
diff --git a/src/js/components/concept/Welcome/components/Item.tsx b/src/js/components/concept/Welcome/components/Item.tsx
new file mode 100644
index 0000000000..77bb0aeca6
--- /dev/null
+++ b/src/js/components/concept/Welcome/components/Item.tsx
@@ -0,0 +1,219 @@
+import React from "react";
+import styled from "styled-components";
+
+import { DOMRoot } from "@/utils/config";
+import { Popper, Modal, PopperPlacementType } from "@material-ui/core";
+import { OfflineBolt } from "@material-ui/icons";
+
+import { Menu } from "@/Menu";
+import { Overlay } from "@/Overlay";
+import { Button } from "@/Button";
+import { DatabaseIcon } from "@/concept/DatabaseIcon";
+import { useMenu } from "@/Menu/hooks/useMenu";
+
+const EditButton = styled(Button)`
+ border-radius: 100em;
+ padding: 0.5rem 1rem;
+ font-size: var(--font-size--text-sm);
+ opacity: 0;
+ grid-area: main;
+ margin-left: auto;
+ margin: auto 0.25rem auto auto;
+ justify-self: center;
+ align-self: flex-end;
+ position: relative;
+ z-index: 2;
+
+ &:focus-visible,
+ &[aria-pressed="true"] {
+ opacity: 1;
+ }
+`;
+
+const ItemWrap = styled.li`
+ align-self: stretch;
+ display: flex;
+ align-items: stretch;
+ justify-content: stretch;
+ display: grid;
+ grid-template-areas: "main";
+
+ &.is-current {
+ background-color: var(--background-plus-2);
+ border-radius: 0.5rem;
+ box-shadow: 0 0.25rem 0.5rem var(--shadow-color---opacity-15);
+ }
+
+ &:hover {
+ ${EditButton} {
+ opacity: 1;
+ }
+ }
+`;
+
+const ItemButton = styled(Button)`
+ grid-area: main;
+ flex-direction: column;
+ align-items: stretch;
+ gap: 0;
+ flex: 1 1 100%;
+ display: grid;
+ justify-items: stretch;
+ text-align: left;
+ grid-template-areas: "icon name status" "icon detail status";
+ grid-template-columns: auto 1fr;
+ gap: 0 0.125rem;
+ border-radius: 0.5rem;
+ position: relative;
+ z-index: 1;
+ padding-right: 4rem;
+
+ &[aria-pressed="true"] {
+ background: var(--background-minus-2);
+ }
+
+ ${DatabaseIcon.Wrap} {
+ grid-area: icon;
+ grid-row: 1 / -1;
+ margin: auto;
+ }
+`;
+
+const Name = styled.h3`
+ margin: 0;
+ display: flex;
+ grid-area: name;
+ justify-self: stretch;
+ font-weight: normal;
+ font-size: var(--font-size--text-base);
+ overflow: hidden;
+ color: inherit;
+`;
+
+const Detail = styled.span`
+ grid-area: detail;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-size: var(--font-size--text-xs);
+ opacity: var(--opacity-med);
+ color: inherit;
+ overflow: hidden;
+`;
+
+const Status = styled.span`
+ align-self: center;
+ grid-area: status;
+ grid-row: 1 / -1;
+`;
+
+interface ItemProps {
+ isCurrentDatabase?: boolean;
+ db: Database;
+ onChooseDatabase?: (db: Database) => void;
+ onRemoveDatabase?: (db: Database) => void;
+ onRenameDatabase?: (db: Database) => void;
+ onDeleteDatabase?: (db: Database) => void;
+}
+
+export const Item = ({
+ isCurrentDatabase = false,
+ db,
+ onChooseDatabase,
+ onRemoveDatabase,
+ onRenameDatabase,
+ onDeleteDatabase,
+}: ItemProps): React.ReactElement => {
+ const { triggerProps, menuProps, closeMenu } = useMenu();
+
+ return (
+ <>
+
+ onChooseDatabase && onChooseDatabase(db)}
+ >
+
+ {db.name}
+ {db.id}
+ {/* TODO: Tooltip or help text when trying to interact with an offline DB */}
+ {db.status === "offline" && (
+
+
+
+ )}
+
+
+ Edit
+
+
+ {menuProps.isOpen && (
+
+
+
+
+ {onChooseDatabase && (
+ {
+ onChooseDatabase(db);
+ closeMenu();
+ }}
+ >
+ Open
+
+ )}
+ {onRemoveDatabase && (
+ {
+ onRemoveDatabase(db);
+ closeMenu();
+ }}
+ >
+ Remove
+
+ )}
+ {onRenameDatabase && (
+ {
+ onRenameDatabase(db);
+ closeMenu();
+ }}
+ >
+ Rename
+
+ )}
+
+ {onDeleteDatabase && (
+ {
+ onDeleteDatabase(db);
+ closeMenu();
+ }}
+ >
+ Delete
+
+ )}
+
+
+
+
+ )}
+ >
+ );
+};
diff --git a/src/js/components/concept/Welcome/components/JoinDatabase.tsx b/src/js/components/concept/Welcome/components/JoinDatabase.tsx
new file mode 100644
index 0000000000..ab48796508
--- /dev/null
+++ b/src/js/components/concept/Welcome/components/JoinDatabase.tsx
@@ -0,0 +1,97 @@
+import React from "react";
+import styled from "styled-components";
+
+import { Button } from "@/Button";
+import { Heading, Actions, PageWrapper, TextField } from "../Welcome";
+
+import { Plus, Wifi } from "iconoir-react";
+
+const Preview = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 1rem;
+ gap: 0.5rem;
+ flex: 0 0 auto;
+ margin: auto;
+`;
+
+const IconWrap = styled.div`
+ width: 5em;
+ height: 5em;
+ border-radius: 18%;
+ margin: 0 auto 1rem;
+ border: 2px solid var(--link-color);
+ display: flex;
+ place-content: center;
+ place-items: center;
+ color: var(--link-color);
+ position: relative;
+
+ svg {
+ position: absolute;
+ width: 4rem;
+ height: 4rem;
+
+ + svg {
+ bottom: -10%;
+ right: -10%;
+ width: 1.75rem;
+ height: 1.75rem;
+ background: var(--link-color);
+ color: var(--link-color---contrast);
+ border-radius: 1000em;
+ }
+ }
+`;
+
+const EmptyLogin = { address: "", password: "" };
+
+export interface JoinDatabaseProps {
+ onLogin: (login) => void;
+ onGoBack: () => void;
+}
+
+export const JoinDatabase = React.forwardRef(
+ (props: JoinDatabaseProps, ref): JSX.Element => {
+ const { onLogin: handleLogin, onGoBack: handleGoBack } = props;
+ const [login, setLogin] = React.useState(EmptyLogin);
+
+ return (
+
+
+ Join a Shared Database
+
+
+
+
+
+ setLogin({ ...login, address: e.target.value.trim() })
+ }
+ />
+ setLogin({ ...login, password: e.target.value })}
+ />
+
+
+ handleGoBack()}>
+ Cancel
+
+ handleLogin(login)}
+ >
+ Join
+
+
+
+ );
+ }
+);
diff --git a/src/js/components/concept/Welcome/components/SkeletonItem.tsx b/src/js/components/concept/Welcome/components/SkeletonItem.tsx
new file mode 100644
index 0000000000..ee497f5bf3
--- /dev/null
+++ b/src/js/components/concept/Welcome/components/SkeletonItem.tsx
@@ -0,0 +1,95 @@
+import React from "react";
+import styled from "styled-components";
+
+import { Button } from "@/Button";
+
+const ItemWrap = styled.li`
+ align-self: stretch;
+ display: flex;
+ align-items: stretch;
+ justify-content: stretch;
+ display: grid;
+ grid-template-areas: "main";
+
+ &:nth-child(2) {
+ opacity: 0.8;
+ }
+ &:nth-child(3) {
+ opacity: 0.6;
+ }
+ &:nth-child(4) {
+ opacity: 0.4;
+ }
+ &:nth-child(5) {
+ opacity: 0.2;
+ }
+ &:nth-child(6) {
+ opacity: 0.1;
+ }
+`;
+
+const ItemButton = styled(Button)`
+ grid-area: main;
+ flex-direction: column;
+ align-items: stretch;
+ gap: 0;
+ flex: 1 1 100%;
+ display: grid;
+ justify-items: stretch;
+ text-align: left;
+ grid-template-areas: "icon name status" "icon detail status";
+ grid-template-columns: auto 1fr;
+ gap: 0 0.125rem;
+ border-radius: 0.5rem;
+ position: relative;
+ z-index: 1;
+ padding-right: 4rem;
+ background: var(--background-plus-1---opacity-med);
+`;
+
+const Name = styled.h3`
+ margin: 0;
+ display: flex;
+ grid-area: name;
+ justify-self: stretch;
+ width: 50%;
+ height: .9rem;
+ border-radius: 0.25rem;
+ background: var(--background-plus-2---opacity-med);
+`;
+
+const Detail = styled.span`
+ grid-area: detail;
+ width: 100%;
+ height: .9rem;
+ align-self: flex-end;
+ border-radius: 0.25rem;
+ background: var(--background-plus-2---opacity-med);
+`;
+
+const Icon = styled.div`
+ width: 2em;
+ height: 2em;
+ border-radius: 18%;
+ background: var(--background-plus-2---opacity-med);
+ grid-area: icon;
+ grid-row: 1 / -1;
+ margin: auto;
+`;
+
+interface SkeletonItemProps { }
+
+export const SkeletonItem = (props: SkeletonItemProps): React.ReactElement => {
+ return (
+ <>
+
+
+
+
+
+ {/* TODO: Tooltip or help text when trying to interact with an offline DB */}
+
+
+ >
+ );
+};
diff --git a/src/js/components/concept/Welcome/index.ts b/src/js/components/concept/Welcome/index.ts
new file mode 100644
index 0000000000..e5020bba12
--- /dev/null
+++ b/src/js/components/concept/Welcome/index.ts
@@ -0,0 +1,2 @@
+import { Welcome } from './Welcome';
+export { Welcome };
\ No newline at end of file
diff --git a/src/js/components/concept/Welcome/mockData.ts b/src/js/components/concept/Welcome/mockData.ts
new file mode 100644
index 0000000000..2233fe4569
--- /dev/null
+++ b/src/js/components/concept/Welcome/mockData.ts
@@ -0,0 +1,195 @@
+export const synced = false;
+
+export const mockDatabases = [{
+ name: "Duke Energy Corporation",
+ id: "/penatibus/et/magnis/dis/parturient.js",
+ color: "#96c622",
+ icon: "📔",
+ isRemote: true
+}, {
+ name: "Lumber Liquidators Holdings, Inc",
+ id: "/morbi/non/lectus.aspx",
+ color: "#3ab348",
+ icon: "🎒",
+ isRemote: true
+}, {
+ name: "RELX N.V.",
+ id: "/libero/non/mattis/pulvinar/nulla/pede.js",
+ color: "#0215d1",
+ icon: "📚",
+ isRemote: true
+}, {
+ name: "Laredo Petroleum, Inc.",
+ id: "/vestibulum/sit.jsp",
+ color: "#209adc",
+ icon: "💻",
+ isRemote: false
+}, {
+ name: "Turquoise Hill Resources Ltd.",
+ id: "/morbi/vestibulum/velit/id/pretium/iaculis.jsp",
+ color: "#8c3a6a",
+ icon: null,
+ isRemote: false
+}, {
+ name: "Timken Company (The)",
+ id: "/egestas/metus/aenean/fermentum/donec.json",
+ color: "#0c1050",
+ icon: "📖",
+ isRemote: false
+}, {
+ name: "Thor Industries, Inc.",
+ id: "/dui/nec/nisi/volutpat/eleifend.jpg",
+ color: "#7bd0b1",
+ icon: null,
+ isRemote: false
+}, {
+ name: "Enterprise Bancorp Inc",
+ id: "/sociis/natoque/penatibus/et/magnis/dis/parturient.js",
+ color: "#f7b0aa",
+ icon: "💻",
+ isRemote: true
+}, {
+ name: "Qwest Corporation",
+ id: "/dapibus/dolor/vel/est.xml",
+ color: "#092888",
+ icon: null,
+ isRemote: false
+}, {
+ name: "Capitala Finance Corp.",
+ id: "/sed.aspx",
+ color: "#0583ff",
+ icon: "📘",
+ isRemote: true
+}, {
+ name: "uniQure N.V.",
+ id: "/natoque/penatibus.js",
+ color: "#aa3dd0",
+ icon: null,
+ isRemote: false
+}, {
+ name: "HDFC Bank Limited",
+ id: "/consequat/dui/nec/nisi/volutpat/eleifend/donec.js",
+ color: "#2ca775",
+ icon: "📓",
+ isRemote: false
+}, {
+ name: "Choice Hotels International, Inc.",
+ id: "/nulla/suscipit/ligula/in/lacus/curabitur.aspx",
+ color: "#660751",
+ icon: "💻",
+ isRemote: true
+}, {
+ name: "PennantPark Investment Corporation",
+ id: "/nec/euismod/scelerisque/quam.xml",
+ color: "#ec2f42",
+ icon: "🔖",
+ isRemote: true
+}, {
+ name: "AMN Healthcare Services Inc",
+ id: "/velit/nec/nisi/vulputate/nonummy/maecenas/tincidunt.aspx",
+ color: "#4eb180",
+ icon: "📓",
+ isRemote: true
+}, {
+ name: "Gabelli Utility Trust (The)",
+ id: "/amet.jsp",
+ color: "#60ebea",
+ icon: "💁",
+ isRemote: true
+}, {
+ name: "Champions Oncology, Inc.",
+ id: "/sem/fusce/consequat/nulla/nisl/nunc/nisl.jsp",
+ color: "#69e3a1",
+ icon: "💁",
+ isRemote: true
+}, {
+ name: "OncoGenex Pharmaceuticals Inc.",
+ id: "/leo/odio/condimentum/id/luctus/nec/molestie.jpg",
+ color: "#d937f2",
+ icon: "🔖",
+ isRemote: false
+}, {
+ name: "Ollie's Bargain Outlet Holdings, Inc.",
+ id: "/cras/non/velit.aspx",
+ color: "#0703b7",
+ icon: "📗",
+ isRemote: true
+}, {
+ name: "Viking Therapeutics, Inc.",
+ id: "/volutpat/dui/maecenas/tristique.xml",
+ color: "#939adf",
+ icon: "🎒",
+ isRemote: false
+}, {
+ name: "American Financial Group, Inc.",
+ id: "/vivamus/vestibulum/sagittis/sapien/cum.html",
+ color: "#3f38ec",
+ icon: "💁",
+ isRemote: false
+}, {
+ name: "Harmony Merger Corp.",
+ id: "/justo/in/hac/habitasse/platea.aspx",
+ color: "#43f9f1",
+ icon: "📗",
+ isRemote: false
+}, {
+ name: "Boulevard Acquisition Corp. II",
+ id: "/odio/elementum/eu/interdum/eu.js",
+ color: "#b161e8",
+ icon: "😎",
+ isRemote: false
+}, {
+ name: "KNOT Offshore Partners LP",
+ id: "/dolor/morbi/vel/lectus/in/quam/fringilla.xml",
+ color: "#f79c0e",
+ icon: "📖",
+ isRemote: false
+}, {
+ name: "Buckle, Inc. (The)",
+ id: "/velit/id/pretium/iaculis/diam.xml",
+ color: "#683f8e",
+ icon: null,
+ isRemote: true
+}, {
+ name: "NeoGenomics, Inc.",
+ id: "/volutpat/quam/pede/lobortis/ligula.aspx",
+ color: "#1c7dbf",
+ icon: "📓",
+ isRemote: false
+}, {
+ name: "J. Alexander's Holdings, Inc.",
+ id: "/sagittis/nam/congue.png",
+ color: "#0240e7",
+ icon: "🔖",
+ isRemote: false
+}, {
+ name: "Haynes International, Inc.",
+ id: "/massa/quis.xml",
+ color: "#a7cbf9",
+ icon: "💁",
+ isRemote: false
+}, {
+ name: "Surmodics, Inc.",
+ id: "/convallis/eget/eleifend/luctus/ultricies/eu.jpg",
+ color: "#c8c9e0",
+ icon: "📖",
+ isRemote: false
+}, {
+ name: "First Trust RBA Quality Income ETF",
+ id: "/nisi/at/nibh/in.html",
+ color: "#ab583c",
+ icon: "📖",
+ isRemote: true
+}, {
+ name: "Colonial Municipal Income Trust",
+ id: "/cursus.jpg",
+ color: "#781854",
+ icon: null,
+ isRemote: false
+}, {
+ name: "Bridgepoint Education, Inc.",
+ id: "/pellentesque/at/nulla/suspendisse/potenti/cras/in.png",
+ color: "#c04dd0",
+ icon: "📖",
+ isRemote: false
+}]
diff --git a/src/js/components/utils/classnames.ts b/src/js/components/utils/classnames.ts
new file mode 100644
index 0000000000..109918b7f0
--- /dev/null
+++ b/src/js/components/utils/classnames.ts
@@ -0,0 +1 @@
+export const classnames = (...classes) => classes.filter(Boolean).join(' ');
\ No newline at end of file
diff --git a/src/js/components/utils/config.ts b/src/js/components/utils/config.ts
new file mode 100644
index 0000000000..6455f30b7d
--- /dev/null
+++ b/src/js/components/utils/config.ts
@@ -0,0 +1,4 @@
+
+export const preferredDateFormat = { month: 'long', day: 'numeric' };
+
+export const DOMRoot = document.querySelector("body");
diff --git a/src/js/components/utils/data/People.ts b/src/js/components/utils/data/People.ts
new file mode 100644
index 0000000000..06316e165b
--- /dev/null
+++ b/src/js/components/utils/data/People.ts
@@ -0,0 +1,98 @@
+export const people =
+ [{
+ username: "oorgen0",
+ personId: "5058615e-b5ce-4e48-9a70-1bb48079883f",
+ color: "#accb85"
+ }, {
+ username: "pcanepe1",
+ personId: "a22896b5-0cc0-40de-ae64-7105106beb6a",
+ color: "#12b47e"
+ }, {
+ username: "jthorrold2",
+ personId: "54f83c31-ac3b-412c-b02a-794db3cbff1d",
+ color: "#c542ed"
+ }, {
+ username: "afick3",
+ personId: "2bb8342d-e276-4333-8bc8-01201adf7778",
+ color: "#7920ef"
+ }, {
+ username: "nhannah4",
+ personId: "3c48194e-6506-402f-a17b-e979b3ae6026",
+ color: "#980e37"
+ }, {
+ username: "dmcentegart5",
+ personId: "648994a7-4970-4418-85f5-6710872e4dd2",
+ color: "#3d53cb"
+ }, {
+ username: "tclimar6",
+ personId: "62201231-bcdd-4b91-9d7e-d3869bfa8c5f",
+ color: "#594977"
+ }, {
+ username: "gchivrall7",
+ personId: "fd37b6e2-1b9d-4bbd-898c-fc4981835a59",
+ color: "#4fff47"
+ }, {
+ username: "dthomel8",
+ personId: "f452b384-79e3-4850-b077-770155cbf5ee",
+ color: "#8e9c1a"
+ }, {
+ username: "blinfoot9",
+ personId: "c547ffaf-63cb-4c45-b8b4-517c46c8eaa5",
+ color: "#696cf2"
+ }, {
+ username: "phardsona",
+ personId: "390cc8be-0b09-41e5-afa5-341dc69f03c4",
+ color: "#b33a7c"
+ }, {
+ username: "cdowneb",
+ personId: "dd4a68be-1a92-4afe-856c-7dc4ec4b58b4",
+ color: "#0f5dfe"
+ }, {
+ username: "ahortopc",
+ personId: "41a06c67-eb68-465c-8190-93a2c9a20875",
+ color: "#e8487d"
+ }, {
+ username: "osuthereld",
+ personId: "63d40d9a-1615-44c7-aa3b-9a910cc1aabb",
+ color: "#4f0d1a"
+ }, {
+ username: "mgrimblebye",
+ personId: "e0357779-edf2-490e-9a43-c4918d204e3b",
+ color: "#77b002"
+ }, {
+ username: "ccoochf",
+ personId: "3d2efbf6-3f43-4fa8-9136-b9d1fb7b7615",
+ color: "#803b68"
+ }, {
+ username: "anasseyg",
+ personId: "184ffca6-8acb-41cb-ae0e-9683dc234cc7",
+ color: "#3250c9"
+ }, {
+ username: "slasselleh",
+ personId: "a590cf82-2d5b-49a6-a625-cb2d98022a3c",
+ color: "#257179"
+ }, {
+ username: "ilerouxi",
+ personId: "aaccec70-7b66-40fe-8186-cbf5660282e7",
+ color: "#096908"
+ }, {
+ username: "fdransfieldj",
+ personId: "769e3b69-7270-43fc-b3fd-0c5401a352c8",
+ color: "#e75898"
+ }, {
+ username: "dsteinhamk",
+ personId: "ab64c0e7-4d2d-415d-8b14-6996d363bf10",
+ color: "#dee1fd"
+ }, {
+ username: "glouisetl",
+ personId: "2886b735-2104-4715-a717-e7041462d507",
+ color: "#b52850"
+ }, {
+ username: "jprozesckym",
+ personId: "ee321686-ad80-4796-b640-008a3fcb88e8",
+ color: "#228c57"
+ }, {
+ username: "pdrucen",
+ personId: "beb6085e-1437-438a-a1d6-933a0f9fa870",
+ color: "#497828"
+ }];
diff --git a/src/js/components/utils/data/PeoplePresence.ts b/src/js/components/utils/data/PeoplePresence.ts
new file mode 100644
index 0000000000..a2a93463da
--- /dev/null
+++ b/src/js/components/utils/data/PeoplePresence.ts
@@ -0,0 +1,3 @@
+import { people } from './people';
+
+export const peoplePresence = people.map((p, index) => ({ ...p, uid: index.toString() }))
\ No newline at end of file
diff --git a/src/js/components/utils/getOs.ts b/src/js/components/utils/getOs.ts
new file mode 100644
index 0000000000..d74eecb283
--- /dev/null
+++ b/src/js/components/utils/getOs.ts
@@ -0,0 +1,9 @@
+export const getOs = (window) => {
+ if (window.navigator.appVersion.includes('Windows')) {
+ return 'windows'
+ } else if (window.navigator.appVersion.includes('Mac')) {
+ return 'mac'
+ } else if (window.navigator.appVersion.includes('Linux')) {
+ return 'linux'
+ }
+}
\ No newline at end of file
diff --git a/src/js/components/utils/interfaces.ts b/src/js/components/utils/interfaces.ts
index 37943850b6..c398aa6a77 100644
--- a/src/js/components/utils/interfaces.ts
+++ b/src/js/components/utils/interfaces.ts
@@ -94,4 +94,4 @@ type Block = {
type BlockGraph = {
tree: any[],
blocks: any,
-};
\ No newline at end of file
+};
diff --git a/src/js/components/utils/storybook.tsx b/src/js/components/utils/storybook.ts
similarity index 51%
rename from src/js/components/utils/storybook.tsx
rename to src/js/components/utils/storybook.ts
index f89d11bc74..9cd265f654 100644
--- a/src/js/components/utils/storybook.tsx
+++ b/src/js/components/utils/storybook.ts
@@ -1,4 +1,4 @@
-import { Box } from '@chakra-ui/react'
+import styled from 'styled-components';
// .storybook/constants.js
export enum BADGE {
@@ -10,27 +10,27 @@ export enum BADGE {
}
export const badges = {
- [ BADGE.IN_USE ]: {
+ [BADGE.IN_USE]: {
color: '#fff',
contrast: '#E1230C',
title: 'In Use'
},
- [ BADGE.DEV ]: {
+ [BADGE.DEV]: {
color: '#fff',
contrast: '#E1230C',
title: 'Dev'
},
- [ BADGE.BETA ]: {
+ [BADGE.BETA]: {
color: '#fff',
contrast: '#0084FF',
title: 'Beta'
},
- [ BADGE.STABLE ]: {
+ [BADGE.STABLE]: {
color: '#fff',
contrast: '#00C820',
title: 'Stable'
},
- [ BADGE.CONCEPT ]: {
+ [BADGE.CONCEPT]: {
color: '#fff',
contrast: '#FF6B00',
title: 'Concept'
@@ -45,38 +45,35 @@ export const Storybook = () => null;
*
* Not used in the application, but useful for testing.
*/
-const App = ({ children }) =>
- {children}
- ;
+const App = styled.div`
+ background-color: var(--background-color);
+ color: var(--body-text-color);
+ position: relative;
+ z-index: 0;
+
+ .docs-story & {
+ margin: -30px -20px;
+ }
+`;
+
+const Wrapper = styled.div`
+ padding: 2rem;
+`;
+
+const Desktop = styled(Wrapper)`
+ padding: 2rem;
+ background: rebeccapurple;
-const Wrapper = ({ children }) => {children}
+ .is-storybook-canvas & {
+ min-height: 100vh;
+ display: flex;
-const Desktop = ({ children }) => {children}
+ }
+`;
Storybook.App = App;
Storybook.Desktop = Desktop;
-Storybook.Wrapper = Wrapper;
\ No newline at end of file
+Storybook.Wrapper = Wrapper;
diff --git a/src/js/components/utils/style/style.ts b/src/js/components/utils/style/style.ts
new file mode 100644
index 0000000000..dde5702ceb
--- /dev/null
+++ b/src/js/components/utils/style/style.ts
@@ -0,0 +1,231 @@
+import { createGlobalStyle } from 'styled-components';
+import { transparentize, readableColor } from 'polished';
+
+export const permuteColorOpacities = (theme) =>
+ Object.keys(theme).map((color) => {
+ return (
+ `--${color}: ${theme[color]};` +
+ `--${color}---contrast: ${readableColor(theme[color])};` +
+ Object.keys(opacities)
+ .map((opacity) => {
+ return `--${color}---${opacity}: ${transparentize(
+ 1 - opacities[opacity],
+ theme[color]
+ )};`;
+ })
+ .join("")
+ );
+ });
+
+export const opacityStyles = () => Object.keys(opacities).map(opacity => `--${opacity}: ${opacities[opacity]};`).join("")
+
+const opacities = {
+ "opacity-lower": 0.1,
+ "opacity-low": 0.25,
+ "opacity-med": 0.5,
+ "opacity-high": 0.75,
+ "opacity-higher": 0.85,
+ "opacity-05": 0.05,
+ "opacity-10": 0.10,
+ "opacity-15": 0.15,
+ "opacity-20": 0.20,
+ "opacity-25": 0.25,
+ "opacity-30": 0.30,
+ "opacity-80": 0.80,
+ "opacity-90": 0.9,
+};
+
+export const themeLight = {
+ "link-color": "#0071DB",
+ "highlight-color": "#F9A132",
+ "text-highlight-color": "#ffdb8a",
+ "warning-color": "#D20000",
+ "confirmation-color": "#009E23",
+ "header-text-color": "#322F38",
+ "body-text-color": "#433F38",
+ "border-color": "hsla(32, 81%, 10%, 0.08)",
+ "background-plus-2": "#fff",
+ "background-plus-1": "#fbfbfb",
+ "background-color": "#F6F6F6",
+ "background-minus-1": "#FAF8F6",
+ "background-minus-2": "#EFEDEB",
+ "graph-control-bg": "#f9f9f9",
+ "graph-control-color": "black",
+ "graph-node-normal": "#909090",
+ "graph-node-hlt": "#0075E1",
+ "graph-link-normal": "#cfcfcf",
+ "error-color": "#fd5243", // Deprecate. Replace with "danger-color"
+ "shadow-color": "#000"
+}
+
+export const themeDark = {
+ "link-color": "#0071DB",
+ "highlight-color": "#FBBE63",
+ "text-highlight-color": "#FBBE63",
+ "warning-color": "#DE3C21",
+ "confirmation-color": "#189E36",
+ "header-text-color": "#BABABA",
+ "body-text-color": "#AAA",
+ "border-color": "hsla(32, 81%, 90%, 0.08)",
+ "background-minus-1": "#151515",
+ "background-minus-2": "#111",
+ "background-color": "#1A1A1A",
+ "background-plus-1": "#222",
+ "background-plus-2": "#333",
+ "graph-control-bg": "#272727",
+ "graph-control-color": "white",
+ "graph-node-normal": "#909090",
+ "graph-node-hlt": "#FBBE63",
+ "graph-link-normal": "#323232",
+ "error-color": "#fd5243", // Deprecate. Replace with "danger-color"
+ "shadow-color": "#000"
+}
+
+const lightThemeColors = permuteColorOpacities(themeLight);
+const darkThemeColors = permuteColorOpacities(themeDark);
+
+export const GlobalStyles = createGlobalStyle`
+ :root {
+ --zindex-dropdown: 1000;
+ --zindex-sticky: 1020;
+ --zindex-fixed: 1030;
+ --zindex-modal-backdrop: 1040;
+ --zindex-modal: 1050;
+ --zindex-popover: 1060;
+ --zindex-tooltip: 1070;
+
+ --font-family-serif: "IBM Plex Serif", BlinkMacSystemFont,"Segoe UI Variable","Segoe UI",system-ui,ui-sans-serif,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
+ --font-family-default: "IBM Plex Sans", BlinkMacSystemFont,"Segoe UI Variable","Segoe UI",system-ui,ui-sans-serif,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
+ --font-family-code: "IBM Plex Mono", BlinkMacSystemFont,"Segoe UI Variable","Segoe UI",system-ui,ui-sans-serif,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
+
+ --depth-shadow-4: 0 2px 4px rgba(0, 0, 0, 0.2);
+ --depth-shadow-8: 0 4px 8px rgba(0, 0, 0, 0.2);
+ --depth-shadow-16: 0 4px 16px rgba(0, 0, 0, 0.2);
+ --depth-shadow-64: 0 24px 60px rgba(0, 0, 0, 0.2);
+
+ --font-size--text-xs: 0.75rem;
+ --font-size--text-sm: 0.875rem;
+ --font-size--text-base: 1rem;
+ --font-size--text-lg: 1.125em;
+ --font-size--text-xl: 1.25rem;
+ --font-size--text-2xl: 1.5rem;
+
+ ${opacityStyles}
+
+ .is-theme-light {
+ ${lightThemeColors}
+ }
+
+ .is-theme-dark {
+ ${darkThemeColors}
+ }
+
+ [class*="is-theme-"] {
+ background: var(--background-color);
+ color: var(--body-text-color);
+ }
+ }
+
+ * {
+ box-sizing: border-box;
+ }
+
+ html {
+ padding: 0;
+ margin: 0;
+ height: 100vh;
+ background: var(--background-color);
+ font-family: var(--font-family-default);
+ color: var(--body-text-color);
+ font-size: 16px;
+ line-height: 1.5;
+ position: relative;
+
+ a {
+ color: var(--link-color);
+ }
+ h1, h2, h3, h4, h5, h6 {
+ margin: 0.2em 0;
+ line-height: 1.3;
+ color: var(--header-text-color);
+ }
+
+ h1 {
+ font-size: 3.123em;
+ font-weight: 600;
+ letter-spacing: -0.03em;
+ }
+
+ h2 {
+ font-size: 2.375em;
+ font-weight: 500;
+ letter-spacing: -0.03em;
+ }
+
+ h3 {
+ font-size: 1.75em;
+ font-weight: 500;
+ letter-spacing: -0.02em;
+ }
+
+ h4 {
+ font-size: 1.3125em;
+ }
+
+ h5 {
+ font-size: 0.75em;
+ font-weight: 500;
+ line-height: 1em;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ }
+
+ input {
+ font-family: inherit;
+ }
+
+ mark {
+ background-color: var(--highlight-color);
+ border-radius: 0.25rem;
+ color: #000;
+ }
+
+ kbd {
+ text-transform: uppercase;
+ font-family: inherit;
+ font-size: 0.85em;
+ letter-spacing: 0.05em;
+ font-weight: 600;
+ display: inline-flex;
+ background: var(--body-text-color---opacity-lower);
+ border-radius: 0.25rem;
+ padding: 0.25rem 0.5rem;
+ }
+
+ img {
+ max-width: 100%;
+ height: auto;
+ }
+
+ :focus {
+ outline-width: none;
+ }
+ :focus-visible {
+ outline-width: 1px;
+ }
+ }
+
+ body {
+ overflow: hidden;
+ height: 100vh;
+ width: 100vw;
+ }
+
+ * {
+ box-sizing: border-box;
+ }
+
+ .MuiSvgIcon-root {
+ font-size: 1.5rem;
+ }
+`;
\ No newline at end of file
diff --git a/src/js/components/utils/useAppState.ts b/src/js/components/utils/useAppState.ts
index 6f6d351205..905fcb335b 100644
--- a/src/js/components/utils/useAppState.ts
+++ b/src/js/components/utils/useAppState.ts
@@ -1,4 +1,5 @@
import React from 'react';
+import { mockDatabases as databaseMenuData } from '@/concept/DatabaseMenu/mockData';
import * as presenceData from '@/PresenceDetails/mockData';
const mockData = {
@@ -9,6 +10,8 @@ const mockData = {
color: '#007e51',
},
hostAddress: '192.169.0.1',
+ activeDatabase: databaseMenuData[0],
+ inactiveDatabases: databaseMenuData.slice(1),
currentPageMembers: presenceData.currentPageMembers,
differentPageMembers: presenceData.differentPageMembers,
}
@@ -16,33 +19,39 @@ const mockData = {
export const useAppState = () => {
// Session
- const [ currentUser, setCurrentUser ] = React.useState(mockData.currentUser);
- const [ hostAddress, setHostAddress ] = React.useState(mockData.hostAddress);
- const [ currentPageMembers, setCurrentPageMembers ] = React.useState(mockData.currentPageMembers);
- const [ differentPageMembers, setDifferentPageMembers ] = React.useState(mockData.differentPageMembers);
+ const [currentUser, setCurrentUser] = React.useState(mockData.currentUser);
+ const [hostAddress, setHostAddress] = React.useState(mockData.hostAddress);
+ const [currentPageMembers, setCurrentPageMembers] = React.useState(mockData.currentPageMembers);
+ const [differentPageMembers, setDifferentPageMembers] = React.useState(mockData.differentPageMembers);
+
+ // Database
+ const [activeDatabase, setActiveDatabase] = React.useState(mockData.activeDatabase);
+ const [inactiveDatabases, setInactiveDatabases] = React.useState(mockData.inactiveDatabases);
+ const [isSynced, setIsSynced] = React.useState(null);
+ const [connectionStatus, setConnectionStatus] = React.useState(mockData.connectionStatus as ConnectionStatus);
// Preferences
- const [ isThemeDark, setIsThemeDark ] = React.useState(false);
+ const [isThemeDark, setIsThemeDark] = React.useState(false);
// App properties
- const [ route, setRoute ] = React.useState('');
- const [ isOnline, setIsOnline ] = React.useState(false);
+ const [route, setRoute] = React.useState('');
+ const [isOnline, setIsOnline] = React.useState(false);
// Window properties
- const [ isWinFullscreen, setIsWinFullscreen ] = React.useState(false);
- const [ isWinFocused, setIsWinFocused ] = React.useState(true);
- const [ isWinMaximized, setIsWinMaximized ] = React.useState(false);
- const [ isElectron, setIsElectron ] = React.useState(true);
+ const [isWinFullscreen, setIsWinFullscreen] = React.useState(false);
+ const [isWinFocused, setIsWinFocused] = React.useState(true);
+ const [isWinMaximized, setIsWinMaximized] = React.useState(false);
+ const [isElectron, setIsElectron] = React.useState(true);
// Layout components
- const [ isLeftSidebarOpen, setIsLeftSidebarOpen ] = React.useState(false);
- const [ isRightSidebarOpen, setIsRightSidebarOpen ] = React.useState(false);
- const [ isCommandBarOpen, setIsCommandBarOpen ] = React.useState(false);
+ const [isLeftSidebarOpen, setIsLeftSidebarOpen] = React.useState(false);
+ const [isRightSidebarOpen, setIsRightSidebarOpen] = React.useState(false);
+ const [isCommandBarOpen, setIsCommandBarOpen] = React.useState(false);
// Dialogs and Workflows
- const [ isMergeDialogOpen, setIsMergeDialogOpen ] = React.useState(false);
- const [ isDatabaseDialogOpen, setIsDatabaseDialogOpen ] = React.useState(false);
- const [ isSettingsOpen, setIsSettingsOpen ] = React.useState(false);
+ const [isMergeDialogOpen, setIsMergeDialogOpen] = React.useState(false);
+ const [isDatabaseDialogOpen, setIsDatabaseDialogOpen] = React.useState(false);
+ const [isSettingsOpen, setIsSettingsOpen] = React.useState(false);
return {
currentUser,
@@ -54,8 +63,14 @@ export const useAppState = () => {
setCurrentPageMembers,
differentPageMembers,
setDifferentPageMembers,
+ activeDatabase,
+ setActiveDatabase,
+ inactiveDatabases,
isSettingsOpen,
setIsSettingsOpen,
+ setInactiveDatabases,
+ isSynced,
+ setIsSynced,
isElectron,
setIsElectron,
setRoute,
@@ -79,5 +94,7 @@ export const useAppState = () => {
setIsMergeDialogOpen,
isDatabaseDialogOpen,
setIsDatabaseDialogOpen,
+ connectionStatus,
+ setConnectionStatus
}
-}
+}
\ No newline at end of file
diff --git a/src/js/components/utils/useFocusRingEl.tsx b/src/js/components/utils/useFocusRingEl.tsx
new file mode 100644
index 0000000000..6c2b1538b9
--- /dev/null
+++ b/src/js/components/utils/useFocusRingEl.tsx
@@ -0,0 +1,70 @@
+import React from "react";
+import ReactDOM from "react-dom";
+import styled from "styled-components";
+import { useFocusRing } from "@react-aria/focus";
+import { DOMRoot } from "@/utils/config";
+
+const focusRingRectStyle = (ref): any => {
+ const position = ref?.current?.getBoundingClientRect();
+ const style = getComputedStyle(ref?.current);
+ const borderTopLeftRadius = style.borderTopLeftRadius;
+ const borderTopRightRadius = style.borderTopRightRadius;
+ const borderBottomRightRadius = style.borderBottomRightRadius;
+ const borderBottomLeftRadius = style.borderBottomLeftRadius;
+ let inset = style.getPropertyValue("--focus-ring-inset");
+ if (inset === "") {
+ inset = "-3px";
+ }
+
+ return {
+ "--inset": inset,
+ "--left": position.left + "px",
+ "--top": position.top + "px",
+ "--width": position.width + "px",
+ "--height": position.height + "px",
+ "--border-top-left-radius": borderTopLeftRadius,
+ "--border-top-right-radius": borderTopRightRadius,
+ "--border-bottom-right-radius": borderBottomRightRadius,
+ "--border-bottom-left-radius": borderBottomLeftRadius,
+ };
+};
+
+const FocusRingEl = styled.div>`
+ position: absolute;
+ inset: var(--inset);
+ border: 2px solid var(--link-color);
+ box-shadow: inset 0 0 0 1px var(--background-color);
+ top: calc(var(--top) + var(--inset));
+ left: calc(var(--left) + var(--inset));
+ width: calc(var(--width) - var(--inset) * 2);
+ height: calc(var(--height) - var(--inset) * 2);
+ z-index: 99999;
+ pointer-events: none;
+ border-top-left-radius: calc(var(--border-top-left-radius) - var(--inset));
+ border-top-right-radius: calc(var(--border-top-right-radius) - var(--inset));
+ border-bottom-left-radius: calc(
+ var(--border-bottom-left-radius) - var(--inset)
+ );
+ border-bottom-right-radius: calc(
+ var(--border-bottom-right-radius) - var(--inset)
+ );
+`;
+
+export const useFocusRingEl = (ref) => {
+ const { isFocusVisible, focusProps } = useFocusRing(ref);
+
+ const FocusRing = isFocusVisible ? (
+ ReactDOM.createPortal(
+ ,
+ DOMRoot,
+ )
+ ) : (
+ <>>
+ );
+
+ return {
+ isFocusVisible,
+ focusProps,
+ FocusRing,
+ };
+};
diff --git a/src/js/theme/spacing.js b/src/js/theme/spacing.js
deleted file mode 100644
index 5c76f2bcd0..0000000000
--- a/src/js/theme/spacing.js
+++ /dev/null
@@ -1,19 +0,0 @@
-
-const scale = [ 0.25, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 20, 24, 28 ];
-
-const cssSize = (n) => `${n / 2}rem`;
-
-const makeSpace = (initial) => {
- const space = initial;
-
- scale.forEach(scale => {
- space[ `${scale}` ] = cssSize(scale);
- })
- return space;
-}
-
-export const spacing = {
- space: makeSpace({
- px: '1px'
- })
-}
diff --git a/src/js/theme/theme.js b/src/js/theme/theme.js
deleted file mode 100644
index ee0a6dba3f..0000000000
--- a/src/js/theme/theme.js
+++ /dev/null
@@ -1,555 +0,0 @@
-import { extendTheme, cssVar } from '@chakra-ui/react'
-import { readableColor } from 'polished';
-import { spacing } from './spacing'
-
-const $arrowBg = cssVar("popper-arrow-bg");
-
-const shadows = {
- focusLight: '0 0 0 3px #0071DB',
- focusDark: '0 0 0 3px #498eda',
-
- focusInsetLight: 'inset 0 0 0 3px #0071DB',
- focusInsetDark: 'inset 0 0 0 3px #498eda',
-
- page: '0 0.25rem 1rem #00000055',
-}
-
-const fonts = {
- body: '"IBM Plex Serif", BlinkMacSystemFont,"Segoe UI Variable","Segoe UI",system-ui,ui-sans-serif,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',
- default: '"IBM Plex Sans", BlinkMacSystemFont,"Segoe UI Variable","Segoe UI",system-ui,ui-sans-serif,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',
- code: '"IBM Plex Mono", BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI", system-ui, ui-sans-serif, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
-}
-
-const radii = {
- none: '0',
- sm: '0.25rem',
- md: '0.5rem',
- lg: '1rem',
- full: '9999px',
-}
-
-const colors = {
- // light theme
- linkLight: "#0071DB",
- linkContrastLight: '#fff',
- highlightLight: "#F9A132",
- textHighlightLight: "#ffdb8a",
- highlightContrastLight: "#000",
- warningLight: "#D20000",
-
- backgroundPlus2Light: "#fff",
- backgroundPlus1Light: "#fbfbfb",
- backgroundColorLight: "#F6F6F6",
- backgroundMinus1Light: "#FAF8F6",
- backgroundMinus2Light: "#EFEDEB",
- backgroundVibrancyLight: "#ffffff99",
-
- errorLight: "#fd5243",
- shadowLight: "#000",
-
- // dark theme
- backgroundMinu2Dark: "#151515",
- backgroundMinus1Dark: "#111",
- backgroundColorDark: "#1A1A1A",
- backgroundPlus1Dark: "#222",
- backgroundPlus2Dark: "#333",
- backgroundVibrancyDark: "#222222aa",
-
- linkDark: "#498eda",
- linkContrastDark: '#fff',
- highlightDark: "#FBBE63",
- textHighlightDark: "#FBBE63",
- highlightContrastDark: "#000",
- warningDark: "#DE3C21",
-
- errorDark: "#fd5243",
- shadowDark: "#000",
-
- // interactives
-
-}
-
-const semanticTokens = {
- shadows: {
- focus: {
- default: "focusLight",
- _dark: "focusDark",
- },
- focusInset: {
- default: "focusInsetLight",
- _dark: "focusInsetDark",
- },
- focusPlaceholder: {
- default: '0 0 0 3px transparent'
- },
- focusPlaceholderInset: {
- default: 'inset 0 0 0 3px transparent'
- },
- page: {
- default: "0 0.25rem 1rem #00000022",
- _dark: "0 0.25rem 1rem #00000055",
- },
- menu: {
- default: "0 0.25rem 1rem #00000055",
- },
- popover: {
- default: "0 0.25rem 1rem #00000055",
- },
- tooltip: {
- default: "0 0.25rem 1rem #00000055",
- },
- dialog: {
- default: "0 0.25rem 1rem #00000022",
- },
- },
- colors: {
- brand: {
- default: 'linkLight',
- _dark: 'linkDark'
- },
- // separator colors
- "separator.border": {
- default: '00000011',
- _dark: '#ffffff55'
- },
- "separator.divider": {
- default: '#00000011',
- _dark: '#ffffff11'
- },
- // background colors
- "background.floor": {
- default: 'backgroundColorLight',
- _dark: 'backgroundColorDark'
- },
- "background.basement": {
- default: 'backgroundMinus1Light',
- _dark: 'backgroundMinus1Dark'
- },
- "background.upper": {
- default: 'backgroundPlus1Light',
- _dark: 'backgroundPlus1Dark'
- },
- "background.attic": {
- default: 'backgroundPlus2Light',
- _dark: 'backgroundPlus2Dark'
- },
- "background.vibrancy": {
- default: 'backgroundVibrancyLight',
- _dark: 'backgroundVibrancyDark'
- },
- // foreground colors
- "foreground.primary": {
- default: 'hsla(0, 0%, 0%, 0.87)',
- _dark: 'hsla(0, 0%, 100%, 0.8)'
- },
- "foreground.secondary": {
- default: 'hsla(0, 0%, 0%, 0.57)',
- _dark: 'hsla(0, 0%, 100%, 0.57)'
- },
- // interactions
- "interaction.surface.hover": {
- default: 'hsla(0, 0%, 0%, 0.04)',
- _dark: 'hsla(0, 0%, 100%, 0.08)',
- },
- "interaction.surface.active": {
- default: 'hsla(0, 0%, 0%, 0.12)',
- _dark: 'hsla(0, 0%, 100%, 0.16)',
- },
- // status colors
- danger: {
- default: 'warningLight',
- _dark: 'warningDark'
- },
- info: {
- default: 'linkLight',
- _dark: 'linkDark'
- },
- warning: {
- default: 'warningLight',
- _dark: 'warningDark'
- },
- success: {
- default: 'confirmationLight',
- _dark: 'confirmationDark'
- },
- // other colors
- textHighlight: {
- default: 'textHighlightLight',
- _dark: 'textHighlightDark'
- },
- highlight: {
- default: 'highlightLight',
- _dark: 'highlightDark'
- },
- highlightContrast: {
- default: 'highlightContrastLight',
- _dark: 'highlightContrastDark'
- },
- link: {
- default: 'linkLight',
- _dark: 'linkDark'
- },
- linkContrast: {
- default: 'linkContrastLight',
- _dark: 'linkContrastDark'
- },
- // block content colors
- "ref.foreground": {
- default: "#fbbe63bb",
- _dark: "#fbbe6366",
- },
- "ref.background": {
- default: "#fbbe63bb",
- _dark: "#fbbe6311",
- }
- }
-}
-
-const makeAvatarColor = (bg, color) => {
- if (bg && color) {
- return {
- col: bg,
- color: color,
- }
- } else if (bg && !color) {
- return {
- bg: bg,
- color: readableColor(bg)
- }
- } else if (color && !bg) {
- return {
- bg: readableColor(color),
- color: color,
- }
- }
-}
-
-const components = {
- Alert: {
- variants: {
- // variant used by toasts
- solid: ({ colorScheme }) => ({
- container: {
- bg: 'background.vibrancy',
- backdropFilter: "blur(20px)",
- color: "foreground.primary",
- _after: {
- content: "''",
- position: "absolute",
- inset: 0,
- borderRadius: "inherit",
- background: colorScheme,
- opacity: 0.1,
- zIndex: -1,
- }
- },
- title: {
- fontWeight: "normal"
- },
- icon: {
- color: colorScheme || "info"
- }
- })
- }
- },
- Avatar: {
- baseStyle: ({ bg, color }) => {
- return {
- container: {
- borderColor: "background.floor",
- ...makeAvatarColor(bg, color)
- },
- }
- },
- },
- Accordion: {
- baseStyle: {
- button: {
- borderRadius: "sm",
- borderColor: "separator.border",
- _hover: {
- bg: "interaction.surface.hover",
- },
- _active: {
- bg: "interaction.surface.active"
- },
- _focus: {
- outline: "none",
- boxShadow: "none",
- },
- _focusVisible: {
- outline: "none",
- boxShadow: "focusInset"
- }
- },
- }
- },
- Breadcrumb: {
- baseStyle: {
- container: {
- lineHeight: 1.5,
- },
- separator: {
- color: 'separator.border'
- },
- }
- },
- Button: {
- baseStyle: {
- transitionProperty: 'common',
- transitionTimingFunction: 'ease-in-out',
- "& > .chakra-button__icon svg": {
- height: "1.5em",
- width: "1.5em",
- },
- _active: {
- transitionDuration: "0s",
- },
- _focus: {
- outline: 'none',
- boxShadow: 'none'
- },
- _focusVisible: {
- outline: 'none',
- boxShadow: 'focus'
- }
- },
- variants: {
- link: {
- color: "link",
- borderRadius: "1px"
- },
- solid: {
- _hover: {
- bg: "interaction.surface.hover"
- },
- _active: {
- color: 'foreground.primary',
- bg: 'interaction.surface.active',
- },
- },
- outline: {
- bg: "transparent",
- borderWidth: "1px",
- borderStyle: "solid",
- borderColor: "interaction.surface.hover",
- _hover: {
- borderColor: "transparent",
- bg: "interaction.surface.hover",
- },
- _active: {
- color: 'foreground.primary',
- bg: 'interaction.surface.active',
- borderColor: "transparent",
- },
- }
- },
- colorSchemes: {
- danger: {
- color: "danger"
- }
- }
- },
- FormLabel: {
- baseStyle: {
- color: "foreground.secondary",
- }
- },
- IconButton: {
- baseStyle: {
- fontSize: "1em",
- _active: {
- transitionDuration: "0s",
- },
- _focus: {
- outline: 'none',
- boxShadow: 'none'
- },
- _focusVisible: {
- outline: 'none',
- boxShadow: 'focus'
- }
- },
- variants: {
- solid: {
- _active: {
- color: 'linkContrast',
- bg: 'link',
- },
- }
- }
- },
- Menu: {
- baseStyle: {
- list: {
- zIndex: 3,
- overflow: 'hidden',
- p: 0,
- bg: 'background.vibrancy',
- backdropFilter: "blur(20px)",
- minWidth: '0',
- width: 'max-content',
- shadow: 'menu'
- },
- groupTitle: {
- color: "foreground.secondary",
- textTransform: "uppercase",
- fontSize: "0.75em",
- fontWeight: "bold",
- },
- item: {
- "&.isActive": {
- bg: "background.attic",
- },
- "svg": {
- flexShrink: 0,
- fontSize: "1.5em",
- },
- // additional selector to catch icons not using the icon prop
- "&& > svg:first-of-type, svg:not(span > svg)": {
- marginInlineEnd: "0.75rem"
- },
- }
- },
- sizes: {
- sm: {
- item: {
- padding: '0.5rem 1rem',
- fontSize: "sm"
- }
- }
- }
- },
- Modal: {
- baseStyle: {
- dialogContainer: {
- WebkitAppRegion: 'no-drag',
- _focus: {
- outline: 'none'
- }
- },
- dialog: {
- shadow: "dialog",
- border: "1px solid",
- borderColor: 'separator.divider',
- bg: 'background.upper'
- }
- }
- },
- Popover: {
- parts: [ "arrow", "content" ],
- baseStyle: {
- content: {
- bg: "background.upper",
- shadow: "popover",
- [ $arrowBg.variable ]: "colors.background.upper",
- }
- }
- },
- Spinner: {
- baseStyle: ({ thickness }) => ({
- flexShrink: 0,
- color: "separator.border",
- borderWidth: thickness,
- }),
- defaultProps: {
- thickness: '1.5px',
- }
- },
- Table: {
- baseStyle: {
- border: 'separator.divider',
- },
- variants: {
- striped: {
- td: {
- border: 'none',
- },
- thead: {
- th: {
- border: 'none'
- }
- },
- tbody: {
- tr: {
- "&:nth-of-type(odd)": {
- td: {
- background: "background.upper",
- "&:first-of-type": {
- borderLeftRadius: "lg"
- },
- "&:last-of-type": {
- borderRightRadius: "lg"
- }
- },
- },
- },
- },
- }
- }
- },
- Tabs: {
- variants: {
- line: {
- tabList: {
- borderBottom: "separator.border"
- },
- tab: {
- color: "link",
- borderBottom: "2px solid",
- borderBottomColor: "separator.border",
- _selected: {
- bg: 'background.attic',
- color: 'foreground.primary',
- borderBottomColor: "foreground.primary"
- },
- _focus: {
- outline: 'none',
- shadow: 'none'
- },
- _focusVisible: {
- shadow: "focus"
- }
- }
- }
- }
- },
- Tooltip: {
- baseStyle: {
- bg: 'background.attic',
- color: 'foreground.primary',
- shadow: 'tooltip',
- px: "8px",
- py: "2px",
- borderRadius: "sm",
- fontWeight: "medium",
- fontSize: "sm",
- boxShadow: "md",
- maxW: "320px",
- zIndex: "tooltip",
- }
- },
-}
-
-const config = {
- initialColorMode: 'dark',
- useSystemColorMode: true,
-}
-
-const styles = {
- global: {
- 'html, body': {
- bg: 'background.floor',
- color: 'foreground.primary',
- lineHeight: '1.5',
- height: '100vh',
- fontFamily: 'default',
- }
- }
-}
-
-const sizes = {
- ...spacing
-}
-
-export const theme = extendTheme({ config, radii, fonts, colors, shadows, semanticTokens, spacing, sizes, components, styles });
diff --git a/test/e2e/athena-navigation.spec.ts b/test/e2e/athena-navigation.spec.ts
index 7503acc499..85c2bdb90c 100644
--- a/test/e2e/athena-navigation.spec.ts
+++ b/test/e2e/athena-navigation.spec.ts
@@ -24,7 +24,7 @@ test('athena create new page then click create page', async ({ page }) => {
await inputInAthena(page, title);
await Promise.all([
- page.click('text=Create page: ' + title),
+ page.click('text=Create Page: ' + title),
page.waitForNavigation()
]);
await expect(page.locator(pageTitleLocator)).toHaveText(title);
@@ -33,7 +33,6 @@ test('athena create new page then click create page', async ({ page }) => {
});
test('athena search block then enter on result', async ({ page }) => {
- const title = 'Welcome to Athens, Open-Source Networked Thought!';
await waitForBoot(page);
await inputInAthena(page, 'welcome');
@@ -43,15 +42,16 @@ test('athena search block then enter on result', async ({ page }) => {
await page.press('[placeholder="Find or Create Page"]', 'ArrowDown');
// Press Enter
await page.press('[placeholder="Find or Create Page"]', 'Enter');
- await expect(page.locator(pageTitleLocator)).toHaveText(title);
+
+ await expect(page.locator(".block-page > h1 > textarea")).toHaveValue('Welcome to Athens, Open-Source Networked Thought!');
});
test('athena search block then click on result', async ({ page }) => {
- const title = 'Welcome to Athens, Open-Source Networked Thought!';
await waitForBoot(page);
await inputInAthena(page, 'welcome');
// Click text=WelcomeWelcome to Athens, Open-Source Networked Thought!
- await page.click('text='+title);
- await expect(page.locator(pageTitleLocator)).toHaveText(title);
+ await page.click('text=WelcomeWelcome to Athens, Open-Source Networked Thought!');
+ await expect(page.locator(".block-page > h1 > textarea")).toHaveValue('Welcome to Athens, Open-Source Networked Thought!');
+
});
diff --git a/test/e2e/block-context-menu.spec.ts b/test/e2e/block-context-menu.spec.ts
index 390af90ecb..14168acfdd 100644
--- a/test/e2e/block-context-menu.spec.ts
+++ b/test/e2e/block-context-menu.spec.ts
@@ -4,9 +4,9 @@ import { saveLastBlockAndEnter, waitForBoot, createPage, deleteCurrentPage } fro
const rightClickFirstBullet = async (page: Page) => {
- await page.click('.block-body >> nth=0 >> [aria-label="Block anchor"]', {
- button: 'right'
- });
+ await page.click('.block-body >> nth=0 >> svg', {
+ button: 'right'
+ });
};
test.describe("no blocks selected", () => {
@@ -23,19 +23,21 @@ test.describe("no blocks selected", () => {
});
test('right-click one block', async ({ page }) => {
- await expect(page.locator('text="Copy block refs"')).toBeVisible();
+ await expect(page.locator('text="Copy block ref"')).toBeVisible();
});
test('clicking out of the context menu onto the surrounding page closes context menu', async ({ page }) => {
await page.click('.node-page');
- await expect(page.locator('text="Copy block refs"')).not.toBeVisible();
+ await expect(page.locator('text="Copy block ref"')).not.toBeVisible();
});
// This should close the context menu but doesn't yet.
+ /*
test('clicking out of the context menu on the block itself closes context menu', async ({ page }) => {
await page.click('text=alice');
- await expect(page.locator('text="Copy block refs"')).not.toBeVisible();
+ await expect(page.locator('text="Copy block ref"')).not.toBeVisible();
});
+ */
})
diff --git a/test/e2e/utils.ts b/test/e2e/utils.ts
index 70dee1e3ca..c1501ea0d6 100644
--- a/test/e2e/utils.ts
+++ b/test/e2e/utils.ts
@@ -6,7 +6,7 @@ export const isMac = process.platform === "darwin";
// NOTE: this is not supported by Playwright right now.
-export const createLocalAthensDB = async (page: Page, dbName: string) => {
+export const createLocalAthensDB = async (page:Page, dbName:string) => {
// click db picker
await page.click('button:nth-child(1)');
// click "Add Database"
@@ -21,31 +21,31 @@ export const createLocalAthensDB = async (page: Page, dbName: string) => {
// Fill [placeholder="DB Name"]
await page.fill('[placeholder="DB Name"]', dbName);
- const [ fileChooser ] = await Promise.all([
+ const [fileChooser] = await Promise.all([
// TODO this is broken in Playwright for electron, no fix in sight
page.waitForEvent('filechooser'),
page.click('text=Browse')
- ]);
+ ]);
await fileChooser.setFiles('~/my-e2e-dbs');
await page.pause()
};
-export const lastBlockSelector = '.block-input-textarea >> nth=-1';
+export const lastBlockSelector = '.textarea >> nth=-1';
-export const inputInLastBlock = async (page: Page, text: string) => {
+export const inputInLastBlock = async (page:Page, text:string) => {
await page.click(lastBlockSelector);
await page.fill(lastBlockSelector, text);
};
-export const saveLastBlock = async (page: Page, text: string) => {
+export const saveLastBlock = async (page:Page, text:string) => {
await inputInLastBlock(page, text);
// Move away from the block to force a save.
return page.press(lastBlockSelector, 'ArrowUp');
};
-export const saveLastBlockAndEnter = async (page: Page, text: string) => {
- if (text == "") {
+export const saveLastBlockAndEnter = async (page:Page, text:string) => {
+ if (text=="") {
// This is a side effect of how we're waiting for the new block to appear below.
// If someone finds a better way to do this you can remove this restriction.
throw new Error("Cannot use saveLastBlockAndEnter with empty string.");
@@ -54,28 +54,28 @@ export const saveLastBlockAndEnter = async (page: Page, text: string) => {
await page.press(lastBlockSelector, 'Enter');
// Wait for the new block to be visible.
// TODO: we shouldn't need to do this, instead we should have deterministic states from input.
- await page.locator('.block-input-textarea.is-editing:text-is("")').waitFor();
+ await page.locator('.textarea.is-editing:text-is("")').waitFor();
};
-export const indentLastBlock = async (page: Page) => {
+export const indentLastBlock = async (page:Page) => {
await page.click(lastBlockSelector);
return page.press(lastBlockSelector, 'Tab');
};
-export const unindentLastBlock = async (page: Page) => {
+export const unindentLastBlock = async (page:Page) => {
await page.click(lastBlockSelector);
return page.press(lastBlockSelector, 'Shift+Tab');
};
-export const goToDailyPages = async (page: Page) => {
+export const goToDailyPages = async (page:Page) => {
// The sixth button is the daily notes button.
// TODO: find a better way to address this button, maybe tooltip?
await page.click('button:nth-child(6)');
}
-export const waitForBoot = async (page: Page) => {
- if (!isElectron) {
+export const waitForBoot = async (page:Page) => {
+ if (!isElectron){
await page.goto('/');
}
// Wait for an element that signals the app has finished loading.
@@ -86,15 +86,15 @@ export const waitForBoot = async (page: Page) => {
await page.waitForSelector("text=Find");
}
-export const inputInAthena = async (page: Page, query: string) => {
+export const inputInAthena = async (page:Page, query:string) => {
await page.click('button:has-text("Find or create a page")');
await page.fill('[placeholder="Find or Create Page"]', query);
}
-export const pageTitleLocator = ".page-header > h1.page-title > .block";
+export const pageTitleLocator = ".node-page > header > h1 > span";
-export const createPage = async (page: Page, title: string) => {
+export const createPage = async (page:Page, title:string) => {
await inputInAthena(page, title);
// Press Enter
@@ -107,18 +107,18 @@ export const createPage = async (page: Page, title: string) => {
await page.locator(`${pageTitleLocator}:has-text("${title}")`).waitFor();
}
-export const deleteCurrentPage = async (page: Page) => {
+export const deleteCurrentPage = async (page:Page) => {
// Open page elipsis menu
await page.click(".node-page > header > button");
await page.click('button:has-text("Delete Page")');
await page.click('button:nth-child(6)');
}
-export const todaysDate = async (page: Page) => {
+export const todaysDate = async (page:Page) => {
// This gets today's date as formatted by athens itself.
// TODO: this doesn't work in the prod build, disabled usage for now.
// Need to match the date formatting logic to use again.
const todaysDate = await page.evaluate(`athens.dates.get_day()`);
- const dateTitle = todaysDate.arr[ 3 ];
+ const dateTitle = todaysDate.arr[3];
return dateTitle;
};
diff --git a/yarn.lock b/yarn.lock
index 61f0debd28..bc5fe94aca 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -487,11 +487,6 @@
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.5.tgz#afe37a45f39fce44a3d50a7958129ea5b1a5c074"
integrity sha512-59KHWHXxVA9K4HNF4sbHCf+eJeFe0Te/ZFGqBT4OjXhrwvA04sGfaEGsVTdsjoszq0YTP49RC9UKe5g8uN2RwQ==
-"@babel/helper-plugin-utils@^7.16.7":
- version "7.16.7"
- resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5"
- integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==
-
"@babel/helper-remap-async-to-generator@^7.14.5":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.14.5.tgz#51439c913612958f54a987a4ffc9ee587a2045d6"
@@ -978,13 +973,6 @@
dependencies:
"@babel/helper-plugin-utils" "^7.10.4"
-"@babel/plugin-syntax-jsx@^7.12.13":
- version "7.16.7"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz#50b6571d13f764266a113d77c82b4a6508bbe665"
- integrity sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==
- dependencies:
- "@babel/helper-plugin-utils" "^7.16.7"
-
"@babel/plugin-syntax-jsx@^7.14.5":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz#000e2e25d8673cce49300517a3eda44c263e4201"
@@ -1665,14 +1653,7 @@
dependencies:
regenerator-runtime "^0.13.4"
-"@babel/runtime@^7.12.13", "@babel/runtime@^7.13.10":
- version "7.17.8"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2"
- integrity sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA==
- dependencies:
- regenerator-runtime "^0.13.4"
-
-"@babel/runtime@^7.15.4":
+"@babel/runtime@^7.1.2", "@babel/runtime@^7.15.4", "@babel/runtime@^7.6.2":
version "7.15.4"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a"
integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==
@@ -1793,556 +1774,6 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
-"@chakra-ui/accordion@1.4.9":
- version "1.4.9"
- resolved "https://registry.yarnpkg.com/@chakra-ui/accordion/-/accordion-1.4.9.tgz#0c5dd7d55c94f583b135583697f212506085e572"
- integrity sha512-ZrfrLwAu6p9B41sZ+iEWjfPW/mn2TdUDXv165qr1O355619e2Btjb01x3IYoN4GlE2iF7GOVjC5uYGNyLpBlZg==
- dependencies:
- "@chakra-ui/descendant" "2.1.3"
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/icon" "2.0.5"
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/transition" "1.4.7"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/alert@1.3.7":
- version "1.3.7"
- resolved "https://registry.yarnpkg.com/@chakra-ui/alert/-/alert-1.3.7.tgz#f36020ffc3b2c26be67025c56bccbf0639a81a67"
- integrity sha512-fFpJYBpHOIK/BX4BVl/xafYiDBUW+Bq/gUYDOo4iAiO4vHgxo74oa+yOwSRNlNjAgIX7pi2ridsYQALKyWyxxQ==
- dependencies:
- "@chakra-ui/icon" "2.0.5"
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/anatomy@1.3.0":
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/@chakra-ui/anatomy/-/anatomy-1.3.0.tgz#38a40dd6f2bb076fe8bebe8fb8e4769ea005e03d"
- integrity sha512-vj/lcHkCuq/dtbl69DkNsftZTnrGEegB90ODs1B6rxw8iVMdDSYkthPPFAkqzNs4ppv1y2IBjELuVzpeta1OHA==
- dependencies:
- "@chakra-ui/theme-tools" "^1.3.6"
-
-"@chakra-ui/avatar@1.3.9":
- version "1.3.9"
- resolved "https://registry.yarnpkg.com/@chakra-ui/avatar/-/avatar-1.3.9.tgz#6e4eae5901428e90d2aa9ef8938a775959794d9f"
- integrity sha512-QhtVuFRXhV7X5iMCHI1lXOA0U2hJnpKC9uIEB80EkBuNYJDEz/y8ViOQPRivMVU//wymwLcbvjDCZd1urMjVYQ==
- dependencies:
- "@chakra-ui/image" "1.1.8"
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/breadcrumb@1.3.6":
- version "1.3.6"
- resolved "https://registry.yarnpkg.com/@chakra-ui/breadcrumb/-/breadcrumb-1.3.6.tgz#fe22e162c37add5830bd1292172bb11d859c6f35"
- integrity sha512-iXxienBO6RUnJEcDvyDWyRt+mzPyl7/b6N8i0vrjGKGLpgtayJFvIdo33tFcvx6TCy7V9hiE3HTtZnNomWdR6A==
- dependencies:
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/button@1.5.8":
- version "1.5.8"
- resolved "https://registry.yarnpkg.com/@chakra-ui/button/-/button-1.5.8.tgz#57945201a971fab38d1a700e62f6d840f6d9ad5c"
- integrity sha512-harZywey/6OclxIB5p/Ge/coeGKZWoqmu7JjXlbwTUd3U9IQiOVo/zekY1JscCSz2oZoVBCvoKZVt3on5dPwmA==
- dependencies:
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/spinner" "1.2.6"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/checkbox@1.6.8":
- version "1.6.8"
- resolved "https://registry.yarnpkg.com/@chakra-ui/checkbox/-/checkbox-1.6.8.tgz#b1dd32db34f4b2fda7ebe522f2eb580690f5f434"
- integrity sha512-CYmJbMA9BXb6ArKmXIAuQ22aQ97HgtslbJlqRKsV/FmZuk1DXF1dcVXzqeInhe5HacQ8z/+SmSqL9Q3fjswKag==
- dependencies:
- "@chakra-ui/form-control" "1.5.9"
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/utils" "1.10.4"
- "@chakra-ui/visually-hidden" "1.1.6"
-
-"@chakra-ui/clickable@1.2.6":
- version "1.2.6"
- resolved "https://registry.yarnpkg.com/@chakra-ui/clickable/-/clickable-1.2.6.tgz#7f3deef71580acf47c2395cac2c1734f43418a3f"
- integrity sha512-89SsrQwwwAadcl/bN8nZqqaaVhVNFdBXqQnxVy1t07DL5ezubmNb5SgFh9LDznkm9YYPQhaGr3W6HFro7iAHMg==
- dependencies:
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/close-button@1.2.7":
- version "1.2.7"
- resolved "https://registry.yarnpkg.com/@chakra-ui/close-button/-/close-button-1.2.7.tgz#6f3073618ae777d7e36a80fb17bc00aaa790e7a5"
- integrity sha512-cYTxfgrIlPU4IZm1sehZXxx/TNQBk9c3LBPvTpywEM8GVRGINh4YLq8WiMaPtO+TDNBnKoWS/jS4IHnR+abADw==
- dependencies:
- "@chakra-ui/icon" "2.0.5"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/color-mode@1.4.6":
- version "1.4.6"
- resolved "https://registry.yarnpkg.com/@chakra-ui/color-mode/-/color-mode-1.4.6.tgz#6532086b0030934a5b18ba2b7117b8077b989064"
- integrity sha512-gCO8Z/jv68jXop94MUQNzigl7JXICAgZQUUqLaKhdy1h2zatVDIPFfjwwjnsgM97G0BxQaNBOC87+PD2UYjzHw==
- dependencies:
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/react-env" "1.1.6"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/control-box@1.1.6":
- version "1.1.6"
- resolved "https://registry.yarnpkg.com/@chakra-ui/control-box/-/control-box-1.1.6.tgz#15a40a2cab525799988ae53948b61eed81a7f177"
- integrity sha512-EUcq5f854puG6ZA6wAWl4107OPl8+bj4MMHJCa48BB0qec0U8HCEtxQGnFwJmaYLalIAjMfHuY3OwO2A3Hi9hA==
- dependencies:
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/counter@1.2.8":
- version "1.2.8"
- resolved "https://registry.yarnpkg.com/@chakra-ui/counter/-/counter-1.2.8.tgz#18ea2139db00ba9400eb31d58b95eb62471a93e2"
- integrity sha512-lVuK+ycKxEE0G4Jkl8A6GWdXUFAih89KA1IkkhQG6NwqdGzbgouTInwBLg1Sm5uwgQ5QqSr9S42QyDoleUyF0g==
- dependencies:
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/css-reset@1.1.3":
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/@chakra-ui/css-reset/-/css-reset-1.1.3.tgz#da65507ea1d69ed309bc34619881e23b5004ec7d"
- integrity sha512-AgfrE7bRTJvNi/4zIfacI/kBHmHmHEIeQtHwCvk/0qM9V2gK1VM3ctYlnibf7BTh17F/UszweOGRb1lHSPfWjw==
-
-"@chakra-ui/descendant@2.1.3":
- version "2.1.3"
- resolved "https://registry.yarnpkg.com/@chakra-ui/descendant/-/descendant-2.1.3.tgz#6198ccce207b3d8697dedefff6886f18ca13b5ce"
- integrity sha512-aNYNv99gEPENCdw2N5y3FvL5wgBVcLiOzJ2TxSwb4EVYszbgBZ8Ry1pf7lkoSfysdxD0scgy2cVyxO8TsYTU4g==
- dependencies:
- "@chakra-ui/react-utils" "^1.2.3"
-
-"@chakra-ui/editable@1.4.0":
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/@chakra-ui/editable/-/editable-1.4.0.tgz#9c6f277d6ae2e1883fd72b1ec492df3659d62390"
- integrity sha512-QH5ZMCK/U3pQINtSPiqxxA5XCdiXKBfAI1+siiuSqKtmCriltcArEU4groQn/bm7EY6UJIr/MV3azSDeeBIsaQ==
- dependencies:
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/focus-lock@1.2.6":
- version "1.2.6"
- resolved "https://registry.yarnpkg.com/@chakra-ui/focus-lock/-/focus-lock-1.2.6.tgz#ecdc9688651c55c67f9059720f0885ea7c02b979"
- integrity sha512-ZJNE1oNdUM1aGWuCJ+bxFa/d3EwxzfMWzTKzSvKDK50GWoUQQ10xFTT9nY/yFpkcwhBvx1KavxKf44mIhIbSog==
- dependencies:
- "@chakra-ui/utils" "1.10.4"
- react-focus-lock "2.5.2"
-
-"@chakra-ui/form-control@1.5.9":
- version "1.5.9"
- resolved "https://registry.yarnpkg.com/@chakra-ui/form-control/-/form-control-1.5.9.tgz#ab74441162aaaa09e455ad7fc5921ac3af5ea493"
- integrity sha512-JuUB9dHXFqTYm+Z+cOULk56AcrX9y3eaied0j/KGdPwtIjS2kkjulq7A8sJJdsle4M6XleMinjW+1KO2PMExQg==
- dependencies:
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/icon" "2.0.5"
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/hooks@1.8.5":
- version "1.8.5"
- resolved "https://registry.yarnpkg.com/@chakra-ui/hooks/-/hooks-1.8.5.tgz#db6dc4a7992c7d48cfec5cd796f6a1eb19143eb7"
- integrity sha512-/UrBfUG7NLxuU/09gy2qQfEH+H5SPBUaUiFtokRlq887D/32JQ3XksZdF78RKMCM/0bbZuIjqUkuN/wO9kAbSw==
- dependencies:
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/utils" "1.10.4"
- compute-scroll-into-view "1.0.14"
- copy-to-clipboard "3.3.1"
-
-"@chakra-ui/icon@2.0.5":
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/@chakra-ui/icon/-/icon-2.0.5.tgz#d57f53e6a2c7ae1bae7292a1778fd466c02e2e29"
- integrity sha512-ZrqRvCCIxGr4qFd/r1pmtd9tobRmv8KAxV7ygFoc/t4vOSKTcVIjhE12gsI3FzgvXM15ZFVwsxa1zodwgo5neQ==
- dependencies:
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/image@1.1.8":
- version "1.1.8"
- resolved "https://registry.yarnpkg.com/@chakra-ui/image/-/image-1.1.8.tgz#f77bb1f1333b8a6d3b056c4f076f4a55f5e44f2f"
- integrity sha512-ffO5lyTfGXxaFr9Bdkrb+GahjXsqeph8R1jXYFYwLjos+/sZZJmHJz/cjyoKjKPd6J7puKVZ6Cxz+Ej6PJlQcA==
- dependencies:
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/input@1.4.4":
- version "1.4.4"
- resolved "https://registry.yarnpkg.com/@chakra-ui/input/-/input-1.4.4.tgz#da350861dc89c4d6d5219f39a177ba0f9d549bdb"
- integrity sha512-A1TYz8lOdSVuMnWRnR7Y+cddnnr5d2o1Vvd8Im09WW2j09xy06xD/EaFy8dI51Ab0ACldglVs66qx5dO7WoV0w==
- dependencies:
- "@chakra-ui/form-control" "1.5.9"
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/layout@1.7.7":
- version "1.7.7"
- resolved "https://registry.yarnpkg.com/@chakra-ui/layout/-/layout-1.7.7.tgz#646fd689f9d54ce5cb4f71ec68dbf314b30699ab"
- integrity sha512-HuZ/Zv9xWzLip263tX2Vt0oaqwaS6Srw78Sdl3DiGSifN8x+ooEAxmeDAIaU2PO21YX+f6s+9A738NAtSM2R+Q==
- dependencies:
- "@chakra-ui/icon" "2.0.5"
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/live-region@1.1.6":
- version "1.1.6"
- resolved "https://registry.yarnpkg.com/@chakra-ui/live-region/-/live-region-1.1.6.tgz#135461a19ae2d479eefb012376ffa0f500b83b16"
- integrity sha512-9gPQHXf7oW0jXyT5R/JzyDMfJ3hF70TqhN8bRH4fMyfNr2Se+SjztMBqCrv5FS5rPjcCeua+e0eArpoB3ROuWQ==
- dependencies:
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/media-query@2.0.4":
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/@chakra-ui/media-query/-/media-query-2.0.4.tgz#25e8074a19613d4ccce880a1f92c8e733708b079"
- integrity sha512-kn6g/L0IFFUHz2v4yiCsBnhg9jUeA7525Z+AWl+BPtvryi7i9J+AJ27y/QAge7vUGy4dwDeFyxOZTs2oZ9/BsA==
- dependencies:
- "@chakra-ui/react-env" "1.1.6"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/menu@1.8.9":
- version "1.8.9"
- resolved "https://registry.yarnpkg.com/@chakra-ui/menu/-/menu-1.8.9.tgz#689ecaa35df7c97cad3a4649c533f9493b374fc5"
- integrity sha512-rvQQU56nQoaz+IZXyamKaAU/87IiGIDrX9wEONHth7QDT/93whnFNYPtUMHMzILz0oliysBey4dlmtRzk5vUpQ==
- dependencies:
- "@chakra-ui/clickable" "1.2.6"
- "@chakra-ui/descendant" "2.1.3"
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/popper" "2.4.3"
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/transition" "1.4.7"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/modal@1.10.10":
- version "1.10.10"
- resolved "https://registry.yarnpkg.com/@chakra-ui/modal/-/modal-1.10.10.tgz#dfdf932f333d83c4125fb2416ce811808dab1c28"
- integrity sha512-/OLnZhhGXQEaCqtrCCf2nu27mVxT/3Kd+NBNMKGZ4X70Dm6HD3x1Zrsto2hVo8l3kLEPRpkfpXhKu61doMc8zw==
- dependencies:
- "@chakra-ui/close-button" "1.2.7"
- "@chakra-ui/focus-lock" "1.2.6"
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/portal" "1.3.8"
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/transition" "1.4.7"
- "@chakra-ui/utils" "1.10.4"
- aria-hidden "^1.1.1"
- react-remove-scroll "2.4.1"
-
-"@chakra-ui/number-input@1.4.5":
- version "1.4.5"
- resolved "https://registry.yarnpkg.com/@chakra-ui/number-input/-/number-input-1.4.5.tgz#4ffa67857266639f2cb90fd01f8f1cf56d8d7112"
- integrity sha512-jxOvJUEuXZXQrOgMGZ+rPNjSrIoV7MSb7CPt3C1jVuiumr/GgNu54awmrky3Zj4ikj68rREEUXAGKBgm9oU3nQ==
- dependencies:
- "@chakra-ui/counter" "1.2.8"
- "@chakra-ui/form-control" "1.5.9"
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/icon" "2.0.5"
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/pin-input@1.7.8":
- version "1.7.8"
- resolved "https://registry.yarnpkg.com/@chakra-ui/pin-input/-/pin-input-1.7.8.tgz#326b2b5686e7fe0a47237595426fb5158d337d3d"
- integrity sha512-P4uJBVKDxTetQhj+s0L7TbUTTqbcHwkLpo4bGUEdQpHMfGFlJgGu0wFT5Z8O0fw+vGNfguFfkqkVRRgK8FkHlA==
- dependencies:
- "@chakra-ui/descendant" "2.1.3"
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/popover@1.11.7":
- version "1.11.7"
- resolved "https://registry.yarnpkg.com/@chakra-ui/popover/-/popover-1.11.7.tgz#e9682a789a52c03187feb74220241ff81ce1cbde"
- integrity sha512-TjMZlpBomIuGuQgGQi2rTSVFwFbc9HdJSU3anyFyDQb4ZnunyqaIEMoqFdj/dK8tDdWIatozKjX6AzSimmSvLg==
- dependencies:
- "@chakra-ui/close-button" "1.2.7"
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/popper" "2.4.3"
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/popper@2.4.3":
- version "2.4.3"
- resolved "https://registry.yarnpkg.com/@chakra-ui/popper/-/popper-2.4.3.tgz#fcdc917d13a56b9d44868c78a009e4dd692697a2"
- integrity sha512-TGzFnYt3mtIVkIejtYIAu4Ka9DaYLzMR4NgcqI6EtaTvgK7Xep+6RTiY/Nq+ZT3l/eaNUwqHRFoNrDUg1XYasA==
- dependencies:
- "@chakra-ui/react-utils" "1.2.3"
- "@popperjs/core" "^2.9.3"
-
-"@chakra-ui/portal@1.3.8":
- version "1.3.8"
- resolved "https://registry.yarnpkg.com/@chakra-ui/portal/-/portal-1.3.8.tgz#128045d236de26c0872f2b78600ecba0ab960285"
- integrity sha512-rpSu/RdtlKfOBzw11qHs91IwUTffUfppBz33PfOFNZpDGmO0+6pWkz40I16eSgYtQigZRQG1spz6Ul7tsh+1ag==
- dependencies:
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/progress@1.2.6":
- version "1.2.6"
- resolved "https://registry.yarnpkg.com/@chakra-ui/progress/-/progress-1.2.6.tgz#4a3a40e826c8c72160d3c8ff411e86244e280ebc"
- integrity sha512-thaHRIYTVktgV78vJMNwzfCX+ickhSpn2bun6FtGVUphFx4tjV+ggz+IGohm6AH2hapskoR1mQU2iNZb6BK0hQ==
- dependencies:
- "@chakra-ui/theme-tools" "1.3.6"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/provider@1.7.12":
- version "1.7.12"
- resolved "https://registry.yarnpkg.com/@chakra-ui/provider/-/provider-1.7.12.tgz#da7571adea7a0dc2899083b7e2774477858a2d69"
- integrity sha512-SSq4z4nMjCbqdGrRkbxzR4o96uRah1HnSFui3lM2263zJN7fyezqiseRboID+i7eIUCBWHMLdsabARAD8t1tDQ==
- dependencies:
- "@chakra-ui/css-reset" "1.1.3"
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/portal" "1.3.8"
- "@chakra-ui/react-env" "1.1.6"
- "@chakra-ui/system" "1.11.2"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/radio@1.4.10":
- version "1.4.10"
- resolved "https://registry.yarnpkg.com/@chakra-ui/radio/-/radio-1.4.10.tgz#a554af383f768ec372e31c5cdd7d1ffe89dcd9b3"
- integrity sha512-TgqBgfezypC4do1Vj4iBp4kptXVWdnhASJ97VFuau2QQPT6zKl3Ke2di+XLhH3CZNCDHpvU/KxQNJ6bfj5GMGg==
- dependencies:
- "@chakra-ui/form-control" "1.5.9"
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/utils" "1.10.4"
- "@chakra-ui/visually-hidden" "1.1.6"
-
-"@chakra-ui/react-env@1.1.6":
- version "1.1.6"
- resolved "https://registry.yarnpkg.com/@chakra-ui/react-env/-/react-env-1.1.6.tgz#9915b02fd1f8ca62ccf578eaec793f1c4dea78b0"
- integrity sha512-L90LNvCfe04FTkN9OPok/o2e60zLJNBH8Im/5dUHvqy7dXLXok8ZDad5vEL46XmGbhe7O8fbxhG6FmAYdcCHrQ==
- dependencies:
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/react-utils@1.2.3", "@chakra-ui/react-utils@^1.2.3":
- version "1.2.3"
- resolved "https://registry.yarnpkg.com/@chakra-ui/react-utils/-/react-utils-1.2.3.tgz#3356c9299bc8faada8fac6c5886ca65ec95bb5be"
- integrity sha512-r8pUwCVVB7UPhb0AiRa9ZzSp4xkMz64yIeJ4O4aGy4WMw7TRH4j4QkbkE1YC9tQitrXrliOlvx4WWJR4VyiGpw==
- dependencies:
- "@chakra-ui/utils" "^1.10.4"
-
-"@chakra-ui/react@^1.8.6":
- version "1.8.6"
- resolved "https://registry.yarnpkg.com/@chakra-ui/react/-/react-1.8.6.tgz#e37b9df24fa2ec7d91d2baaa00fd617e3a1bd80c"
- integrity sha512-FEh/KG0uPeNOMQuIlyPfGjHvGB7LN1AAhkdFefqzNt0zNy8Giv4p1PKY7wdCh5QEFor++A83L1wIWvTGQVJ2vQ==
- dependencies:
- "@chakra-ui/accordion" "1.4.9"
- "@chakra-ui/alert" "1.3.7"
- "@chakra-ui/avatar" "1.3.9"
- "@chakra-ui/breadcrumb" "1.3.6"
- "@chakra-ui/button" "1.5.8"
- "@chakra-ui/checkbox" "1.6.8"
- "@chakra-ui/close-button" "1.2.7"
- "@chakra-ui/control-box" "1.1.6"
- "@chakra-ui/counter" "1.2.8"
- "@chakra-ui/css-reset" "1.1.3"
- "@chakra-ui/editable" "1.4.0"
- "@chakra-ui/form-control" "1.5.9"
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/icon" "2.0.5"
- "@chakra-ui/image" "1.1.8"
- "@chakra-ui/input" "1.4.4"
- "@chakra-ui/layout" "1.7.7"
- "@chakra-ui/live-region" "1.1.6"
- "@chakra-ui/media-query" "2.0.4"
- "@chakra-ui/menu" "1.8.9"
- "@chakra-ui/modal" "1.10.10"
- "@chakra-ui/number-input" "1.4.5"
- "@chakra-ui/pin-input" "1.7.8"
- "@chakra-ui/popover" "1.11.7"
- "@chakra-ui/popper" "2.4.3"
- "@chakra-ui/portal" "1.3.8"
- "@chakra-ui/progress" "1.2.6"
- "@chakra-ui/provider" "1.7.12"
- "@chakra-ui/radio" "1.4.10"
- "@chakra-ui/react-env" "1.1.6"
- "@chakra-ui/select" "1.2.9"
- "@chakra-ui/skeleton" "1.2.12"
- "@chakra-ui/slider" "1.5.9"
- "@chakra-ui/spinner" "1.2.6"
- "@chakra-ui/stat" "1.2.7"
- "@chakra-ui/switch" "1.3.8"
- "@chakra-ui/system" "1.11.2"
- "@chakra-ui/table" "1.3.6"
- "@chakra-ui/tabs" "1.6.8"
- "@chakra-ui/tag" "1.2.7"
- "@chakra-ui/textarea" "1.2.9"
- "@chakra-ui/theme" "1.14.0"
- "@chakra-ui/toast" "1.5.7"
- "@chakra-ui/tooltip" "1.4.9"
- "@chakra-ui/transition" "1.4.7"
- "@chakra-ui/utils" "1.10.4"
- "@chakra-ui/visually-hidden" "1.1.6"
-
-"@chakra-ui/select@1.2.9":
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/@chakra-ui/select/-/select-1.2.9.tgz#5e4943d26f4ddcb0dce359c4fb82e08ab503a389"
- integrity sha512-f8cRy3whXFYviuKGfugPnvXTGarPVt2ux5pffipmliYOhfaJ8O2OtdmNJ/od4WaeGStUH13x12GsEqVw2LBKOg==
- dependencies:
- "@chakra-ui/form-control" "1.5.9"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/skeleton@1.2.12":
- version "1.2.12"
- resolved "https://registry.yarnpkg.com/@chakra-ui/skeleton/-/skeleton-1.2.12.tgz#870857f6511a380a6539f9e71d9e565611bc5ebc"
- integrity sha512-buHqfKw24+EQXFGHlSRq2obHxZgz0FUKSFNMlQS3tMoFwBkLRO/jAQfjj9KKR5b0m2qu1qLBmwFHJLih1+bnzg==
- dependencies:
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/media-query" "2.0.4"
- "@chakra-ui/system" "1.11.2"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/slider@1.5.9":
- version "1.5.9"
- resolved "https://registry.yarnpkg.com/@chakra-ui/slider/-/slider-1.5.9.tgz#c4fea8487f26c35bf78ae0489e62e0b8542ec147"
- integrity sha512-m9n/BpnD/hEDS9q3T17ezgTFWDdvCocPzxQXzLLDN2Z2xOgwyLTQVLk4iB1yROvLCUl7Ig9C4+a4/7fivm+IHw==
- dependencies:
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/spinner@1.2.6":
- version "1.2.6"
- resolved "https://registry.yarnpkg.com/@chakra-ui/spinner/-/spinner-1.2.6.tgz#d85fb3d763a69d40570b591507c5087dba38e6c4"
- integrity sha512-GoUCccN120fGRVgUtfuwcEjeoaxffB+XsgpxX7jhWloXf8b6lkqm68bsxX4Ybb2vGN1fANI98/45JmrnddZO/A==
- dependencies:
- "@chakra-ui/utils" "1.10.4"
- "@chakra-ui/visually-hidden" "1.1.6"
-
-"@chakra-ui/stat@1.2.7":
- version "1.2.7"
- resolved "https://registry.yarnpkg.com/@chakra-ui/stat/-/stat-1.2.7.tgz#e173171d80f9e756966604e620987bbd7590d291"
- integrity sha512-m76jumFW1N+mCG4ytrUz9Mh09nZtS4OQcADEvOslfdI5StwwuzasTA1tueaelPzdhBioMwFUWL05Fr1fXbPJ/Q==
- dependencies:
- "@chakra-ui/icon" "2.0.5"
- "@chakra-ui/utils" "1.10.4"
- "@chakra-ui/visually-hidden" "1.1.6"
-
-"@chakra-ui/styled-system@1.18.1":
- version "1.18.1"
- resolved "https://registry.yarnpkg.com/@chakra-ui/styled-system/-/styled-system-1.18.1.tgz#a09638b2add9795dc442e47f322240a34ef5e543"
- integrity sha512-uhWMNAfkk1DFAQ4nKu+t23WBQ1/XSJq8Y3sBZJQpvopfwOcarbVvEiM5voSUWPA7pkpD/BprGM7zjIRockUcmw==
- dependencies:
- "@chakra-ui/utils" "1.10.4"
- csstype "^3.0.9"
-
-"@chakra-ui/switch@1.3.8":
- version "1.3.8"
- resolved "https://registry.yarnpkg.com/@chakra-ui/switch/-/switch-1.3.8.tgz#1d86794bc28f3b88d62ed202e6bd678108583d13"
- integrity sha512-xcsq4G9YUNRSp0F+XBDjeGZFlJeEdGJptuixk6PZjqRJYUyH+k2bk1bJ2Bv2bjvmkDCojI42MkvWTLHrOqp41A==
- dependencies:
- "@chakra-ui/checkbox" "1.6.8"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/system@1.11.2":
- version "1.11.2"
- resolved "https://registry.yarnpkg.com/@chakra-ui/system/-/system-1.11.2.tgz#7c8aae2758260d3a238e8c0410c17af1fe1cd5af"
- integrity sha512-s4HGYVo86XuSav5PLfuVT26Y+l3ca/nQVF6QxS6YCNiUxdBlahlzsZz3yMz3MKp11voljnY8vj4z4dvOd2sjUQ==
- dependencies:
- "@chakra-ui/color-mode" "1.4.6"
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/styled-system" "1.18.1"
- "@chakra-ui/utils" "1.10.4"
- react-fast-compare "3.2.0"
-
-"@chakra-ui/table@1.3.6":
- version "1.3.6"
- resolved "https://registry.yarnpkg.com/@chakra-ui/table/-/table-1.3.6.tgz#e271676dc03cd4c684e4041df2cf394d86a28510"
- integrity sha512-7agZAgAeDFKviqStvixqnLAH54+setzhx67EztioZTr5Xu+6hQ4rotfJbu8L4i587pcbNg98kCEXEkidjw0XRQ==
- dependencies:
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/tabs@1.6.8":
- version "1.6.8"
- resolved "https://registry.yarnpkg.com/@chakra-ui/tabs/-/tabs-1.6.8.tgz#7ef8cf667e9fb1280b673e4fb0e2dc4415f05af9"
- integrity sha512-f1kM9VhAXqKzTAVRoPRIINNiUgvBcadP9m5GtjAgE4DzCrQKnTDImjIkFhXlMvWEmB5ynXZcCGlsgIZ2A9Hs9g==
- dependencies:
- "@chakra-ui/clickable" "1.2.6"
- "@chakra-ui/descendant" "2.1.3"
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/tag@1.2.7":
- version "1.2.7"
- resolved "https://registry.yarnpkg.com/@chakra-ui/tag/-/tag-1.2.7.tgz#5861a92e83e63825f6fe563921d2704e921b585f"
- integrity sha512-RKrKOol4i/CnpFfo3T9LMm1abaqM+5Bs0soQLbo1iJBbBACY09sWXrQYvveQ2GYzU/OrAUloHqqmKjyVGOlNtg==
- dependencies:
- "@chakra-ui/icon" "2.0.5"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/textarea@1.2.9":
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/@chakra-ui/textarea/-/textarea-1.2.9.tgz#5d73b6be994b3488eecf037a54e7136682d3b5b0"
- integrity sha512-HHeUdBA2JrH/S4PopcpOjRmBWKv4wpxQ+Q4mD03UBznyFARZe3XFJOnxhAPdpB/ZadbdgiyXK27TR0uzaqlONw==
- dependencies:
- "@chakra-ui/form-control" "1.5.9"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/theme-tools@1.3.6", "@chakra-ui/theme-tools@^1.3.6":
- version "1.3.6"
- resolved "https://registry.yarnpkg.com/@chakra-ui/theme-tools/-/theme-tools-1.3.6.tgz#2e5b5c192efd685c158e940a5cedcb0eb51f8602"
- integrity sha512-Wxz3XSJhPCU6OwCHEyH44EegEDQHwvlsx+KDkUDGevOjUU88YuNqOVkKtgTpgMLNQcsrYZ93oPWZUJqqCVNRew==
- dependencies:
- "@chakra-ui/utils" "1.10.4"
- "@ctrl/tinycolor" "^3.4.0"
-
-"@chakra-ui/theme@1.14.0":
- version "1.14.0"
- resolved "https://registry.yarnpkg.com/@chakra-ui/theme/-/theme-1.14.0.tgz#0aa1d602db8256e09b2e7f172108dea4a79748b1"
- integrity sha512-zKy/8JSbiCP0QeBsLzdub7aBnfX2k0qp5vD+RA+mxPEiykEvPGg+TwryxRM5KMZK1Zdgg95aH+9mwiGe9tJt3A==
- dependencies:
- "@chakra-ui/anatomy" "1.3.0"
- "@chakra-ui/theme-tools" "1.3.6"
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/toast@1.5.7":
- version "1.5.7"
- resolved "https://registry.yarnpkg.com/@chakra-ui/toast/-/toast-1.5.7.tgz#d0ffccd13f925967353fb65a590f2ac8f88332db"
- integrity sha512-vM88vX2jTfSwOXWqcj9o9pm+msojJS0cG0Pe/wSuYP+D274SdE8oB2OFqJyijsQ7WQq/P6BIlgquzUcS4smu9A==
- dependencies:
- "@chakra-ui/alert" "1.3.7"
- "@chakra-ui/close-button" "1.2.7"
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/theme" "1.14.0"
- "@chakra-ui/transition" "1.4.7"
- "@chakra-ui/utils" "1.10.4"
- "@reach/alert" "0.13.2"
-
-"@chakra-ui/tooltip@1.4.9":
- version "1.4.9"
- resolved "https://registry.yarnpkg.com/@chakra-ui/tooltip/-/tooltip-1.4.9.tgz#0fa875ee50fd9a4f9b3c93d5d841a222b739afd7"
- integrity sha512-W1GVMFWkLLBfiFsOddhr7oWr2rTKqSy2xxMkR5MuomNaqORW4tvjN/wNSLMUuUHVxtWM+iRQkslE5r6k5/1HAw==
- dependencies:
- "@chakra-ui/hooks" "1.8.5"
- "@chakra-ui/popper" "2.4.3"
- "@chakra-ui/portal" "1.3.8"
- "@chakra-ui/react-utils" "1.2.3"
- "@chakra-ui/utils" "1.10.4"
- "@chakra-ui/visually-hidden" "1.1.6"
-
-"@chakra-ui/transition@1.4.7":
- version "1.4.7"
- resolved "https://registry.yarnpkg.com/@chakra-ui/transition/-/transition-1.4.7.tgz#c8f3248d54fbf7e661e620acf3a3292832c88805"
- integrity sha512-2sbMoKB/enp6Qbte3DD6zwBHyO4YAUSgvSr3wn7DAy4hz9kRZHPuUf/N+i9QZ0whL2koXLgdZvV6RNtSTShq4g==
- dependencies:
- "@chakra-ui/utils" "1.10.4"
-
-"@chakra-ui/utils@1.10.4", "@chakra-ui/utils@^1.10.4":
- version "1.10.4"
- resolved "https://registry.yarnpkg.com/@chakra-ui/utils/-/utils-1.10.4.tgz#40a32d4efd8684b2e7432a40b285796383eacfd3"
- integrity sha512-AM91VQQxw8F4F1WDA28mqKY6NFIOuzc2Ekkna88imy2OiqqmYH0xkq8J16L2qj4cLiLozpYqba3C79pWioy6FA==
- dependencies:
- "@types/lodash.mergewith" "4.6.6"
- css-box-model "1.2.1"
- framesync "5.3.0"
- lodash.mergewith "4.6.2"
-
-"@chakra-ui/visually-hidden@1.1.6":
- version "1.1.6"
- resolved "https://registry.yarnpkg.com/@chakra-ui/visually-hidden/-/visually-hidden-1.1.6.tgz#7a546a5aebe4779c8f18d65b1f0e56249720f28d"
- integrity sha512-Xzy5bA0UA+IyMgwJizQYSEdgz8cC/tHdmFB3CniXzmpKTSK8mJddeEBl+cGbXHBzxEUhH7xF1eaS41O+0ezWEQ==
- dependencies:
- "@chakra-ui/utils" "1.10.4"
-
"@cnakazawa/watch@^1.0.3":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a"
@@ -2351,11 +1782,6 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
-"@ctrl/tinycolor@^3.4.0":
- version "3.4.0"
- resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz#c3c5ae543c897caa9c2a68630bed355be5f9990f"
- integrity sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ==
-
"@develar/schema-utils@~2.6.5":
version "2.6.5"
resolved "https://registry.yarnpkg.com/@develar/schema-utils/-/schema-utils-2.6.5.tgz#3ece22c5838402419a6e0425f85742b961d9b6c6"
@@ -2396,24 +1822,6 @@
dir-compare "^2.4.0"
fs-extra "^9.0.1"
-"@emotion/babel-plugin@^11.7.1":
- version "11.7.2"
- resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.7.2.tgz#fec75f38a6ab5b304b0601c74e2a5e77c95e5fa0"
- integrity sha512-6mGSCWi9UzXut/ZAN6lGFu33wGR3SJisNl3c0tvlmb8XChH1b2SUvxvnOh7hvLpqyRdHHU9AiazV3Cwbk5SXKQ==
- dependencies:
- "@babel/helper-module-imports" "^7.12.13"
- "@babel/plugin-syntax-jsx" "^7.12.13"
- "@babel/runtime" "^7.13.10"
- "@emotion/hash" "^0.8.0"
- "@emotion/memoize" "^0.7.5"
- "@emotion/serialize" "^1.0.2"
- babel-plugin-macros "^2.6.1"
- convert-source-map "^1.5.0"
- escape-string-regexp "^4.0.0"
- find-root "^1.1.0"
- source-map "^0.5.7"
- stylis "4.0.13"
-
"@emotion/cache@^10.0.27":
version "10.0.29"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0"
@@ -2424,17 +1832,6 @@
"@emotion/utils" "0.11.3"
"@emotion/weak-memoize" "0.2.5"
-"@emotion/cache@^11.7.1":
- version "11.7.1"
- resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.7.1.tgz#08d080e396a42e0037848214e8aa7bf879065539"
- integrity sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==
- dependencies:
- "@emotion/memoize" "^0.7.4"
- "@emotion/sheet" "^1.1.0"
- "@emotion/utils" "^1.0.0"
- "@emotion/weak-memoize" "^0.2.5"
- stylis "4.0.13"
-
"@emotion/core@^10.1.1":
version "10.1.1"
resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.1.1.tgz#c956c1365f2f2481960064bcb8c4732e5fb612c3"
@@ -2461,43 +1858,18 @@
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
-"@emotion/is-prop-valid@0.8.8", "@emotion/is-prop-valid@^0.8.2", "@emotion/is-prop-valid@^0.8.6", "@emotion/is-prop-valid@^0.8.8":
+"@emotion/is-prop-valid@0.8.8", "@emotion/is-prop-valid@^0.8.6", "@emotion/is-prop-valid@^0.8.8":
version "0.8.8"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
dependencies:
"@emotion/memoize" "0.7.4"
-"@emotion/is-prop-valid@^1.1.2":
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz#34ad6e98e871aa6f7a20469b602911b8b11b3a95"
- integrity sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==
- dependencies:
- "@emotion/memoize" "^0.7.4"
-
"@emotion/memoize@0.7.4":
version "0.7.4"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
-"@emotion/memoize@^0.7.4", "@emotion/memoize@^0.7.5":
- version "0.7.5"
- resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.5.tgz#2c40f81449a4e554e9fc6396910ed4843ec2be50"
- integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==
-
-"@emotion/react@^11":
- version "11.8.2"
- resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.8.2.tgz#e51f5e6372e22e82780836c9288da19af4b51e70"
- integrity sha512-+1bcHBaNJv5nkIIgnGKVsie3otS0wF9f1T1hteF3WeVvMNQEtfZ4YyFpnphGoot3ilU/wWMgP2SgIDuHLE/wAA==
- dependencies:
- "@babel/runtime" "^7.13.10"
- "@emotion/babel-plugin" "^11.7.1"
- "@emotion/cache" "^11.7.1"
- "@emotion/serialize" "^1.0.2"
- "@emotion/utils" "^1.1.0"
- "@emotion/weak-memoize" "^0.2.5"
- hoist-non-react-statics "^3.3.1"
-
"@emotion/serialize@^0.11.15", "@emotion/serialize@^0.11.16":
version "0.11.16"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-0.11.16.tgz#dee05f9e96ad2fb25a5206b6d759b2d1ed3379ad"
@@ -2509,27 +1881,11 @@
"@emotion/utils" "0.11.3"
csstype "^2.5.7"
-"@emotion/serialize@^1.0.2":
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.2.tgz#77cb21a0571c9f68eb66087754a65fa97bfcd965"
- integrity sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==
- dependencies:
- "@emotion/hash" "^0.8.0"
- "@emotion/memoize" "^0.7.4"
- "@emotion/unitless" "^0.7.5"
- "@emotion/utils" "^1.0.0"
- csstype "^3.0.2"
-
"@emotion/sheet@0.9.4":
version "0.9.4"
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-0.9.4.tgz#894374bea39ec30f489bbfc3438192b9774d32e5"
integrity sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==
-"@emotion/sheet@^1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.1.0.tgz#56d99c41f0a1cda2726a05aa6a20afd4c63e58d2"
- integrity sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==
-
"@emotion/styled-base@^10.0.27":
version "10.0.31"
resolved "https://registry.yarnpkg.com/@emotion/styled-base/-/styled-base-10.0.31.tgz#940957ee0aa15c6974adc7d494ff19765a2f742a"
@@ -2548,23 +1904,12 @@
"@emotion/styled-base" "^10.0.27"
babel-plugin-emotion "^10.0.27"
-"@emotion/styled@^11":
- version "11.8.1"
- resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.8.1.tgz#856f6f63aceef0eb783985fa2322e2bf66d04e17"
- integrity sha512-OghEVAYBZMpEquHZwuelXcRjRJQOVayvbmNR0zr174NHdmMgrNkLC6TljKC5h9lZLkN5WGrdUcrKlOJ4phhoTQ==
- dependencies:
- "@babel/runtime" "^7.13.10"
- "@emotion/babel-plugin" "^11.7.1"
- "@emotion/is-prop-valid" "^1.1.2"
- "@emotion/serialize" "^1.0.2"
- "@emotion/utils" "^1.1.0"
-
"@emotion/stylis@0.8.5", "@emotion/stylis@^0.8.4":
version "0.8.5"
resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04"
integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==
-"@emotion/unitless@0.7.5", "@emotion/unitless@^0.7.4", "@emotion/unitless@^0.7.5":
+"@emotion/unitless@0.7.5", "@emotion/unitless@^0.7.4":
version "0.7.5"
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed"
integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
@@ -2574,16 +1919,50 @@
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.11.3.tgz#a759863867befa7e583400d322652a3f44820924"
integrity sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==
-"@emotion/utils@^1.0.0", "@emotion/utils@^1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.1.0.tgz#86b0b297f3f1a0f2bdb08eeac9a2f49afd40d0cf"
- integrity sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==
-
-"@emotion/weak-memoize@0.2.5", "@emotion/weak-memoize@^0.2.5":
+"@emotion/weak-memoize@0.2.5":
version "0.2.5"
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46"
integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==
+"@formatjs/ecma402-abstract@1.9.8":
+ version "1.9.8"
+ resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.9.8.tgz#f3dad447fbc7f063f88e2a148b7a353161740e74"
+ integrity sha512-2U4n11bLmTij/k4ePCEFKJILPYwdMcJTdnKVBi+JMWBgu5O1N+XhCazlE6QXqVO1Agh2Doh0b/9Jf1mSmSVfhA==
+ dependencies:
+ "@formatjs/intl-localematcher" "0.2.20"
+ tslib "^2.1.0"
+
+"@formatjs/fast-memoize@1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-1.2.0.tgz#1123bfcc5d21d761f15d8b1c32d10e1b6530355d"
+ integrity sha512-fObitP9Tlc31SKrPHgkPgQpGo4+4yXfQQITTCNH8AZdEqB7Mq4nPrjpUL/tNGN3lEeJcFxDbi0haX8HM7QvQ8w==
+ dependencies:
+ tslib "^2.1.0"
+
+"@formatjs/icu-messageformat-parser@2.0.11":
+ version "2.0.11"
+ resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.0.11.tgz#e4ba40b9a8aefc8bccfc96be5906d3bca305b4b3"
+ integrity sha512-5mWb8U8aulYGwnDZWrr+vdgn5PilvtrqQYQ1pvpgzQes/osi85TwmL2GqTGLlKIvBKD2XNA61kAqXYY95w4LWg==
+ dependencies:
+ "@formatjs/ecma402-abstract" "1.9.8"
+ "@formatjs/icu-skeleton-parser" "1.2.12"
+ tslib "^2.1.0"
+
+"@formatjs/icu-skeleton-parser@1.2.12":
+ version "1.2.12"
+ resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.2.12.tgz#45426eb1448c0c08c931eb9f0672283c0e4d0062"
+ integrity sha512-DTFxWmEA02ZNW6fsYjGYSADvtrqqjCYF7DSgCmMfaaE0gLP4pCdAgOPE+lkXXU+jP8iCw/YhMT2Seyk/C5lBWg==
+ dependencies:
+ "@formatjs/ecma402-abstract" "1.9.8"
+ tslib "^2.1.0"
+
+"@formatjs/intl-localematcher@0.2.20":
+ version "0.2.20"
+ resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.2.20.tgz#782aef53d1c1b6112ee67468dc59f9b8d1ba7b17"
+ integrity sha512-/Ro85goRZnCojzxOegANFYL0LaDIpdPjAukR7xMTjOtRx+3yyjR0ifGTOW3/Kjhmab3t6GnyHBYWZSudxEOxPA==
+ dependencies:
+ tslib "^2.1.0"
+
"@geometricpanda/storybook-addon-badges@^0.0.4":
version "0.0.4"
resolved "https://registry.yarnpkg.com/@geometricpanda/storybook-addon-badges/-/storybook-addon-badges-0.0.4.tgz#59873a4b0e7f886da3eff17547835241a9e9e163"
@@ -2601,6 +1980,21 @@
resolved "https://registry.yarnpkg.com/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz#98c23c950a3d9b6c8f0daed06da6c3af06981340"
integrity sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==
+"@internationalized/message@^3.0.2":
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/@internationalized/message/-/message-3.0.2.tgz#c3db2b6b7f75af815819f77da11f8424381416e3"
+ integrity sha512-ZZ8FQDCsri3vUB2mfDD76Vbf97DH361AiZUXKHV4BqwCtYyaNYiZqIr8KXrcMCxJvrIYVQLSn8+jeIQRO3bvtw==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ intl-messageformat "^9.6.12"
+
+"@internationalized/number@^3.0.2":
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/@internationalized/number/-/number-3.0.2.tgz#432574e868713d01acbdd206d358fd59da01bad1"
+ integrity sha512-HKgKYBl5cF85iqFG0Gei+YPivWlFhdhPbMhAcVMsP+X2LCmIG9BvGJZ9oN11SUWknnVAXuLSiIx2SDvwQcdhpg==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+
"@istanbuljs/load-nyc-config@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
@@ -2924,21 +2318,6 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.3.tgz#8b68da1ebd7fc603999cf6ebee34a4899a14b88e"
integrity sha512-xDu17cEfh7Kid/d95kB6tZsLOmSWKCZKtprnhVepjsSaCij+lM3mItSJDuuHDMbCWTh8Ejmebwb+KONcCJ0eXQ==
-"@popperjs/core@^2.9.3":
- version "2.11.4"
- resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.4.tgz#d8c7b8db9226d2d7664553a0741ad7d0397ee503"
- integrity sha512-q/ytXxO5NKvyT37pmisQAItCFqA7FD/vNb8dgaJy3/630Fsc+Mz9/9f2SziBoIZ30TJooXyTwZmhi1zjXmObYg==
-
-"@reach/alert@0.13.2":
- version "0.13.2"
- resolved "https://registry.yarnpkg.com/@reach/alert/-/alert-0.13.2.tgz#71c4a848d51341f1d6d9eaae060975391c224870"
- integrity sha512-LDz83AXCrClyq/MWe+0vaZfHp1Ytqn+kgL5VxG7rirUvmluWaj/snxzfNPWn0Ma4K2YENmXXRC/iHt5X95SqIg==
- dependencies:
- "@reach/utils" "0.13.2"
- "@reach/visually-hidden" "0.13.2"
- prop-types "^15.7.2"
- tslib "^2.1.0"
-
"@reach/router@^1.3.4":
version "1.3.4"
resolved "https://registry.yarnpkg.com/@reach/router/-/router-1.3.4.tgz#d2574b19370a70c80480ed91f3da840136d10f8c"
@@ -2949,22 +2328,334 @@
prop-types "^15.6.1"
react-lifecycles-compat "^3.0.4"
-"@reach/utils@0.13.2":
- version "0.13.2"
- resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.13.2.tgz#87e8fef8ebfe583fa48250238a1a3ed03189fcc8"
- integrity sha512-3ir6cN60zvUrwjOJu7C6jec/samqAeyAB12ZADK+qjnmQPdzSYldrFWwDVV5H0WkhbYXR3uh+eImu13hCetNPQ==
+"@react-aria/checkbox@^3.2.3":
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/@react-aria/checkbox/-/checkbox-3.2.3.tgz#be7f1f881e6d0fb554b5b20b9243e31e222207d5"
+ integrity sha512-bLNdVefKGFA2+QT84htWHYUpxLqA5r3L4q6ilBLOzcRiKpgQM2OW2bQGLN6Zw26MKjmTzEMrR2Db+a/O5e1fUQ==
dependencies:
- "@types/warning" "^3.0.0"
- tslib "^2.1.0"
- warning "^4.0.3"
+ "@babel/runtime" "^7.6.2"
+ "@react-aria/label" "^3.1.3"
+ "@react-aria/toggle" "^3.1.4"
+ "@react-aria/utils" "^3.8.2"
+ "@react-stately/checkbox" "^3.0.3"
+ "@react-stately/toggle" "^3.2.3"
+ "@react-types/checkbox" "^3.2.3"
-"@reach/visually-hidden@0.13.2":
- version "0.13.2"
- resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.13.2.tgz#ee21de376a7e57e60dc92d95a671073796caa17e"
- integrity sha512-sPZwNS0/duOuG0mYwE5DmgEAzW9VhgU3aIt1+mrfT/xiT9Cdncqke+kRBQgU708q/Ttm9tWsoHni03nn/SuPTQ==
+"@react-aria/dialog@^3.1.4":
+ version "3.1.4"
+ resolved "https://registry.yarnpkg.com/@react-aria/dialog/-/dialog-3.1.4.tgz#7fe3f33e09b75dcdf598d0523e982262d6c89220"
+ integrity sha512-OtQGBol3CfcbBpjqXDqXzH5Ygny44PIuyAsZ1e3dfIdtaI+XHsoglyZnvDaVVealIgedHkMubreZnyNYnlzPLg==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ "@react-aria/focus" "^3.4.1"
+ "@react-aria/utils" "^3.8.2"
+ "@react-stately/overlays" "^3.1.3"
+ "@react-types/dialog" "^3.3.1"
+
+"@react-aria/focus@^3.4.1":
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/@react-aria/focus/-/focus-3.4.1.tgz#76fc29e8f1b57b9c87240c9762d7fce7352c61eb"
+ integrity sha512-juM44NCkTvVq+yOVTPADSKJpwKjiqR2Y65W8WTkL5QjaEjYrhph3fdV/ie2jsA7/UszSvMEqJyFcbaHbvDSznA==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ "@react-aria/interactions" "^3.5.1"
+ "@react-aria/utils" "^3.8.2"
+ "@react-types/shared" "^3.8.0"
+ clsx "^1.1.1"
+
+"@react-aria/focus@^3.5.0":
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/@react-aria/focus/-/focus-3.5.0.tgz#02b85f97d6114af1eccc0902ce40723b626cb7f9"
+ integrity sha512-Eib75Q6QgQdn8VVVByg5Vipaaj/C//8Bs++sQY7nkomRx4sdArOnXbDppul3YHP6mRfU9VRLvAigEUlReQF/Xw==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ "@react-aria/interactions" "^3.6.0"
+ "@react-aria/utils" "^3.9.0"
+ "@react-types/shared" "^3.9.0"
+ clsx "^1.1.1"
+
+"@react-aria/i18n@^3.3.2":
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/@react-aria/i18n/-/i18n-3.3.2.tgz#891902938333c6ab5491b7acb7581f8567045dbc"
+ integrity sha512-a4AptbWLPVMJfjPdyW60TFtT4gvKAputx9YaUrIywoV/5p900AcOOc3uuL43+vuCKBzMkGUeTa1a4eL1HstDUA==
dependencies:
- prop-types "^15.7.2"
- tslib "^2.1.0"
+ "@babel/runtime" "^7.6.2"
+ "@internationalized/message" "^3.0.2"
+ "@internationalized/number" "^3.0.2"
+ "@react-aria/ssr" "^3.0.3"
+ "@react-aria/utils" "^3.8.2"
+ "@react-types/shared" "^3.8.0"
+
+"@react-aria/interactions@^3.5.1":
+ version "3.5.1"
+ resolved "https://registry.yarnpkg.com/@react-aria/interactions/-/interactions-3.5.1.tgz#4dc94af662f718ad985fad31440b11cef90109db"
+ integrity sha512-NLzYmHljXEqiUqr+PqszwFchGWUQc+kXWMI8N8vBra7HbPAej9so2iPU6hvn1k/3+b02kjt/2mqTrlN1T+HeGw==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ "@react-aria/utils" "^3.8.2"
+ "@react-types/shared" "^3.8.0"
+
+"@react-aria/interactions@^3.6.0":
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/@react-aria/interactions/-/interactions-3.6.0.tgz#63c16e6179e8ae38221e26256d9a7639d7f9b24e"
+ integrity sha512-dMEGYIIhJ3uxDd19Z/rxuqQp9Rx9c46AInrfzAiOijQj/fTmb4ubCsuFOAQrc0sy1HCY1/ntnRZQuRgT/iS74w==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ "@react-aria/utils" "^3.9.0"
+ "@react-types/shared" "^3.9.0"
+
+"@react-aria/label@^3.1.3":
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/@react-aria/label/-/label-3.1.3.tgz#e2b92aff4f6a148d0c2ca079c3d489ff6d36d55d"
+ integrity sha512-vbY5cirv8N5fRh/AdFMx5Zv21/B4+mwQSiZs8PTlo6+ymXp/u2ycsXXdQswxAvt4jxHUf53ESDNx7xqaAOefwQ==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ "@react-aria/utils" "^3.8.2"
+ "@react-types/label" "^3.4.1"
+ "@react-types/shared" "^3.8.0"
+
+"@react-aria/meter@^3.1.3":
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/@react-aria/meter/-/meter-3.1.3.tgz#d4529b4fad84b52ae956509e173bb510ae6b6ba3"
+ integrity sha512-/UORZsn3Q8clxUMZuI68VYLEkdDjpa32c1FAwv1adyTlQrGvX06iWwvMPKwsJsL6S7AHD5ZMl65H3QT7WXJpbQ==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ "@react-aria/progress" "^3.1.3"
+ "@react-types/meter" "^3.1.2"
+ "@react-types/shared" "^3.8.0"
+
+"@react-aria/overlays@^3.7.2":
+ version "3.7.2"
+ resolved "https://registry.yarnpkg.com/@react-aria/overlays/-/overlays-3.7.2.tgz#dca7693c0ec31371b19c2f51b482f03819b8c391"
+ integrity sha512-KAurJ5MJRnXCPRrO1OdAaXz253cwO5VOOp8wx3/40Zm05o5FBA15ZJZT6BD8rZQOKMCAjkI76tiZQeMQtDULcQ==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ "@react-aria/i18n" "^3.3.2"
+ "@react-aria/interactions" "^3.5.1"
+ "@react-aria/utils" "^3.8.2"
+ "@react-aria/visually-hidden" "^3.2.3"
+ "@react-stately/overlays" "^3.1.3"
+ "@react-types/button" "^3.4.1"
+ "@react-types/overlays" "^3.5.1"
+ dom-helpers "^3.3.1"
+
+"@react-aria/progress@^3.1.3":
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/@react-aria/progress/-/progress-3.1.3.tgz#5b60adfbf52ee2b012a11cfa2c2acf12a56343a0"
+ integrity sha512-8He91F3MYPT63s/3XUYvaRHHGsPgG3/b4TzdhO5GqmpfCnvDyrXrBI5cj+uyIB9gczU59zENJcnP8Q0oOITiMA==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ "@react-aria/i18n" "^3.3.2"
+ "@react-aria/label" "^3.1.3"
+ "@react-aria/utils" "^3.8.2"
+ "@react-types/progress" "^3.1.2"
+ "@react-types/shared" "^3.8.0"
+
+"@react-aria/ssr@^3.0.3":
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.0.3.tgz#eabdb731374df07f361ca9ed5abb4601e0c8aefe"
+ integrity sha512-m7mFU1GGkdlSq++QdAcV6n21B0mc8TEqCSuMdhckkL4psMrnuj5rUoW8pI17LvIxB6RU2tGnjtjJeVBuiE86ow==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+
+"@react-aria/ssr@^3.1.0":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.1.0.tgz#b7163e6224725c30121932a8d1422ef91d1fab22"
+ integrity sha512-RxqQKmE8sO7TGdrcSlHTcVzMP450hqowtBSd2bBS9oPlcokVkaGq28c3Rwa8ty5ctw4EBCjXqjP7xdcKMGDzug==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+
+"@react-aria/switch@^3.1.3":
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/@react-aria/switch/-/switch-3.1.3.tgz#eb369cf6154055cf8429d5ec81caa97443b09f18"
+ integrity sha512-JXRIY9rzRc1iBrwO0EzBukMViU6Ty+TXA9SOsp0DhVfF0QZbIwKKd/M6rFq5xvCItUNMWTqdEdqh2c7hFjhZjQ==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ "@react-aria/toggle" "^3.1.4"
+ "@react-stately/toggle" "^3.2.3"
+ "@react-types/switch" "^3.1.2"
+
+"@react-aria/toggle@^3.1.4":
+ version "3.1.4"
+ resolved "https://registry.yarnpkg.com/@react-aria/toggle/-/toggle-3.1.4.tgz#a4371c4f8ad61b49b2b6e37c1b64a5a9c9e1c60e"
+ integrity sha512-BvIA8QLS7xBp4XrpA2Xc/o5g9SntbrEicANwvc/Xcftu+08ZRsZ4n5/RHTXUUc+i/bJ3yQoxgUwYE7Ru1IhrfA==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ "@react-aria/focus" "^3.4.1"
+ "@react-aria/interactions" "^3.5.1"
+ "@react-aria/utils" "^3.8.2"
+ "@react-stately/toggle" "^3.2.3"
+ "@react-types/checkbox" "^3.2.3"
+ "@react-types/shared" "^3.8.0"
+ "@react-types/switch" "^3.1.2"
+
+"@react-aria/tooltip@^3.1.3":
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/@react-aria/tooltip/-/tooltip-3.1.3.tgz#cf967d9306170ed2ec0ed589fe1cb4cc081ddbe6"
+ integrity sha512-l2/BS1XBKrLpg+dovI3xy6NdCgJ5n82TS4p8vQJa7GcynI1I64R0IjOUFv0lc6ZZsr1G8Wg71SNYfmlgTrPr2w==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ "@react-aria/focus" "^3.4.1"
+ "@react-aria/interactions" "^3.5.1"
+ "@react-aria/utils" "^3.8.2"
+ "@react-stately/tooltip" "^3.0.5"
+ "@react-types/shared" "^3.8.0"
+ "@react-types/tooltip" "^3.1.2"
+
+"@react-aria/utils@^3.8.2":
+ version "3.8.2"
+ resolved "https://registry.yarnpkg.com/@react-aria/utils/-/utils-3.8.2.tgz#7bad03b16d44d0d1fa3f9324478e4aee292160b6"
+ integrity sha512-7ao8UmtN2vOUaJHLeAZUZ+GIvamPXSKr9vvNRFrqC8ekxqmi0xpjduVDyg5QRowXr9uRvZqawqN4tPshQteZ4A==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ "@react-aria/ssr" "^3.0.3"
+ "@react-stately/utils" "^3.2.2"
+ "@react-types/shared" "^3.8.0"
+ clsx "^1.1.1"
+
+"@react-aria/utils@^3.9.0":
+ version "3.9.0"
+ resolved "https://registry.yarnpkg.com/@react-aria/utils/-/utils-3.9.0.tgz#c5446f807091a744311d4d559fa8a42e7448824d"
+ integrity sha512-P0dEOMHGHHJ5KC8iCpaMxAtgdUdeISAm4FZnmoD5fK3JxlKEC046hUxlad83RkNOBZkT2dDvF4HeDCUqdMWHKQ==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ "@react-aria/ssr" "^3.1.0"
+ "@react-stately/utils" "^3.2.2"
+ "@react-types/shared" "^3.9.0"
+ clsx "^1.1.1"
+
+"@react-aria/visually-hidden@^3.2.3":
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/@react-aria/visually-hidden/-/visually-hidden-3.2.3.tgz#4779df0a468873550afb42a7f5fcb2411d82db8d"
+ integrity sha512-iAe5EFI7obEOwTnIdAwWrKq+CrIJFGTw85v8fXnQ7CIVGRDblX85GOUww9bzQNPDLLRYWS4VF702ii8kV4+JCw==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ "@react-aria/interactions" "^3.5.1"
+ "@react-aria/utils" "^3.8.2"
+ clsx "^1.1.1"
+
+"@react-stately/checkbox@^3.0.3":
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/@react-stately/checkbox/-/checkbox-3.0.3.tgz#18ee6bd3b544334b6f853bb5c5f7017ac3bb9c37"
+ integrity sha512-amT889DTLdbjAVjZ9j9TytN73PszynGIspKi1QSUCvXeA2OVyCwShxhV0Pn7yYX8cMinvGXrjhWdhn0nhYeMdg==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ "@react-stately/toggle" "^3.2.3"
+ "@react-stately/utils" "^3.2.2"
+ "@react-types/checkbox" "^3.2.3"
+
+"@react-stately/overlays@^3.1.3":
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/@react-stately/overlays/-/overlays-3.1.3.tgz#b0bb4061c1b20e712dfc32c933ae4bb23e5ccc0e"
+ integrity sha512-X8H/h9F8ZjevwJ7P8ak7v500qQd5x4Y76LsXUXrR6LtcO8FXfp2I+W8sGmBtLZwLQpTJiF1U0WMQqXLE1g6eLA==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ "@react-stately/utils" "^3.2.2"
+ "@react-types/overlays" "^3.5.1"
+
+"@react-stately/toggle@^3.2.3":
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/@react-stately/toggle/-/toggle-3.2.3.tgz#a4de6edc16982990492c6c557e5194f46dacc809"
+ integrity sha512-p5eVjXwNo4y4CeybxfjYmbTzNMNiI67uspbRAJnawWBVWw8X+yIvRfpjYAsqmvsJ+DsvwybSTlQDT6taGoWEsA==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ "@react-stately/utils" "^3.2.2"
+ "@react-types/checkbox" "^3.2.3"
+ "@react-types/shared" "^3.8.0"
+
+"@react-stately/tooltip@^3.0.5":
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/@react-stately/tooltip/-/tooltip-3.0.5.tgz#0cb716791ef3242acd810794162d651b1c47a328"
+ integrity sha512-rHqPSfkxbx0T0B/j+WDl4G2CfLjFeBfyaifGiIUJWHO/0Kwvh5am88VeHtuTVzC2DPEGTdtXqYns21EuJOrDlQ==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ "@react-stately/overlays" "^3.1.3"
+ "@react-stately/utils" "^3.2.2"
+ "@react-types/tooltip" "^3.1.2"
+
+"@react-stately/utils@^3.2.2":
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/@react-stately/utils/-/utils-3.2.2.tgz#468eafa60740c6b0b847a368215dfaa55e87f505"
+ integrity sha512-7NCpRMAexDdgVqbrB9uDrkDpM4Tdw5BU6Gu6IKUXmKsoDYziE6mAjaGkCZBitsrln1Cezc6euI5YPa1JqxgpJg==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+
+"@react-types/button@^3.4.1":
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/@react-types/button/-/button-3.4.1.tgz#715ac9d4997c79233be4d9020b58f85936b8252b"
+ integrity sha512-B54M84LxdEppwjXNlkBEJyMfe9fd+bvFV7R6+NJvupGrZm/LuFNYjFcHk7yjMKWTdWm6DbpIuQz54n5qTW7Vlg==
+ dependencies:
+ "@react-types/shared" "^3.8.0"
+
+"@react-types/checkbox@^3.2.3":
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/@react-types/checkbox/-/checkbox-3.2.3.tgz#2b9d529c55c9884519c7f626f0fe8be7d0f18be1"
+ integrity sha512-YqeAFyrpaxI/eW6zQ7tVkKIASgzpywRrc6C/rV6Mw0zzGGSSvmYvdOBx9yHOEvpts7dLgaGlmLK6CeG7s4yGKg==
+ dependencies:
+ "@react-types/shared" "^3.8.0"
+
+"@react-types/dialog@^3.3.1":
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/@react-types/dialog/-/dialog-3.3.1.tgz#eb07e3d703643f7967243d56951d58a2cf77096f"
+ integrity sha512-1i6fVtixUNlftSNbVPFRieyEy3N/GNqcqpeOsJUB1jby28ppbM+JCp3Icb0ijaNC9Nl8c/oI8srtOWIQIKUJiQ==
+ dependencies:
+ "@react-types/overlays" "^3.5.1"
+ "@react-types/shared" "^3.8.0"
+
+"@react-types/label@^3.4.1":
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/@react-types/label/-/label-3.4.1.tgz#6e6d9ddde1bf66afd95b15cf0934bd24cefa62fb"
+ integrity sha512-4RKrRg7PFwHk53vwnctIR6FkEH9LAOdiw9vxS4/LU/iWt9+3d6+aFDBnIceSIkYOOwENjkrBD1nquGaD8wog+Q==
+ dependencies:
+ "@react-types/shared" "^3.8.0"
+
+"@react-types/meter@^3.1.2":
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/@react-types/meter/-/meter-3.1.2.tgz#1a362218e3727a62b720009e957c59d3887fd186"
+ integrity sha512-rF14kgjXkafmwaMGTpNsDMfjXhGNvKLdf02fwVBEqQjUJ5PFu/DubrIecu4Q6+TG/hEXjD0EryU+YxAwVOzkmg==
+ dependencies:
+ "@react-types/progress" "^3.1.2"
+ "@react-types/shared" "^3.8.0"
+
+"@react-types/overlays@^3.5.1":
+ version "3.5.1"
+ resolved "https://registry.yarnpkg.com/@react-types/overlays/-/overlays-3.5.1.tgz#35350dfca639d04a8fbd973de59b141450df1b46"
+ integrity sha512-T3o6wQ5NNm1rSniIa01bIa6fALC8jbwpYxFMaQRrdEpIvwktt0Fi5Xo6/97+oe4HvzzU0JMhtwWDTdRySvgeZw==
+ dependencies:
+ "@react-types/shared" "^3.8.0"
+
+"@react-types/progress@^3.1.2":
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/@react-types/progress/-/progress-3.1.2.tgz#7878ce5b03bfa92f5f8ca999a4cafc545f3d07a4"
+ integrity sha512-RLddv7VLuN/vWi13zbCmp8L5spbO/ArzimplJ5v59XrDNi/obbFkMOxne8/NrIS06aRiYiP8scXgC+koyyqpJg==
+ dependencies:
+ "@react-types/shared" "^3.8.0"
+
+"@react-types/shared@^3.8.0":
+ version "3.8.0"
+ resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.8.0.tgz#c8abf9dd51dbcf381ad4878741aef5c7ce32fdca"
+ integrity sha512-/HlULcULGQDSn/EArpEYjexITjAaKCHD/0xw6sLBROOJPuancIb1TRlE4Ncux/3ZV/7K1LUmHs5YBiXC8QdcBA==
+
+"@react-types/shared@^3.9.0":
+ version "3.9.0"
+ resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.9.0.tgz#d834f3e6e2c992089192f3c83fb7963e3a6f5207"
+ integrity sha512-YYksINfR6q92P10AhPEGo47Hd7oz1hrnZ6Vx8Gsrq62IbqDdv1XOTzPBaj17Z1ymNY2pitLUSEXsLmozt4wxxQ==
+
+"@react-types/switch@^3.1.2":
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/@react-types/switch/-/switch-3.1.2.tgz#f7015e5e66035c9ae517966842a4f9e5d589fa26"
+ integrity sha512-EaYWoLvUCpOnt//Ov8VBxOjbs4hBpYE/rBAzzIknXaFvKOu867iZBFL7FJbcemOgC8/dwyaj6GUZ1Gw3Z1g59w==
+ dependencies:
+ "@react-types/checkbox" "^3.2.3"
+ "@react-types/shared" "^3.8.0"
+
+"@react-types/tooltip@^3.1.2":
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/@react-types/tooltip/-/tooltip-3.1.2.tgz#a80d1ab5a37337156881a032cdaa136a9e028e32"
+ integrity sha512-puyiRi3IaEeKH25AErZzQKthnxk1McU+7S+Qo2kFLy3F3PyXV0cmSqvKKOhH6kU5Cw4ZnuAlNjCI0tV8PYdlYA==
+ dependencies:
+ "@react-types/overlays" "^3.5.1"
+ "@react-types/shared" "^3.8.0"
"@sentry/browser@6.17.3":
version "6.17.3"
@@ -4077,7 +3768,7 @@
dependencies:
defer-to-connect "^1.0.1"
-"@tweenjs/tween.js@18":
+"@tweenjs/tween.js@^18.6.4":
version "18.6.4"
resolved "https://registry.yarnpkg.com/@tweenjs/tween.js/-/tween.js-18.6.4.tgz#40a3d0a93647124872dec8e0fd1bd5926695b6ca"
integrity sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==
@@ -4202,18 +3893,6 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
-"@types/lodash.mergewith@4.6.6":
- version "4.6.6"
- resolved "https://registry.yarnpkg.com/@types/lodash.mergewith/-/lodash.mergewith-4.6.6.tgz#c4698f5b214a433ff35cb2c75ee6ec7f99d79f10"
- integrity sha512-RY/8IaVENjG19rxTZu9Nukqh0W2UrYgmBj5sdns4hWRZaV8PqR7wIKHFKzvOTjo4zVRV7sVI+yFhAJql12Kfqg==
- dependencies:
- "@types/lodash" "*"
-
-"@types/lodash@*":
- version "4.14.180"
- resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.180.tgz#4ab7c9ddfc92ec4a887886483bc14c79fb380670"
- integrity sha512-XOKXa1KIxtNXgASAnwj7cnttJxS4fksBRywK/9LzRV5YxrF80BXZIGeQSuoESQ/VkUj30Ae0+YcuHc15wJCB2g==
-
"@types/markdown-to-jsx@^6.11.3":
version "6.11.3"
resolved "https://registry.yarnpkg.com/@types/markdown-to-jsx/-/markdown-to-jsx-6.11.3.tgz#cdd1619308fecbc8be7e6a26f3751260249b020e"
@@ -4388,11 +4067,6 @@
resolved "https://registry.yarnpkg.com/@types/verror/-/verror-1.10.4.tgz#805c0612b3a0c124cf99f517364142946b74ba3b"
integrity sha512-OjJdqx6QlbyZw9LShPwRW+Kmiegeg3eWNI41MQQKaG3vjdU2L9SRElntM51HmHBY1cu7izxQJ1lMYioQh3XMBg==
-"@types/warning@^3.0.0":
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52"
- integrity sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=
-
"@types/webpack-env@^1.16.0":
version "1.16.2"
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.16.2.tgz#8db514b059c1b2ae14ce9d7bb325296de6a9a0fa"
@@ -4657,10 +4331,10 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7:
mime-types "~2.1.24"
negotiator "0.6.2"
-accessor-fn@1:
- version "1.3.2"
- resolved "https://registry.yarnpkg.com/accessor-fn/-/accessor-fn-1.3.2.tgz#66074d0127367e996197197822fae846ef0928e7"
- integrity sha512-W4/Lj/gry8AHy+IC7Havr7fNbphHDzVAiZd5h10g8LRRz6ZKla3A1/lkFqoV1jh13R0eJLGWjDBlRGK36fcWiw==
+accessor-fn@^1.3.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/accessor-fn/-/accessor-fn-1.3.1.tgz#88096b96840b6fd0d00b859a38d90f2478e5d8f1"
+ integrity sha512-OjmTIiR8VfVV02EC/kSYpBnu6D+CmjNIFhTgU/CQk9xTkl36fc2TaU+ffezgz0fokeqNWnNBq3BtCpZMPfn0UQ==
acorn-jsx@^5.3.1:
version "5.3.2"
@@ -4915,13 +4589,6 @@ argparse@^2.0.1:
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
-aria-hidden@^1.1.1:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.1.3.tgz#bb48de18dc84787a3c6eee113709c473c64ec254"
- integrity sha512-RhVWFtKH5BiGMycI72q2RAFMLQi8JP9bLuQXgR5a8Znp7P5KOIADSJeyfI8PCVxLEp067B2HbP5JIiI/PXIZeA==
- dependencies:
- tslib "^1.0.0"
-
arr-diff@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
@@ -5196,7 +4863,7 @@ babel-plugin-istanbul@^6.0.0:
istanbul-lib-instrument "^4.0.0"
test-exclude "^6.0.0"
-babel-plugin-macros@^2.0.0, babel-plugin-macros@^2.6.1, babel-plugin-macros@^2.8.0:
+babel-plugin-macros@^2.0.0, babel-plugin-macros@^2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138"
integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==
@@ -5271,7 +4938,7 @@ babel-plugin-react-docgen@^4.2.1:
lodash "^4.17.15"
react-docgen "^5.0.0"
-"babel-plugin-styled-components@>= 1", babel-plugin-styled-components@^1.13.2:
+"babel-plugin-styled-components@>= 1", "babel-plugin-styled-components@>= 1.12.0", babel-plugin-styled-components@^1.13.2:
version "1.13.2"
resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.13.2.tgz#ebe0e6deff51d7f93fceda1819e9b96aeb88278d"
integrity sha512-Vb1R3d4g+MUfPQPVDMCGjm3cDocJEUTR7Xq7QS95JWWeksN1wdFRYpD2kulDgI3Huuaf1CZd+NK4KQmqUFh5dA==
@@ -5344,10 +5011,10 @@ better-opn@^2.1.1:
dependencies:
open "^7.0.3"
-"bezier-js@3 - 6":
- version "6.1.0"
- resolved "https://registry.yarnpkg.com/bezier-js/-/bezier-js-6.1.0.tgz#162b7bdbabe866e3a796285a89d71085140755ec"
- integrity sha512-oc8fkHqG0R+dQuNiXVbPMB0cc8iDqkLAjbA2gq26QmV8tZqW9GGI7iNEX1ioRWlZperQS7v5BX03+9FLVWZbSw==
+bezier-js@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/bezier-js/-/bezier-js-4.1.1.tgz#414df656833104e86765c0fa5e31439fb3e83a34"
+ integrity sha512-oVOS6SSFFFlfnZdzC+lsfvhs/RRcbxJ47U04M4s5QIBaJmr3YWmTIL3qmrOK9uW+nUUcl9Jccmo/xpTrG+bBoQ==
big.js@^5.2.2:
version "5.2.2"
@@ -5856,15 +5523,20 @@ camelize@^1.0.0:
resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b"
integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=
-caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001251, caniuse-lite@^1.0.30001286:
- version "1.0.30001320"
- resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz"
- integrity sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==
+caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001251:
+ version "1.0.30001251"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001251.tgz#6853a606ec50893115db660f82c094d18f096d85"
+ integrity sha512-HOe1r+9VkU4TFmnU70z+r7OLmtR+/chB1rdcJUeQlAinjEeb0cKL20tlAtOagNZhbrtLnCvV19B4FmF1rgzl6A==
-canvas-color-tracker@1:
- version "1.1.5"
- resolved "https://registry.yarnpkg.com/canvas-color-tracker/-/canvas-color-tracker-1.1.5.tgz#aa214a932050f4bc0c7462687f044594cec37180"
- integrity sha512-J0tDDrMU2PJfmIVWwuaU1/uPZ+d3ISmpaYZLrvd7s27ydvafl3HgNzg00DEQHLcqRZLoelBLIm8aVvNCmB582g==
+caniuse-lite@^1.0.30001286:
+ version "1.0.30001286"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001286.tgz#3e9debad420419618cfdf52dc9b6572b28a8fff6"
+ integrity sha512-zaEMRH6xg8ESMi2eQ3R4eZ5qw/hJiVsO/HlLwniIwErij0JDr9P+8V4dtx1l+kLq6j3yy8l8W4fst1lBnat5wQ==
+
+canvas-color-tracker@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/canvas-color-tracker/-/canvas-color-tracker-1.1.4.tgz#4f1ffce1ea91a8092b179ee73330a363a312e7db"
+ integrity sha512-Ppqr1xlcSAOGBRdKruGCyZ6oi1V19iDWrzQ6qh5ZZ4BccaUcOTGnuuCOWCDU/tWtNl57V0ZGaPt71GoL7aghoQ==
dependencies:
tinycolor2 "^1.4.2"
@@ -6098,7 +5770,7 @@ clone-response@^1.0.2:
dependencies:
mimic-response "^1.0.0"
-clsx@^1.0.4:
+clsx@^1.0.4, clsx@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
@@ -6265,11 +5937,6 @@ compression@^1.7.4:
safe-buffer "5.1.2"
vary "~1.1.2"
-compute-scroll-into-view@1.0.14:
- version "1.0.14"
- resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.14.tgz#80e3ebb25d6aa89f42e533956cb4b16a04cfe759"
- integrity sha512-mKDjINe3tc6hGelUMNDzuhorIUZ7kS7BwyY0r2wQd2HOH2tRuJykiC06iSEX8y1TuhNzvz4GcJnK16mM2J1NMQ==
-
compute-scroll-into-view@^1.0.17:
version "1.0.17"
resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz#6a88f18acd9d42e9cf4baa6bec7e0522607ab7ab"
@@ -6589,7 +6256,7 @@ copy-descriptor@^0.1.0:
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
-copy-to-clipboard@3.3.1, copy-to-clipboard@^3.3.1:
+copy-to-clipboard@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae"
integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==
@@ -6775,13 +6442,6 @@ crypto-random-string@^2.0.0:
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
-css-box-model@1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
- integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
- dependencies:
- tiny-invariant "^1.0.6"
-
css-color-keywords@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05"
@@ -6854,11 +6514,6 @@ csstype@^3.0.2:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340"
integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==
-csstype@^3.0.9:
- version "3.0.11"
- resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.11.tgz#d66700c5eacfac1940deb4e3ee5642792d85cd33"
- integrity sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==
-
custom-event@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
@@ -6869,138 +6524,138 @@ cyclist@^1.0.1:
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
-"d3-array@1 - 3", "d3-array@2 - 3", "d3-array@2.10.0 - 3":
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.1.1.tgz#7797eb53ead6b9083c75a45a681e93fc41bc468c"
- integrity sha512-33qQ+ZoZlli19IFiQx4QEpf2CBEayMRzhlisJHSCsSUbDXv6ZishqS1x7uFVClKG4Wr7rZVHvaAttoLow6GqdQ==
+d3-array@2, d3-array@^2.12.1, d3-array@^2.3.0:
+ version "2.12.1"
+ resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.12.1.tgz#e20b41aafcdffdf5d50928004ececf815a465e81"
+ integrity sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==
dependencies:
- internmap "1 - 2"
+ internmap "^1.0.0"
d3-binarytree@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/d3-binarytree/-/d3-binarytree-0.2.0.tgz#10601b89fc966b22ee2bd1a8e9ee4d847dfd0014"
integrity sha512-Z4khfbrBgtbv0M2QSQBaIajxiT6hwkxGt0AoDnTXCWDyyH+Okqy2UU3sXzV01zL5lC75dFAMJ0ftxSKTCr28VA==
-"d3-color@1 - 3":
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2"
- integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
+"d3-color@1 - 2":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-2.0.0.tgz#8d625cab42ed9b8f601a1760a389f7ea9189d62e"
+ integrity sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==
-"d3-dispatch@1 - 3":
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e"
- integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==
+"d3-dispatch@1 - 2", d3-dispatch@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-2.0.0.tgz#8a18e16f76dd3fcaef42163c97b926aa9b55e7cf"
+ integrity sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA==
-"d3-drag@2 - 3":
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba"
- integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==
+d3-drag@2, d3-drag@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-2.0.0.tgz#9eaf046ce9ed1c25c88661911c1d5a4d8eb7ea6d"
+ integrity sha512-g9y9WbMnF5uqB9qKqwIIa/921RYWzlUDv9Jl1/yONQwxbOfszAWTCm8u7HOTgJgRDXiRZN56cHT9pd24dmXs8w==
dependencies:
- d3-dispatch "1 - 3"
- d3-selection "3"
+ d3-dispatch "1 - 2"
+ d3-selection "2"
-"d3-ease@1 - 3":
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4"
- integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==
+"d3-ease@1 - 2":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-2.0.0.tgz#fd1762bfca00dae4bacea504b1d628ff290ac563"
+ integrity sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ==
-"d3-force-3d@2 - 3":
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/d3-force-3d/-/d3-force-3d-3.0.3.tgz#ddd0b9120837ef3cd6ffdad668d99d1992483af2"
- integrity sha512-8HGTbw6y35UYManGCPU4+b9/PGgnyjzRq80DRsp7zFsRl0leVz2pzwx18dwkPe4rDxTOEpM4BuWQ2krbVaGQQA==
+d3-force-3d@^2.3.2:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/d3-force-3d/-/d3-force-3d-2.3.2.tgz#3eba201e9f72456decb3b39c534e8ee6eb6e9a76"
+ integrity sha512-jkNDhUmSCAAq7dxtnILVzn7uOClBZt3YMhupBEoKI9RrvR3x995iUDdbj3VL33ByJAtHZwzytUxdh3zKfvf/iw==
dependencies:
d3-binarytree "^0.2.0"
- d3-dispatch "1 - 3"
+ d3-dispatch "^2.0.0"
d3-octree "^0.2.0"
- d3-quadtree "1 - 3"
- d3-timer "1 - 3"
+ d3-quadtree "^2.0.0"
+ d3-timer "^2.0.0"
-"d3-format@1 - 3":
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641"
- integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
+"d3-format@1 - 2":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-2.0.0.tgz#a10bcc0f986c372b729ba447382413aabf5b0767"
+ integrity sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==
-"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3":
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
- integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
+"d3-interpolate@1 - 2", "d3-interpolate@1.2.0 - 2":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163"
+ integrity sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==
dependencies:
- d3-color "1 - 3"
+ d3-color "1 - 2"
d3-octree@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/d3-octree/-/d3-octree-0.2.0.tgz#d3b3e578733cd0bbb7b6a15f80b0d7b38ab2e54c"
integrity sha512-yPoIxKr4xvZNyKK2bkJybafrNKtBVERbGTUVBovyWxWDQaWnJfWO4ai1jnyBrCeMzpVm/OTAJw2V+EJ9HCuLbQ==
-"d3-quadtree@1 - 3":
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f"
- integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==
+d3-quadtree@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-2.0.0.tgz#edbad045cef88701f6fee3aee8e93fb332d30f9d"
+ integrity sha512-b0Ed2t1UUalJpc3qXzKi+cPGxeXRr4KU9YSlocN74aTzp6R/Ud43t79yLLqxHRWZfsvWXmbDWPpoENK1K539xw==
-"d3-scale-chromatic@1 - 3":
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#15b4ceb8ca2bb0dcb6d1a641ee03d59c3b62376a"
- integrity sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==
+d3-scale-chromatic@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-2.0.0.tgz#c13f3af86685ff91323dc2f0ebd2dabbd72d8bab"
+ integrity sha512-LLqy7dJSL8yDy7NRmf6xSlsFZ6zYvJ4BcWFE4zBrOPnQERv9zj24ohnXKRbyi9YHnYV+HN1oEO3iFK971/gkzA==
dependencies:
- d3-color "1 - 3"
- d3-interpolate "1 - 3"
+ d3-color "1 - 2"
+ d3-interpolate "1 - 2"
-"d3-scale@1 - 4":
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396"
- integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==
+d3-scale@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-3.3.0.tgz#28c600b29f47e5b9cd2df9749c206727966203f3"
+ integrity sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==
dependencies:
- d3-array "2.10.0 - 3"
- d3-format "1 - 3"
- d3-interpolate "1.2.0 - 3"
- d3-time "2.1.1 - 3"
- d3-time-format "2 - 4"
+ d3-array "^2.3.0"
+ d3-format "1 - 2"
+ d3-interpolate "1.2.0 - 2"
+ d3-time "^2.1.1"
+ d3-time-format "2 - 3"
-"d3-selection@2 - 3", d3-selection@3:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31"
- integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==
+d3-selection@2, d3-selection@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-2.0.0.tgz#94a11638ea2141b7565f883780dabc7ef6a61066"
+ integrity sha512-XoGGqhLUN/W14NmaqcO/bb1nqjDAw5WtSYb2X8wiuQWvSZUsUVYsOSkOybUrNvcBjaywBdYPy03eXHMXjk9nZA==
-"d3-time-format@2 - 4":
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a"
- integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==
+"d3-time-format@2 - 3":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-3.0.0.tgz#df8056c83659e01f20ac5da5fdeae7c08d5f1bb6"
+ integrity sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==
dependencies:
- d3-time "1 - 3"
+ d3-time "1 - 2"
-"d3-time@1 - 3", "d3-time@2.1.1 - 3":
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.0.0.tgz#65972cb98ae2d4954ef5c932e8704061335d4975"
- integrity sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ==
+"d3-time@1 - 2", d3-time@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-2.1.1.tgz#e9d8a8a88691f4548e68ca085e5ff956724a6682"
+ integrity sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==
dependencies:
- d3-array "2 - 3"
+ d3-array "2"
-"d3-timer@1 - 3":
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0"
- integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==
+"d3-timer@1 - 2", d3-timer@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-2.0.0.tgz#055edb1d170cfe31ab2da8968deee940b56623e6"
+ integrity sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA==
-"d3-transition@2 - 3":
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f"
- integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==
+d3-transition@2:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-2.0.0.tgz#366ef70c22ef88d1e34105f507516991a291c94c"
+ integrity sha512-42ltAGgJesfQE3u9LuuBHNbGrI/AJjNL2OAUdclE70UE6Vy239GCBEYD38uBPoLeNsOhFStGpPI0BAOV+HMxog==
dependencies:
- d3-color "1 - 3"
- d3-dispatch "1 - 3"
- d3-ease "1 - 3"
- d3-interpolate "1 - 3"
- d3-timer "1 - 3"
+ d3-color "1 - 2"
+ d3-dispatch "1 - 2"
+ d3-ease "1 - 2"
+ d3-interpolate "1 - 2"
+ d3-timer "1 - 2"
-"d3-zoom@2 - 3":
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3"
- integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==
+d3-zoom@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-2.0.0.tgz#f04d0afd05518becce879d04709c47ecd93fba54"
+ integrity sha512-fFg7aoaEm9/jf+qfstak0IYpnesZLiMX6GZvXtUSdv8RH2o4E2qeelgdU09eKS6wGuiGMfcnMI0nTIqWzRHGpw==
dependencies:
- d3-dispatch "1 - 3"
- d3-drag "2 - 3"
- d3-interpolate "1 - 3"
- d3-selection "2 - 3"
- d3-transition "2 - 3"
+ d3-dispatch "1 - 2"
+ d3-drag "2"
+ d3-interpolate "1 - 2"
+ d3-selection "2"
+ d3-transition "2"
dargs@^7.0.0:
version "7.0.0"
@@ -7182,11 +6837,6 @@ detect-newline@^3.1.0:
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
-detect-node-es@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493"
- integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==
-
detect-node@^2.0.4:
version "2.1.0"
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
@@ -7298,6 +6948,13 @@ dom-converter@^0.2.0:
dependencies:
utila "~0.4"
+dom-helpers@^3.3.1:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8"
+ integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==
+ dependencies:
+ "@babel/runtime" "^7.1.2"
+
dom-helpers@^5.0.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
@@ -8287,20 +7944,6 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
-focus-lock@^0.10.2:
- version "0.10.2"
- resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.10.2.tgz#561c62bae8387ecba1dd8e58a6df5ec29835c644"
- integrity sha512-DSaI/UHZ/02sg1P616aIWgToQcrKKBmcCvomDZ1PZvcJFj350PnWhSJxJ76T3e5/GbtQEARIACtbrdlrF9C5kA==
- dependencies:
- tslib "^2.0.3"
-
-focus-lock@^0.9.1:
- version "0.9.2"
- resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.9.2.tgz#9d30918aaa99b1b97677731053d017f82a540d5b"
- integrity sha512-YtHxjX7a0IC0ZACL5wsX8QdncXofWpGPNoVMuI/nZUrPGp6LmNI6+D5j0pPj+v8Kw5EpweA+T5yImK0rnWf7oQ==
- dependencies:
- tslib "^2.0.3"
-
follow-redirects@^1.0.0:
version "1.14.7"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685"
@@ -8311,25 +7954,25 @@ for-in@^1.0.2:
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
-force-graph@^1.42:
- version "1.42.9"
- resolved "https://registry.yarnpkg.com/force-graph/-/force-graph-1.42.9.tgz#50dcb614147f0fb5d641860fbf5a578db9f19219"
- integrity sha512-FvzRlVDGj78CnaQBtTIoGg3d+kV5ei2TBOQ/xrUSKuv9gk2pAmTLJEK8cLYtKYFMMc/v1QTqcRhW9aQA1KTzsw==
- dependencies:
- "@tweenjs/tween.js" "18"
- accessor-fn "1"
- bezier-js "3 - 6"
- canvas-color-tracker "1"
- d3-array "1 - 3"
- d3-drag "2 - 3"
- d3-force-3d "2 - 3"
- d3-scale "1 - 4"
- d3-scale-chromatic "1 - 3"
- d3-selection "2 - 3"
- d3-zoom "2 - 3"
- index-array-by "1"
- kapsule "^1.13"
- lodash.throttle "4"
+force-graph@^1.41.1:
+ version "1.41.1"
+ resolved "https://registry.yarnpkg.com/force-graph/-/force-graph-1.41.1.tgz#7e840ffbaad41674235e4fbbc69fe085e5adf119"
+ integrity sha512-rEgca1WGhAa3d5tQUOFeDe9qeefwO5bHjI9JN+V/lLmG5sSmCKm+eoW3kQHDLaHdAA3TYlNww3LkqBLHkkdvRg==
+ dependencies:
+ "@tweenjs/tween.js" "^18.6.4"
+ accessor-fn "^1.3.0"
+ bezier-js "^4.1.1"
+ canvas-color-tracker "^1.1.4"
+ d3-array "^2.12.1"
+ d3-drag "^2.0.0"
+ d3-force-3d "^2.3.2"
+ d3-scale "^3.3.0"
+ d3-scale-chromatic "^2.0.0"
+ d3-selection "^2.0.0"
+ d3-zoom "^2.0.0"
+ index-array-by "^1.3.1"
+ kapsule "^1.13.4"
+ lodash.throttle "^4.1.1"
foreground-child@^2.0.0:
version "2.0.0"
@@ -8406,33 +8049,6 @@ fragment-cache@^0.2.1:
dependencies:
map-cache "^0.2.2"
-framer-motion@^6:
- version "6.2.8"
- resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-6.2.8.tgz#02abb529191af7e2df444185fe27e932215b715d"
- integrity sha512-4PtBWFJ6NqR350zYVt9AsFDtISTqsdqna79FvSYPfYDXuuqFmiKtZdkTnYPslnsOMedTW0pEvaQ7eqjD+sA+HA==
- dependencies:
- framesync "6.0.1"
- hey-listen "^1.0.8"
- popmotion "11.0.3"
- style-value-types "5.0.0"
- tslib "^2.1.0"
- optionalDependencies:
- "@emotion/is-prop-valid" "^0.8.2"
-
-framesync@5.3.0:
- version "5.3.0"
- resolved "https://registry.yarnpkg.com/framesync/-/framesync-5.3.0.tgz#0ecfc955e8f5a6ddc8fdb0cc024070947e1a0d9b"
- integrity sha512-oc5m68HDO/tuK2blj7ZcdEBRx3p1PjrgHazL8GYEpvULhrtGIFbQArN6cQS2QhW8mitffaB+VYzMjDqBxxQeoA==
- dependencies:
- tslib "^2.1.0"
-
-framesync@6.0.1:
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/framesync/-/framesync-6.0.1.tgz#5e32fc01f1c42b39c654c35b16440e07a25d6f20"
- integrity sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==
- dependencies:
- tslib "^2.1.0"
-
fresh@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
@@ -8600,11 +8216,6 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
has "^1.0.3"
has-symbols "^1.0.1"
-get-nonce@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3"
- integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==
-
get-package-type@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
@@ -8876,6 +8487,11 @@ globby@^9.2.0:
pify "^4.0.1"
slash "^2.0.0"
+goober@^2.0.35:
+ version "2.0.41"
+ resolved "https://registry.yarnpkg.com/goober/-/goober-2.0.41.tgz#0a3d786ff9917bcf2a096eef703bf717838cbec9"
+ integrity sha512-kwjegMT5018zWydhOQlQneCgCtrKJaPsru7TaBWmTYV0nsMeUrM6L6O8JmNYb9UbPMgWcmtf+9p4Y3oJabIH1A==
+
got@^9.6.0:
version "9.6.0"
resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
@@ -9115,11 +8731,6 @@ he@^1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
-hey-listen@^1.0.8:
- version "1.0.8"
- resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
- integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==
-
highlight.js@^10.1.1, highlight.js@~10.7.0:
version "10.7.3"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531"
@@ -9144,7 +8755,7 @@ hmac-drbg@^1.0.1:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
-hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
+hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@@ -9283,6 +8894,16 @@ hyphenate-style-name@^1.0.3:
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==
+iconoir-react@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/iconoir-react/-/iconoir-react-2.1.0.tgz#decfafbdc19851e9e6cc09203ad8550ff6963b86"
+ integrity sha512-PMrSTrTI4iksFrZvPnlQraQdDOE5yEe/Z4O0+v5O7mNbwLFDqCVgtDp6fyR9EDVeIyiPY9/YDfq++eII+DH4rw==
+
+iconoir@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/iconoir/-/iconoir-1.0.0.tgz#8c9ade1c12044872dda1fdef5b0f5fc643cd4bc8"
+ integrity sha512-rZ4ZMMOarUfsRrfH7h3MGIFVNrcWsThtJWpTuZOp/FYhSomaa32HhHg8WH0V3VlN7MIVK4VMD2O7jNq6jRKY3w==
+
iconv-corefoundation@^1.1.7:
version "1.1.7"
resolved "https://registry.yarnpkg.com/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz#31065e6ab2c9272154c8b0821151e2c88f1b002a"
@@ -9372,10 +8993,10 @@ indent-string@^4.0.0:
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
-index-array-by@1:
- version "1.3.2"
- resolved "https://registry.yarnpkg.com/index-array-by/-/index-array-by-1.3.2.tgz#d3f208af6f1e2776b00560e4ae572368405d4c28"
- integrity sha512-OIiTTJ4bfAcHTcMUZp3nmSpxAES+nARZNADnGBhg7OjywT8caxm3FR8fdnSnZIF1LvKDk5wN05oBF6qkVk2ZFg==
+index-array-by@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/index-array-by/-/index-array-by-1.3.1.tgz#48595af44efb32f514efd2e46b88de05a508344c"
+ integrity sha512-e3RmATJZXJWZg9obaLdgPZcz42mzCrr4RuxB/6YaVds7tkUjPRw3Zaebs5YXo4WPyCA0Y9ZKcGYHRqGbGhoU8Q==
infer-owner@^1.0.3, infer-owner@^1.0.4:
version "1.0.4"
@@ -9434,16 +9055,25 @@ internal-slot@^1.0.3:
has "^1.0.3"
side-channel "^1.0.4"
-"internmap@1 - 2":
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009"
- integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
+internmap@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95"
+ integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==
interpret@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9"
integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==
+intl-messageformat@^9.6.12:
+ version "9.9.1"
+ resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-9.9.1.tgz#255d453b0656b4f7e741f31d2b4a95bf2adfe064"
+ integrity sha512-cuzS/XKHn//hvKka77JKU2dseiVY2dofQjIOZv6ZFxFt4Z9sPXnZ7KQ9Ak2r+4XBCjI04MqJ1PhKs/3X22AkfA==
+ dependencies:
+ "@formatjs/fast-memoize" "1.2.0"
+ "@formatjs/icu-messageformat-parser" "2.0.11"
+ tslib "^2.1.0"
+
invariant@^2.2.3, invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
@@ -10297,10 +9927,10 @@ junk@^3.1.0:
resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1"
integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==
-kapsule@^1.13:
- version "1.13.7"
- resolved "https://registry.yarnpkg.com/kapsule/-/kapsule-1.13.7.tgz#63d7e48ef7cf7232374dce53a3650a30d28610ee"
- integrity sha512-CKhyHyKyz5bYehAzz95U0ji5T9f4P0RXXPqQ2tGKjuTz3bJnHIUj8vCzGjnhFZ4XYtVT2wMvIq8D8Awb5rawcQ==
+kapsule@^1.13.4:
+ version "1.13.4"
+ resolved "https://registry.yarnpkg.com/kapsule/-/kapsule-1.13.4.tgz#0fe37264556d9b222c39e849eb48ca8766fcf0c9"
+ integrity sha512-WZz+NTLKrnAfOkWw+o94HdlO+6QqBretdr8EcNSRVxFPxxyOnnBdpoczwZxYzGZqLJDCJJm9P0gRyds+FT+UDA==
dependencies:
debounce "^1.2.1"
@@ -10541,12 +10171,7 @@ lodash.ismatch@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37"
integrity sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=
-lodash.mergewith@4.6.2:
- version "4.6.2"
- resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
- integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
-
-lodash.throttle@4:
+lodash.throttle@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=
@@ -11920,16 +11545,6 @@ polished@^4.0.5, polished@^4.1.3:
dependencies:
"@babel/runtime" "^7.14.0"
-popmotion@11.0.3:
- version "11.0.3"
- resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-11.0.3.tgz#565c5f6590bbcddab7a33a074bb2ba97e24b0cc9"
- integrity sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==
- dependencies:
- framesync "6.0.1"
- hey-listen "^1.0.8"
- style-value-types "5.0.0"
- tslib "^2.1.0"
-
popper.js@1.16.1-lts:
version "1.16.1-lts"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1-lts.tgz#cf6847b807da3799d80ee3d6d2f90df8a3f50b05"
@@ -12127,15 +11742,6 @@ prop-types@^15.0.0, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2,
object-assign "^4.1.1"
react-is "^16.8.1"
-prop-types@^15.8:
- version "15.8.1"
- resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
- integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
- dependencies:
- loose-envify "^1.4.0"
- object-assign "^4.1.1"
- react-is "^16.13.1"
-
proper-lockfile@^4.1.1:
version "4.1.2"
resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f"
@@ -12347,13 +11953,6 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.8:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
-react-clientside-effect@^1.2.5:
- version "1.2.5"
- resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.5.tgz#e2c4dc3c9ee109f642fac4f5b6e9bf5bcd2219a3"
- integrity sha512-2bL8qFW1TGBHozGGbVeyvnggRpMjibeZM2536AKNENLECutp2yfs44IL8Hmpn8qjFQ2K7A9PnYf3vc7aQq/cPA==
- dependencies:
- "@babel/runtime" "^7.12.13"
-
react-codemirror2@^7.2.1:
version "7.2.1"
resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-7.2.1.tgz#38dab492fcbe5fb8ebf5630e5bb7922db8d3a10c"
@@ -12452,55 +12051,24 @@ react-element-to-jsx-string@^14.3.2:
"@base2/pretty-print-object" "1.0.0"
is-plain-object "3.0.1"
-react-error-boundary@^3.1.4:
- version "3.1.4"
- resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0"
- integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==
- dependencies:
- "@babel/runtime" "^7.12.5"
-
react-error-overlay@^6.0.9:
version "6.0.9"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
-react-fast-compare@3.2.0, react-fast-compare@^3.0.1, react-fast-compare@^3.2.0:
+react-fast-compare@^3.0.1, react-fast-compare@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
-react-focus-lock@2.5.2:
- version "2.5.2"
- resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.5.2.tgz#f1e4db5e25cd8789351f2bd5ebe91e9dcb9c2922"
- integrity sha512-WzpdOnEqjf+/A3EH9opMZWauag7gV0BxFl+EY4ElA4qFqYsUsBLnmo2sELbN5OC30S16GAWMy16B9DLPpdJKAQ==
- dependencies:
- "@babel/runtime" "^7.0.0"
- focus-lock "^0.9.1"
- prop-types "^15.6.2"
- react-clientside-effect "^1.2.5"
- use-callback-ref "^1.2.5"
- use-sidecar "^1.0.5"
-
-react-focus-lock@^2.8.1:
- version "2.8.1"
- resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.8.1.tgz#a28f06a4ef5eab7d4ef0d859512772ec1331d529"
- integrity sha512-4kb9I7JIiBm0EJ+CsIBQ+T1t5qtmwPRbFGYFQ0t2q2qIpbFbYTHDjnjJVFB7oMBtXityEOQehblJPjqSIf3Amg==
- dependencies:
- "@babel/runtime" "^7.0.0"
- focus-lock "^0.10.2"
- prop-types "^15.6.2"
- react-clientside-effect "^1.2.5"
- use-callback-ref "^1.2.5"
- use-sidecar "^1.0.5"
-
react-force-graph-2d@^1.19.0:
- version "1.23.10"
- resolved "https://registry.yarnpkg.com/react-force-graph-2d/-/react-force-graph-2d-1.23.10.tgz#a6cc04343ca6d64fd31d782791d05eec5b13ae67"
- integrity sha512-R+4IrrMkwYHnfUbok3sASXthITiKWbPUabG5CSFadawcAKmftovy8/pzSlKCPKUI5bpySLEJZqBHdctJG4Ovfw==
+ version "1.23.6"
+ resolved "https://registry.yarnpkg.com/react-force-graph-2d/-/react-force-graph-2d-1.23.6.tgz#0fa29348a30d3a71c6ed7f79d5af51eac66a6c18"
+ integrity sha512-u0WUKCNnYQSz5kO5GlWT9uiFVatpnqCDgPHe+UKe5EJobBuo+/C/hiaQh75S3lWKGGYwnUnLBq99BkpOmSTnnA==
dependencies:
- force-graph "^1.42"
- prop-types "^15.8"
- react-kapsule "^2.2"
+ force-graph "^1.41.1"
+ prop-types "^15.7.2"
+ react-kapsule "^2.2.2"
react-helmet-async@^1.0.7:
version "1.0.9"
@@ -12521,6 +12089,13 @@ react-highlight.js@1.0.7:
highlight.js "^9.3.0"
prop-types "^15.6.0"
+react-hot-toast@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/react-hot-toast/-/react-hot-toast-2.1.1.tgz#56409ab406b534e9e58274cf98d80355ba0fdda0"
+ integrity sha512-Odrp4wue0fHh0pOfZt5H+9nWCMtqs3wdlFSzZPp7qsxfzmbE26QmGWIh6hG43CukiPeOjA8WQhBJU8JwtWvWbQ==
+ dependencies:
+ goober "^2.0.35"
+
react-inspector@^5.1.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-5.1.1.tgz#58476c78fde05d5055646ed8ec02030af42953c8"
@@ -12535,7 +12110,7 @@ react-intersection-observer@^8.32.1:
resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-8.32.1.tgz#9b949871eb35eb1fc730732bbf8fcfaaaf3f5b02"
integrity sha512-FOmMkMw7MeJ8FkuADpU8TRcvGuTvPB+DRkaikS1QXcWArYLCWC3mjRorq2XeRGBuqmaueOBd27PUazTu9AgInw==
-react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
+react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -12545,10 +12120,10 @@ react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
-react-kapsule@^2.2:
- version "2.2.3"
- resolved "https://registry.yarnpkg.com/react-kapsule/-/react-kapsule-2.2.3.tgz#ce86f7c8ead88756d124b31fd68fd777ed18505f"
- integrity sha512-mAG/4/7ZPDOKz2fsWVncRLCwPGCfEB6H3YCsqQonWV65egmX60lInyuNmibi/7w7vwG/qy8mJHPLnFta21qyBw==
+react-kapsule@^2.2.2:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/react-kapsule/-/react-kapsule-2.2.2.tgz#b1158b132a2d0d79ac5c33c71aa83e6f4b9e20d5"
+ integrity sha512-/dl8dTszQxhGXqlJ10VeOY8upbWRwx9WsBR9cIx1upW0bGmCPkxG2snT6JZiuG+m9QcURf0eRQF8gz1W9upCYQ==
dependencies:
fromentries "^1.3.2"
jerrypick "^1.0.4"
@@ -12580,25 +12155,6 @@ react-refresh@^0.8.3:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==
-react-remove-scroll-bar@^2.1.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.2.0.tgz#d4d545a7df024f75d67e151499a6ab5ac97c8cdd"
- integrity sha512-UU9ZBP1wdMR8qoUs7owiVcpaPwsQxUDC2lypP6mmixaGlARZa7ZIBx1jcuObLdhMOvCsnZcvetOho0wzPa9PYg==
- dependencies:
- react-style-singleton "^2.1.0"
- tslib "^1.0.0"
-
-react-remove-scroll@2.4.1:
- version "2.4.1"
- resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.4.1.tgz#e0af6126621083a5064591d367291a81b2d107f5"
- integrity sha512-K7XZySEzOHMTq7dDwcHsZA6Y7/1uX5RsWhRXVYv8rdh+y9Qz2nMwl9RX/Mwnj/j7JstCGmxyfyC0zbVGXYh3mA==
- dependencies:
- react-remove-scroll-bar "^2.1.0"
- react-style-singleton "^2.1.0"
- tslib "^1.0.0"
- use-callback-ref "^1.2.3"
- use-sidecar "^1.0.1"
-
react-sizeme@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-3.0.1.tgz#4d12f4244e0e6a0fb97253e7af0314dc7c83a5a0"
@@ -12609,15 +12165,6 @@ react-sizeme@^3.0.1:
shallowequal "^1.1.0"
throttle-debounce "^3.0.1"
-react-style-singleton@^2.1.0:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.1.1.tgz#ce7f90b67618be2b6b94902a30aaea152ce52e66"
- integrity sha512-jNRp07Jza6CBqdRKNgGhT3u9umWvils1xsuMOjZlghBDH2MU0PL2WZor4PGYjXpnRCa9DQSlHMs/xnABWOwYbA==
- dependencies:
- get-nonce "^1.0.0"
- invariant "^2.2.4"
- tslib "^1.0.0"
-
react-syntax-highlighter@^13.5.3:
version "13.5.3"
resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-13.5.3.tgz#9712850f883a3e19eb858cf93fad7bb357eea9c6"
@@ -14010,14 +13557,6 @@ style-to-object@0.3.0, style-to-object@^0.3.0:
dependencies:
inline-style-parser "0.1.1"
-style-value-types@5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-5.0.0.tgz#76c35f0e579843d523187989da866729411fc8ad"
- integrity sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==
- dependencies:
- hey-listen "^1.0.8"
- tslib "^2.1.0"
-
styled-components@5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.2.1.tgz#6ed7fad2dc233825f64c719ffbdedd84ad79101a"
@@ -14034,10 +13573,21 @@ styled-components@5.2.1:
shallowequal "^1.1.0"
supports-color "^5.5.0"
-stylis@4.0.13:
- version "4.0.13"
- resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91"
- integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==
+styled-components@^5.3.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.0.tgz#e47c3d3e9ddfff539f118a3dd0fd4f8f4fb25727"
+ integrity sha512-bPJKwZCHjJPf/hwTJl6TbkSZg/3evha+XPEizrZUGb535jLImwDUdjTNxXqjjaASt2M4qO4AVfoHJNe3XB/tpQ==
+ dependencies:
+ "@babel/helper-module-imports" "^7.0.0"
+ "@babel/traverse" "^7.4.5"
+ "@emotion/is-prop-valid" "^0.8.8"
+ "@emotion/stylis" "^0.8.4"
+ "@emotion/unitless" "^0.7.4"
+ babel-plugin-styled-components ">= 1.12.0"
+ css-to-react-native "^3.0.0"
+ hoist-non-react-statics "^3.0.0"
+ shallowequal "^1.1.0"
+ supports-color "^5.5.0"
sumchecker@^3.0.1:
version "3.0.1"
@@ -14237,11 +13787,6 @@ timers-browserify@^2.0.4:
dependencies:
setimmediate "^1.0.4"
-tiny-invariant@^1.0.6:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9"
- integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==
-
tiny-warning@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
@@ -14422,7 +13967,7 @@ tsconfig-paths@^3.9.0:
minimist "^1.2.0"
strip-bom "^3.0.0"
-tslib@^1.0.0, tslib@^1.9.0, tslib@^1.9.3:
+tslib@^1.9.0, tslib@^1.9.3:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
@@ -14782,11 +14327,6 @@ url@^0.11.0:
punycode "1.3.2"
querystring "0.2.0"
-use-callback-ref@^1.2.3, use-callback-ref@^1.2.5:
- version "1.2.5"
- resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.5.tgz#6115ed242cfbaed5915499c0a9842ca2912f38a5"
- integrity sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==
-
use-composed-ref@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.1.0.tgz#9220e4e94a97b7b02d7d27eaeab0b37034438bbc"
@@ -14806,14 +14346,6 @@ use-latest@^1.0.0:
dependencies:
use-isomorphic-layout-effect "^1.0.0"
-use-sidecar@^1.0.1, use-sidecar@^1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.5.tgz#ffff2a17c1df42e348624b699ba6e5c220527f2b"
- integrity sha512-k9jnrjYNwN6xYLj1iaGhonDghfvmeTmYjAiGvOr7clwKfPjMXJf4/HOr7oT5tJwYafgp2tG2l3eZEOfoELiMcA==
- dependencies:
- detect-node-es "^1.1.0"
- tslib "^1.9.3"
-
use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"