);
diff --git a/src/controls/ribbon/style.css b/src/controls/ribbon/style.css
index f2a5924..7338393 100644
--- a/src/controls/ribbon/style.css
+++ b/src/controls/ribbon/style.css
@@ -4,6 +4,7 @@
:import("../icon/style.css") { icon: icon; }
:import("../grid/style.css") { grid_class: grid; } /* NOTE: Weird alias to avoid conflicting with `display: grid;` */
:import("../progress-bar/style.css") { progressBar: bar; }
+:import("../status-indicator/style.css") { statusIndicator: statusIndicator; }
:import("../shared.css") { combinedButton: combinedButton; }
.ribbon {
@@ -44,4 +45,8 @@
.progressButton {
display: grid;
}
+
+ .statusIndicator {
+ height: 100%;
+ }
}
diff --git a/src/controls/shared.css b/src/controls/shared.css
index 4b48996..f49e6c0 100644
--- a/src/controls/shared.css
+++ b/src/controls/shared.css
@@ -34,3 +34,71 @@
.combinedVertical {
/* flex-direction: column; */
}
+
+:global {
+ .react-resizable {
+ position: relative;
+ }
+ .react-resizable-handle {
+ position: absolute;
+ width: 20px;
+ height: 20px;
+ background-repeat: no-repeat;
+ background-origin: content-box;
+ box-sizing: border-box;
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2IDYiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiNmZmZmZmYwMCIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI2cHgiIGhlaWdodD0iNnB4Ij48ZyBvcGFjaXR5PSIwLjMwMiI+PHBhdGggZD0iTSA2IDYgTCAwIDYgTCAwIDQuMiBMIDQgNC4yIEwgNC4yIDQuMiBMIDQuMiAwIEwgNiAwIEwgNiA2IEwgNiA2IFoiIGZpbGw9IiMwMDAwMDAiLz48L2c+PC9zdmc+');
+ background-position: bottom right;
+ padding: 0 3px 3px 0;
+ }
+ .react-resizable-handle-sw {
+ bottom: 0;
+ left: 0;
+ cursor: sw-resize;
+ transform: rotate(90deg);
+ }
+ .react-resizable-handle-se {
+ bottom: 0;
+ right: 0;
+ cursor: se-resize;
+ }
+ .react-resizable-handle-nw {
+ top: 0;
+ left: 0;
+ cursor: nw-resize;
+ transform: rotate(180deg);
+ }
+ .react-resizable-handle-ne {
+ top: 0;
+ right: 0;
+ cursor: ne-resize;
+ transform: rotate(270deg);
+ }
+ .react-resizable-handle-w,
+ .react-resizable-handle-e {
+ top: 50%;
+ margin-top: -10px;
+ cursor: ew-resize;
+ }
+ .react-resizable-handle-w {
+ left: 0;
+ transform: rotate(135deg);
+ }
+ .react-resizable-handle-e {
+ right: 0;
+ transform: rotate(315deg);
+ }
+ .react-resizable-handle-n,
+ .react-resizable-handle-s {
+ left: 50%;
+ margin-left: -10px;
+ cursor: ns-resize;
+ }
+ .react-resizable-handle-n {
+ top: 0;
+ transform: rotate(225deg);
+ }
+ .react-resizable-handle-s {
+ bottom: 0;
+ transform: rotate(45deg);
+ }
+}
diff --git a/src/controls/status-indicator/index.jsx b/src/controls/status-indicator/index.jsx
new file mode 100644
index 0000000..abcb4ac
--- /dev/null
+++ b/src/controls/status-indicator/index.jsx
@@ -0,0 +1,19 @@
+"use strict";
+
+const React = require("react");
+
+const useTheme = require("../../util/themeable");
+const defaultStyle = require("./style.css");
+const generateGridItemStyle = require("../../util/generate-grid-item-style");
+
+module.exports = function StatusIndicator({ x, y, status, children }) {
+ let { withTheme } = useTheme({ control: "statusIndicator", defaultStyle });
+
+ return (
+
+ );
+};
diff --git a/src/controls/status-indicator/style.css b/src/controls/status-indicator/style.css
new file mode 100644
index 0000000..1591caf
--- /dev/null
+++ b/src/controls/status-indicator/style.css
@@ -0,0 +1,20 @@
+.statusIndicator {
+ composes: centerContent from "../shared.css";
+ border-radius: 2px;
+}
+
+.content {
+ padding: 4px; /* FIXME: Why do we have this? */
+}
+
+.status-positive {
+ background-color: rgb(0, 93, 9);
+}
+
+.status-negative {
+ background-color: rgb(106, 0, 0);
+}
+
+.status-neutral {
+ background-color: #5a5a5a;
+}
diff --git a/src/controls/text/index.jsx b/src/controls/text/index.jsx
index f72f92e..557294e 100644
--- a/src/controls/text/index.jsx
+++ b/src/controls/text/index.jsx
@@ -14,7 +14,7 @@ module.exports = function Text({ x, y, align, children }) {
: undefined;
return (
-
+
{children}
);
diff --git a/src/controls/text/style.css b/src/controls/text/style.css
index ad89323..0826a3e 100644
--- a/src/controls/text/style.css
+++ b/src/controls/text/style.css
@@ -1,5 +1,7 @@
.text {
+ /* FIXME: Should be moved to ribbon-only, but this is probably not possible with ICSS */
composes: centerContent from "../shared.css";
+ background-color: transparent;
}
.align-left {
diff --git a/src/index.js b/src/index.js
index 189ef18..8401afd 100644
--- a/src/index.js
+++ b/src/index.js
@@ -9,7 +9,7 @@ module.exports = {
ButtonSet: require("./controls/button-set/index.jsx"),
ProgressBar: require("./controls/progress-bar/index.jsx"),
ProgressButton: require("./controls/progress-button/index.jsx"),
- // Text: require("./controls/text.jsx"),
+ StatusIndicator: require("./controls/status-indicator/index.jsx"),
// /* Pane layout */
// PaneLayout: require("./controls/pane-layout/layout.jsx"),
@@ -40,11 +40,18 @@ module.exports = {
// RibbonStatusIndicator: require("./controls/ribbon/status-indicator.jsx"),
// RibbonText: require("./controls/ribbon/text.jsx")
+ Pane: require("./controls/pane"),
+ PaneSet: require("./controls/pane-set"),
+
SetTheme: require("./util/themeable/set-theme"),
themes: {
dark: {
getIcon: (name) => `/icons/${name}.svg`,
css: require("./themes/dark.css")
+ },
+ light: {
+ getIcon: (name) => `/icons/${name}.svg`,
+ css: require("./themes/light.css")
}
}
};
diff --git a/src/themes/dark.css b/src/themes/dark.css
index 754d7f2..82074f6 100644
--- a/src/themes/dark.css
+++ b/src/themes/dark.css
@@ -1,4 +1,5 @@
$darkGray: rgb(34, 34, 34);
+$lessDarkGray: rgb(44, 44, 44);
$hoverColor: rgba(113, 113, 113, 0.26);
$activeColor: $darkGray;
@@ -25,13 +26,23 @@ body {
inset -1px -1px rgba(135, 131, 131, 0.3);
}
-.bar {
- composes: edgeRaise;
- background-color: rgb(60, 62, 66);
+.uilibThemedElement {
+ color: white;
}
+/* Default to a black background for any newly-defined components that don't have their own styles yet, for minimum readability */
.uilibComponent {
- color: white;
+ background-color: black;
+}
+
+/* ... but don't apply this to known container-type components which don't have their own styling */
+.icon_icon, .grid_grid, .ribbonBox_box {
+ background-color: transparent;
+}
+
+.bar {
+ composes: edgeRaise;
+ background-color: rgb(60, 62, 66);
}
.list_list {
@@ -89,7 +100,7 @@ body {
.button_button {
background-color: #38383c;
- // background-color: magenta;
+ /* background-color: magenta; */
border: none;
&:hover {
@@ -126,6 +137,12 @@ body {
background-color: rgb(54, 135, 18);
}
+.pane_handle {
+ background-color: $darkGray;
+ composes: edgeRaise;
+}
+
.ribbon_ribbon {
/* ... */
}
+
diff --git a/src/themes/light.css b/src/themes/light.css
new file mode 100644
index 0000000..dcb5fc2
--- /dev/null
+++ b/src/themes/light.css
@@ -0,0 +1,141 @@
+$darkGray: rgb(34, 34, 34);
+$lessDarkGray: rgb(44, 44, 44);
+$hoverColor: #e5e5e5;
+$activeColor: #c5dae5;
+
+$white: white;
+$barColor: #edf5f5;
+$lightGray: silver;
+
+/* :export {
+ darkGray: $darkGray;
+ hoverColor: $hoverColor;
+ activeColor: $activeColor;
+} */
+
+/* FIXME: Casemap control names */
+
+body {
+ /* FIXME: Think about whether this belongs in the theme, or whether it should be scoped to some sort of ApplicationFrame instead. */
+ background-color: white;
+ color: black;
+}
+
+.edgeRaise {
+ box-shadow: inset -1px -1px rgba(187, 183, 183, 0.7),
+ inset 1px 1px rgba(206, 206, 206, 0.3);
+}
+
+.edgeLower {
+ box-shadow: inset 1px 1px rgba(187, 183, 183, 0.7),
+ inset -1px -1px rgba(206, 206, 206, 0.3);
+}
+
+.bar {
+ composes: edgeRaise;
+ background-color: $barColor;
+}
+
+.uilibComponent {
+ color: black;
+}
+
+.list_list {
+ /* color: red; */
+}
+
+.list_item {
+ &:nth-child(odd) {
+ /* background-color: rgb(45, 45, 45); */
+ /* background-color: rgb(51, 51, 51); */
+ background-color: rgb(57, 57, 60);
+ }
+
+ &:nth-child(even) {
+ background-color: rgb(27, 27, 27);
+ }
+
+ &.list_item_selected {
+ /* FIXME: Find a better color for this. */
+ /* background-color: rgb(38, 38, 42); */
+ background-color: blue; /* FIXME: Remove testing color */
+ }
+}
+
+.menu_menuBar, .ribbon_ribbon {
+ composes: bar;
+}
+
+.menu_menuBar > .menu_item {
+ &:hover {
+ background-color: $hoverColor;
+ }
+
+ &.menu_item_selected, &menu_item_directPress {
+ background-color: $activeColor;
+ }
+}
+
+.menu_menu {
+ background-color: $barColor;
+ box-shadow: 1px 1px 2px rgb(54, 54, 54);
+
+ & > .menu_item:hover {
+ background-color: $hoverColor;
+ }
+
+ hr {
+ border-bottom: 1px solid rgb(78, 78, 78);
+ }
+}
+
+.menu_divider {
+ border-bottom: 1px solid rgb(78, 78, 78);
+}
+
+.button_button {
+ background-color: #d0d0d0;
+ /* background-color: magenta; */
+ border: none;
+
+ &:hover {
+ background-color: $hoverColor;
+ }
+
+ &:active {
+ @include edge-lower;
+ background-color: $activeColor;
+ }
+
+ &.button_selected {
+ background-color: $darkGray;
+ }
+}
+
+.ribbonBox_box {
+ border-left: 1px solid rgba(30, 27, 27, 0.7);
+ border-right: 1px solid rgba(135, 131, 131, 0.3);
+}
+
+.ribbonBox_label {
+ background-color: #f3f1fa;
+}
+
+.progressBar_bar {
+ border: 1px solid rgb(32, 32, 32);
+ /* background-color: rgb(64, 64, 70); */
+ background-color: rgb(43, 43, 47);
+}
+
+.progressBar_fill {
+ background-color: rgb(54, 135, 18);
+}
+
+.pane_handle {
+ background-color: rgb(245, 245, 245);
+ composes: edgeRaise;
+}
+
+.ribbon_ribbon {
+ /* ... */
+}
diff --git a/src/util/children-with-props.js b/src/util/children-with-props.js
new file mode 100644
index 0000000..1927af5
--- /dev/null
+++ b/src/util/children-with-props.js
@@ -0,0 +1,20 @@
+"use strict";
+
+// NOTE: Only use this where absolutely needed! Normally, the context API should be preferred where possible.
+
+const React = require("react");
+
+module.exports = function childrenWithProps(children, mapper) {
+ return React.Children.map(children, (child, i) => {
+ let extraProps = (typeof mapper === "function")
+ ? mapper(child, i)
+ : mapper;
+
+ if (extraProps != null) {
+ // FIXME: Do we need to check React.isValidElement here, to deal with eg. text nodes? Or is that handled internally by React.cloneElement?
+ return React.cloneElement(child, extraProps);
+ } else {
+ return child;
+ }
+ });
+};
diff --git a/src/util/generate-grid-template-string.js b/src/util/generate-grid-template-string.js
new file mode 100644
index 0000000..dc0ba64
--- /dev/null
+++ b/src/util/generate-grid-template-string.js
@@ -0,0 +1,15 @@
+"use strict";
+
+function normalizeGridCellSize(value) {
+ if (typeof value === "number") {
+ return `${value}fr`;
+ } else {
+ return value;
+ }
+}
+
+module.exports = function generateGridTemplateString(values) {
+ return values
+ .map((value) => normalizeGridCellSize(value))
+ .join(" ");
+};
diff --git a/src/util/sum.js b/src/util/sum.js
new file mode 100644
index 0000000..8873e11
--- /dev/null
+++ b/src/util/sum.js
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = function sum(numbers) {
+ return numbers.reduce((total, number) => total + number, 0);
+};
diff --git a/src/util/themeable/index.jsx b/src/util/themeable/index.jsx
index e8a7435..8e2ef43 100644
--- a/src/util/themeable/index.jsx
+++ b/src/util/themeable/index.jsx
@@ -20,6 +20,8 @@ function mapDefaultName(defaultStyle, className) {
module.exports = function useTheme({ control, defaultStyle }) {
let theme = React.useContext(ThemeContext);
+ console.log({ css: theme.css });
+
let rulePrefix = (control != null)
? `${control}_`
: "";
@@ -37,9 +39,16 @@ module.exports = function useTheme({ control, defaultStyle }) {
(theme != null)
? mapThemeName(theme.css, className, rulePrefix)
: null,
- mapDefaultName(globalStyle, "uilibComponent"),
+ mapDefaultName(globalStyle, "uilibThemedElement"),
(theme != null)
- ? mapThemeName(theme.css, "uilibComponent", "")
+ ? mapThemeName(theme.css, "uilibThemedElement", "")
+ : null,
+ // Special case: unprefixed control-independent class names -- FIXME: This check does not work for conditional class names!
+ (className === "uilibComponent")
+ ? mapDefaultName(globalStyle, className)
+ : null,
+ (theme != null && className === "uilibComponent")
+ ? mapThemeName(theme.css, className, "")
: null,
];
}),
diff --git a/src/util/use-disable-selection.js b/src/util/use-disable-selection.js
new file mode 100644
index 0000000..ac270e7
--- /dev/null
+++ b/src/util/use-disable-selection.js
@@ -0,0 +1,22 @@
+"use strict";
+
+const React = require("react");
+
+module.exports = function useDisableSelection() {
+ let body = document.querySelector("body");
+ let lastSelectSetting = React.useRef();
+
+ return {
+ disableGlobalSelection: function () {
+ if (body.style.userSelect !== "none") {
+ lastSelectSetting.current = body.style.userSelect;
+ body.style.userSelect = "none";
+ }
+ },
+ enableGlobalSelection: function () {
+ if (body.style.userSelect === "none" && lastSelectSetting.current != null) {
+ body.style.userSelect = lastSelectSetting.current;
+ }
+ }
+ };
+};
diff --git a/src/util/use-indexed-state.js b/src/util/use-indexed-state.js
new file mode 100644
index 0000000..742ff77
--- /dev/null
+++ b/src/util/use-indexed-state.js
@@ -0,0 +1,30 @@
+"use strict";
+
+const React = require("react");
+const timm = require("timm");
+
+module.exports = function useIndexedState(initialState = []) {
+ let [ state, setState ] = React.useState(initialState);
+
+ function setIndexedState(index, value) {
+ setState((oldArray) => {
+ let evaluatedValue = (typeof value === "function")
+ ? value(oldArray[index])
+ : value;
+
+ return timm.replaceAt(oldArray, index, evaluatedValue);
+ });
+ }
+
+ function resetState() {
+ setState((oldArray) => {
+ return oldArray.map(() => undefined);
+ });
+ }
+
+ return [
+ state,
+ setIndexedState,
+ resetState
+ ];
+};
diff --git a/yarn.lock b/yarn.lock
index dbd55db..96c981c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1113,7 +1113,12 @@ chardet@^0.7.0:
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
-classnames@^2.2.6:
+clamp@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/clamp/-/clamp-1.0.1.tgz#66a0e64011816e37196828fdc8c8c147312c8634"
+ integrity sha1-ZqDmQBGBbjcZaCj9yMjBRzEshjQ=
+
+classnames@^2.2.5, classnames@^2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
@@ -1841,7 +1846,7 @@ lodash@^4.17.19:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
-loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
+loose-envify@^1.0.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -2115,7 +2120,7 @@ progress@^2.0.0:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
-prop-types@^15.6.2, prop-types@^15.7.2:
+prop-types@15.x, prop-types@^15.6.0, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -2129,11 +2134,27 @@ punycode@^2.1.0:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
+react-draggable@^4.0.3:
+ version "4.4.3"
+ resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.3.tgz#0727f2cae5813e36b0e4962bf11b2f9ef2b406f3"
+ integrity sha512-jV4TE59MBuWm7gb6Ns3Q1mxX8Azffb7oTtDtBgFkxRvhDp38YAARmRplrj0+XGkhOJB5XziArX+4HUUABtyZ0w==
+ dependencies:
+ classnames "^2.2.5"
+ prop-types "^15.6.0"
+
react-is@^16.8.1:
version "16.12.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==
+react-resizable@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/react-resizable/-/react-resizable-2.0.0.tgz#e254908acd949e52015ecf56e9788dc73b8b510d"
+ integrity sha512-oNVMKk+IQNW+nxcCB1W1uShZLJhIh3TDSW8NAbfck6N2jsiUTQ/V5ozBVFEHlRsxgxkf2A22rZJnXLRCBF14OA==
+ dependencies:
+ prop-types "15.x"
+ react-draggable "^4.0.3"
+
react-use-measure@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/react-use-measure/-/react-use-measure-2.0.1.tgz#4f23f94c832cd4512da55acb300d1915dcbf3ae8"
@@ -2141,10 +2162,6 @@ react-use-measure@^2.0.1:
dependencies:
debounce "^1.2.0"
-"react@link:../site-builder/node_modules/react":
- version "0.0.0"
- uid ""
-
readable-stream@^3.4.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
@@ -2471,6 +2488,11 @@ through@^2.3.6:
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
+timm@^1.7.1:
+ version "1.7.1"
+ resolved "https://registry.yarnpkg.com/timm/-/timm-1.7.1.tgz#96bab60c7d45b5a10a8a4d0f0117c6b7e5aff76f"
+ integrity sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==
+
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"