initial structure for components rewrite

This commit is contained in:
Rodrigo Antinarelli 2020-02-13 19:43:20 -03:00 committed by James A. Petts
parent 944afe4a3e
commit c391c007ff
284 changed files with 68087 additions and 17215 deletions

View File

@ -1,135 +0,0 @@
{
"projectName": "@ohif/ui",
"projectOwner": "OHIF",
"repoType": "github",
"repoHost": "https://github.com",
"files": [
"README.md"
],
"imageSize": 100,
"commit": true,
"commitConvention": "none",
"contributors": [
{
"login": "swederik",
"name": "Erik Ziegler",
"avatar_url": "https://avatars3.githubusercontent.com/u/607793?v=4",
"profile": "https://github.com/swederik",
"contributions": [
"code",
"maintenance"
]
},
{
"login": "dannyrb",
"name": "Danny Brown",
"avatar_url": "https://avatars1.githubusercontent.com/u/5797588?v=4",
"profile": "http://dannyrb.com/",
"contributions": [
"code",
"maintenance"
]
},
{
"login": "galelis",
"name": "Gustavo André Lelis",
"avatar_url": "https://avatars3.githubusercontent.com/u/2378326?v=4",
"profile": "https://github.com/galelis",
"contributions": [
"code",
"maintenance"
]
},
{
"login": "maltempi",
"name": "Thiago Maltempi",
"avatar_url": "https://avatars3.githubusercontent.com/u/7017182?v=4",
"profile": "https://github.com/maltempi",
"contributions": [
"code"
]
},
{
"login": "EsrefDurna",
"name": "Esref Durna",
"avatar_url": "https://avatars0.githubusercontent.com/u/1230575?v=4",
"profile": "https://www.linkedin.com/in/siliconvalleynextgeneration/",
"contributions": [
"code"
]
},
{
"login": "pieper",
"name": "Steve Pieper",
"avatar_url": "https://avatars0.githubusercontent.com/u/126077?v=4",
"profile": "http://www.isomics.com",
"contributions": [
"code"
]
},
{
"login": "biharck",
"name": "Biharck Araujo",
"avatar_url": "https://avatars0.githubusercontent.com/u/1713255?v=4",
"profile": "http://www.biharck.com.br",
"contributions": [
"code"
]
},
{
"login": "rodrigolabs",
"name": "Rodrigo Antinarelli",
"avatar_url": "https://avatars3.githubusercontent.com/u/1905961?v=4",
"profile": "https://rodrigoea.com/",
"contributions": [
"code"
]
},
{
"login": "jfmedeiros1820",
"name": "João Felipe de Medeiros Moreira",
"avatar_url": "https://avatars1.githubusercontent.com/u/2211708?v=4",
"profile": "https://github.com/jfmedeiros1820",
"contributions": [
"code"
]
},
{
"login": "jamesg1",
"name": "James Gosbell",
"avatar_url": "https://avatars3.githubusercontent.com/u/3621147?v=4",
"profile": "https://github.com/jamesg1",
"contributions": [
"code"
]
},
{
"login": "evren217",
"name": "Evren Ozkan",
"avatar_url": "https://avatars1.githubusercontent.com/u/4920551?v=4",
"profile": "https://github.com/evren217",
"contributions": [
"code"
]
},
{
"login": "zsaltzman",
"name": "Zach S.",
"avatar_url": "https://avatars1.githubusercontent.com/u/19156530?v=4",
"profile": "https://github.com/zsaltzman",
"contributions": [
"code"
]
},
{
"login": "muakdogan",
"name": "Mete Ugur Akdogan",
"avatar_url": "https://avatars0.githubusercontent.com/u/19971240?v=4",
"profile": "https://github.com/muakdogan",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7
}

View File

@ -1,11 +0,0 @@
####
##
## We need to pin WebPack's version for docz to work. See this issue:
## https://github.com/pedronauck/docz/issues/704#issuecomment-480295032
##
## react-scripts checks that dependency versions match before running
## Our top level webpack version causes the preflight check to fail
## This prevents that check
##
####
SKIP_PREFLIGHT_CHECK=true

2
platform/ui/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.docz
node_modules

View File

@ -0,0 +1,3 @@
{
"extends": "stylelint-config-standard"
}

View File

@ -1,37 +0,0 @@
const merge = require('webpack-merge');
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const pkg = require('./../package.json');
const ROOT_DIR = path.join(__dirname, './..');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
module.exports = (env, argv) => {
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
return merge(commonConfig, {
devtool: 'source-map',
stats: {
colors: true,
hash: true,
timings: true,
assets: true,
chunks: false,
chunkModules: false,
modules: false,
children: false,
warnings: true,
},
optimization: {
minimize: true,
sideEffects: true,
},
output: {
path: ROOT_DIR,
library: 'ohifUi',
libraryTarget: 'umd',
filename: pkg.main,
},
});
};

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2018 Open Health Imaging Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,125 +1,41 @@
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<div align="center">
<h1>@ohif/ui</h1>
# Basic Docz example
<p><strong>@ohif/ui</strong> is a collection of components and utilities that power OHIF's <a href="https://github.com/OHIF/Viewers">zero-footprint DICOM viewer</a>.</p>
</div>
## Using `create-docz-app`
<div align="center">
<a href="https://react.ohif.org/"><strong>Read The Docs</strong></a> |
<a href="https://react.ohif.org/contributing">Edit the docs</a>
</div>
<hr />
[![NPM version][npm-version-image]][npm-url]
[![NPM downloads][npm-downloads-image]][npm-url]
[![All Contributors](https://img.shields.io/badge/all_contributors-13-orange.svg?style=flat-square)](#contributors)
[![MIT License][license-image]][license-url]
<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->
This set of "Medical Imaging Viewer" React components are maintained separately
to:
- Decouple presentation from business logic
- Test and develop components in isolation
- Provide well documented, reusable components
- Aid rapid application development for context specific viewers
## Install
> This component library is pre- v1.0. All realeases until a v1.0 have the
> possibility of introducing breaking changes. Please depend on an "exact"
> version in your projects to prevent issues caused by loose versioning.
For full installation instructions, be sure to check out our
[getting started](https://react.ohif.org/getting-started#installation) guide.
```bash
// with npm
npm i @ohif/ui --save-exact
// with yarn
yarn add @ohif/ui --exact
```sh
npx create-docz-app docz-app-basic
# or
yarn create docz-app docz-app-basic
```
## Usage
## Download manually
```jsx
import React, { Component } from 'react';
import { LayoutButton } from '@ohif/ui';
class Example extends Component {
constructor(props) {
super(props);
this.state = {
selectedCell: {
className: 'hover',
col: 1,
row: 1,
},
};
}
render() {
return (
<LayoutButton
selectedCell={this.state.selectedCell}
onChange={cell => this.setState({ selectedCell: cell })}
/>
);
}
}
```sh
curl https://codeload.github.com/doczjs/docz/tar.gz/master | tar -xz --strip=2 docz-master/examples/basic
mv basic docz-basic-example
cd docz-basic-example
```
## Developing Locally
_Restore dependencies after cloning:_
```bash
# Restore workspace dependencies (from repository root)
yarn install
# From project root
yarn run dev:ui
# OR from this project's directory
yarn run dev
## Setup
```sh
yarn # npm i
```
## Contributors ✨
## Run
Thanks goes to these wonderful people
([emoji key](https://allcontributors.org/docs/en/emoji-key)):
```sh
yarn dev # npm run dev
```
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore -->
<table><tr><td align="center"><a href="https://github.com/swederik"><img src="https://avatars3.githubusercontent.com/u/607793?v=4" width="100px;" alt="Erik Ziegler"/><br /><sub><b>Erik Ziegler</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=swederik" title="Code">💻</a> <a href="#maintenance-swederik" title="Maintenance">🚧</a></td><td align="center"><a href="http://dannyrb.com/"><img src="https://avatars1.githubusercontent.com/u/5797588?v=4" width="100px;" alt="Danny Brown"/><br /><sub><b>Danny Brown</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=dannyrb" title="Code">💻</a> <a href="#maintenance-dannyrb" title="Maintenance">🚧</a></td><td align="center"><a href="https://github.com/galelis"><img src="https://avatars3.githubusercontent.com/u/2378326?v=4" width="100px;" alt="Gustavo André Lelis"/><br /><sub><b>Gustavo André Lelis</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=galelis" title="Code">💻</a> <a href="#maintenance-galelis" title="Maintenance">🚧</a></td><td align="center"><a href="https://github.com/maltempi"><img src="https://avatars3.githubusercontent.com/u/7017182?v=4" width="100px;" alt="Thiago Maltempi"/><br /><sub><b>Thiago Maltempi</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=maltempi" title="Code">💻</a></td><td align="center"><a href="https://www.linkedin.com/in/siliconvalleynextgeneration/"><img src="https://avatars0.githubusercontent.com/u/1230575?v=4" width="100px;" alt="Esref Durna"/><br /><sub><b>Esref Durna</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=EsrefDurna" title="Code">💻</a></td><td align="center"><a href="http://www.isomics.com"><img src="https://avatars0.githubusercontent.com/u/126077?v=4" width="100px;" alt="Steve Pieper"/><br /><sub><b>Steve Pieper</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=pieper" title="Code">💻</a></td><td align="center"><a href="http://www.biharck.com.br"><img src="https://avatars0.githubusercontent.com/u/1713255?v=4" width="100px;" alt="Biharck Araujo"/><br /><sub><b>Biharck Araujo</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=biharck" title="Code">💻</a></td></tr><tr><td align="center"><a href="https://rodrigoea.com/"><img src="https://avatars3.githubusercontent.com/u/1905961?v=4" width="100px;" alt="Rodrigo Antinarelli"/><br /><sub><b>Rodrigo Antinarelli</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=rodrigolabs" title="Code">💻</a></td><td align="center"><a href="https://github.com/jfmedeiros1820"><img src="https://avatars1.githubusercontent.com/u/2211708?v=4" width="100px;" alt="João Felipe de Medeiros Moreira"/><br /><sub><b>João Felipe de Medeiros Moreira</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=jfmedeiros1820" title="Code">💻</a></td><td align="center"><a href="https://github.com/jamesg1"><img src="https://avatars3.githubusercontent.com/u/3621147?v=4" width="100px;" alt="James Gosbell"/><br /><sub><b>James Gosbell</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=jamesg1" title="Code">💻</a></td><td align="center"><a href="https://github.com/evren217"><img src="https://avatars1.githubusercontent.com/u/4920551?v=4" width="100px;" alt="Evren Ozkan"/><br /><sub><b>Evren Ozkan</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=evren217" title="Code">💻</a></td><td align="center"><a href="https://github.com/zsaltzman"><img src="https://avatars1.githubusercontent.com/u/19156530?v=4" width="100px;" alt="Zach S."/><br /><sub><b>Zach S.</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=zsaltzman" title="Code">💻</a></td><td align="center"><a href="https://github.com/muakdogan"><img src="https://avatars0.githubusercontent.com/u/19971240?v=4" width="100px;" alt="Mete Ugur Akdogan"/><br /><sub><b>Mete Ugur Akdogan</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=muakdogan" title="Code">💻</a></td></tr></table>
## Build
<!-- ALL-CONTRIBUTORS-LIST:END -->
```sh
yarn build # npm run build
```
This project follows the
[all-contributors](https://github.com/all-contributors/all-contributors)
specification. Contributions of any kind welcome!
## Serve built app
## License
MIT © [OHIF](https://github.com/OHIF)
<!--
Links:
-->
<!-- prettier-ignore-start -->
[all-contributors-image]: https://img.shields.io/badge/all_contributors-0-orange.svg?style=flat-square
[npm-url]: https://npmjs.org/package/@ohif/ui
[npm-downloads-image]: https://img.shields.io/npm/dm/@ohif/ui.svg?style=flat-square
[npm-version-image]: https://img.shields.io/npm/v/@ohif/ui.svg?style=flat-square
[license-image]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square
[license-url]: LICENSE
<!-- prettier-ignore-end -->
```sh
yarn serve # npm run serve
```

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
module.exports = require("../../babel.config.js");

View File

@ -1,75 +0,0 @@
/**
* Docz Configuration File:
* https://www.docz.site/docs/project-configuration
*/
import { css } from "docz-plugin-css";
export default {
dest: "example/build",
public: "/public",
wrapper: "src/__docs__/wrapper",
indexHtml: "src/__docs__/index.html",
// Limited support for importing `.styl` files
codeSandbox: false,
// https://github.com/pedronauck/docz/pull/849/files
// Because we re-export using `index.js` files
notUseSpecifiers: true,
menu: [
"Introduction",
"Getting Started",
{ name: "Elements", menu: ["Form / Select"] },
{
name: "Components",
menu: [
"CINE Dialog",
"Layout Button",
"Measurement Table",
"Overlay Trigger",
"Quick Switch",
"Rounded Button Group",
"Select Tree",
"Simple Dialog",
"Study Browser",
"Study List",
"Table List",
"Toolbar Section",
"About Modal",
"User Preferences Modal"
]
},
"Styling & Theming",
"Translating",
"Compatibility"
],
// Rollup Aliases?
// https://github.com/pedronauck/docz/issues/373
plugins: [
// CSS
css(),
// Stylus
css({
preprocessor: "stylus",
cssmodules: false
})
],
// `docz` uses file-loader to pull in SVGs. This kills our icons before
// They can be picked up by our `babel-plugin-inline-react-svg` dependency
// You can see where it's configured here:
// https://github.com/pedronauck/docz/blob/f72624d0aa5231dab17e1770e8d36be920f590a2/core/docz-core/src/bundler/loaders.ts#L135-L144
// How we delete our rule:
// https://github.com/neutrinojs/webpack-chain/issues/48
onCreateWebpackChain: config => {
config.module.rules.delete("svg");
},
modifyBabelRc: babelrc => {
const newBabelRc = {
...babelrc,
plugins: [
...babelrc.plugins,
require.resolve("babel-plugin-inline-react-svg")
]
};
return newBabelRc;
}
};

View File

@ -1,10 +0,0 @@
const base = require("../../jest.config.base.js");
const pkg = require("./package");
const esModules = ["react-dnd"];
module.exports = {
...base,
name: pkg.name,
displayName: pkg.name
};

View File

@ -1,12 +1,10 @@
{
"name": "@ohif/ui",
"version": "1.4.4",
"version": "2.0.0",
"description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors",
"license": "MIT",
"repository": "OHIF/Viewers",
"main": "dist/index.umd.js",
"module": "src/index.js",
"publishConfig": {
"access": "public"
},
@ -20,51 +18,24 @@
"README.md"
],
"scripts": {
"dev": "docz dev",
"dev:ui": "yarn run dev",
"docs": "docz build",
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
"build:package": "yarn run build",
"prepublishOnly": "yarn run build",
"test:unit": "jest --watchAll",
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/i18n": "^0.2.3",
"i18next": "^17.0.3",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-i18next": "^10.11.0"
"dev": "concurrently \"npm run watch:css\" \"docz dev\"",
"build": "concurrently \"npm run build:css\" \"docz build\"",
"serve": "docz serve",
"build:css": "postcss src/styles/tailwind.css -o src/gatsby-theme-docz/app.css",
"watch:css": "postcss src/styles/tailwind.css -o src/gatsby-theme-docz/app.css -w"
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"@ohif/i18n": "^0.2.3",
"browser-detect": "^0.2.28",
"classnames": "2.2.6",
"i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1",
"lodash.debounce": "^4.0.8",
"lodash.isequal": "4.5.0",
"moment": "2.24.0",
"prop-types": "15.6.2",
"react-bootstrap-modal": "4.2.0",
"react-dates": "21.2.1",
"react-dnd": "9.4.0",
"react-dnd-html5-backend": "^9.4.0",
"react-dnd-touch-backend": "^9.4.0",
"react-draggable": "^4.1.0",
"react-i18next": "^10.11.0",
"react-modal": "^3.11.1",
"react-with-direction": "1.3.0"
"concurrently": "^5.1.0",
"docz": "latest",
"prop-types": "^15.7.2",
"react": "^16.11.0",
"react-dom": "^16.11.0"
},
"devDependencies": {
"@mdx-js/tag": "^0.20.3",
"autoprefixer": "^9.6.0",
"docz": "1.2.0",
"docz-core": "1.2.0",
"docz-plugin-css": "0.11.0",
"docz-theme-default": "1.2.0",
"lodash.clonedeep": "^4.5.0",
"react-powerplug": "1.0.0"
"autoprefixer": "^9.7.4",
"postcss-cli": "^7.1.0",
"stylelint": "^13.1.0",
"stylelint-config-standard": "^20.0.0",
"tailwindcss": "^1.2.0"
}
}

View File

@ -1 +1,7 @@
module.exports = require("../../postcss.config.js");
const tailwindcss = require('tailwindcss');
const configs = require('../../postcss.config.js');
module.exports = {
...configs,
plugins: [tailwindcss('./tailwind.js'), require('autoprefixer')],
};

View File

@ -1,44 +0,0 @@
/* Tip & Success "Call outs" */
p.tip {
border-left-color: #f66;
}
p.success {
border-left-color: #42b983;
}
p.tip::before {
content: '!';
background-color: #f66;
}
p.success::before {
content: '\f00c';
font-family: FontAwesome;
background-color: #42b983;
}
p.tip,
p.success {
padding: 12px 24px 12px 30px;
margin: 2em 0;
line-height: 1.6em;
border-left-width: 4px;
border-left-style: solid;
background-color: #f8f8f8;
position: relative;
border-bottom-right-radius: 2px;
border-top-right-radius: 2px;
}
p.tip::before,
p.success::before {
position: absolute;
top: 14px;
left: -12px;
color: #fff;
width: 20px;
height: 20px;
border-radius: 100%;
text-align: center;
line-height: 20px;
font-weight: bold;
font-family: 'Dosis', 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif;
font-size: 14px;
}

View File

@ -1,152 +0,0 @@
import './ScrollableArea.styl';
import React, { Component } from 'react';
import { Icon } from './../elements/Icon';
import PropTypes from 'prop-types';
import getScrollbarSize from '../utils/getScrollbarSize.js';
import throttled from '../utils/throttled.js';
export class ScrollableArea extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
class: PropTypes.string,
scrollableClass: PropTypes.string,
scrollX: PropTypes.bool,
scrollY: PropTypes.bool,
hideScrollbar: PropTypes.bool,
scrollStep: PropTypes.number,
};
static defaultProps = {
hideScrollbar: true,
class: 'flex-grow fit',
scrollY: true,
scrollX: false,
scrollStep: 100,
};
constructor(props) {
super(props);
this.state = {
scrollAreaClasses: '',
};
this.scrollHandlerThrottled = throttled(150, this.scrollHandler);
}
render() {
let scrollableClass = 'scrollable';
if (this.props.scrollableClass) {
scrollableClass += ` ${this.props.scrollableClass}`;
}
if (this.props.scrollX) {
scrollableClass += ` scrollX`;
}
if (this.props.scrollY) {
scrollableClass += ` scrollY`;
}
return (
<div
className={`scrollArea ${this.props.class} ${
this.state.scrollAreaClasses
}`}
>
<div
className={scrollableClass}
ref={element => {
this.scrollableElement = element;
}}
onScroll={this.scrollHandlerThrottled}
onMouseEnter={this.scrollHandlerThrottled}
onTransitionEnd={this.scrollHandlerThrottled}
>
{this.props.children}
</div>
<div className="scrollNav scrollNavUp" onClick={this.scrollNavUp}>
{/* <svg className="scrollNavIcon"> */}
<Icon name="angle-double-up" />
</div>
<div className="scrollNav scrollNavDown" onClick={this.scrollNavDown}>
<Icon name="angle-double-down" />
</div>
</div>
);
}
componentDidMount() {
this.adjustMargins();
this.scrollHandler();
window.addEventListener('resize', this.adjustMargins);
}
componentDidUpdate() {
this.adjustMargins();
this.scrollHandler();
}
componentWillUnmount() {
window.removeEventListener('resize', this.adjustMargins);
}
scrollNavDown = event => {
const {
scrollTop: currentTop,
offsetHeight: height,
scrollHeight,
} = this.scrollableElement;
const limit = scrollHeight - height;
let scrollTop = currentTop + this.props.scrollStep;
scrollTop = scrollTop > limit ? limit : scrollTop;
this.scrollableElement.scrollTop = scrollTop;
};
scrollNavUp = event => {
const { scrollTop: currentTop } = this.scrollableElement;
let scrollTop = currentTop - this.props.scrollStep;
scrollTop = scrollTop < 0 ? 0 : scrollTop;
this.scrollableElement.scrollTop = scrollTop;
};
adjustMargins = () => {
if (this.props.hideScrollbar) {
const x = this.props.scrollX ? 1 : 0;
const y = this.props.scrollY ? 1 : 0;
const scrollbarSize = getScrollbarSize();
this.scrollableElement.style.marginRight = `${0 -
scrollbarSize[0] * y}px`;
this.scrollableElement.style.marginBottom = `${0 -
scrollbarSize[1] * x}px`;
}
};
scrollHandler = () => {
const {
offsetHeight: height,
scrollTop: scrollTop,
scrollHeight,
} = this.scrollableElement;
let scrollAreaClasses = '';
// Check if can scroll up
if (scrollTop) {
scrollAreaClasses += 'canScrollUp';
}
// Check if can scroll down
if (scrollTop + height < scrollHeight) {
scrollAreaClasses += ' canScrollDown';
}
if (this.state.scrollAreaClasses !== scrollAreaClasses) {
this.setState({
scrollAreaClasses,
});
}
};
}

View File

@ -1,69 +0,0 @@
.scrollArea
overflow: hidden
position: relative
.scrollable
max-height: inherit
overflow: hidden
zoom: 1
&.scrollX
overflow-x: scroll
&.scrollY
overflow-y: scroll
&.fit
height: 100%
width: 100%
.scrollable
bottom: 0
left: 0
max-height: none
position: absolute
right: 0
top: 0
.scrollNav
background-color: rgba(0, 0, 0, 0.75)
box-shadow: 0 0 10px 10px rgba(0, 0, 0, 0.75)
cursor: pointer
height: 24px
left: 10px
opacity: 0
position: absolute
right: 10px
transition: all 0.3s ease
.scrollNavIcon
color: var(--active-color);
display: block;
width: 18px;
height: 18px;
margin: 0 auto;
transition: color 0.3s ease;
&:hover
background-color: rgba(0, 0, 0, 0.9)
box-shadow: 0 0 10px 10px rgba(0, 0, 0, 0.9)
.scrollNavIcon
color: var(--hover-color)
.scrollNavUp
border-bottom-left-radius: 12px
border-bottom-right-radius: 12px
top: 0
transform: translateY(-24px)
.scrollNavDown
border-top-left-radius: 12px
border-top-right-radius: 12px
bottom: 0
transform: translateY(24px)
&.canScrollUp .scrollNavUp,
&.canScrollDown .scrollNavDown
opacity: 1
transform: translateY(0)

View File

@ -1,17 +0,0 @@
import React, { Fragment } from 'react';
import { Link } from 'docz';
import PropTypes from 'prop-types';
const NameSpace = ({ name }) => (
<Fragment>
The namespace used on this component was: <pre>{name}</pre> Check the{' '}
<Link to="/translating#how-to-translate-components">translation docs</Link>{' '}
to see how to override it.
</Fragment>
);
NameSpace.propTypes = {
name: PropTypes.string.isRequired,
};
export default NameSpace;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,15 +0,0 @@
---
name: Compatibility
route: /compatibility
---
# Compatibility
## Browser Support
`@ohif/ui` relies on
[browsers supported by Styled Components](https://www.styled-components.com/docs/faqs#which-browsers-are-supported)
or browsers supported by emotion.
Supported Browsers: IE11, IE 9+ (with Map + Set polyfills), Chrome, Firefox (and
derivatives), Edge, and Safari.

View File

@ -1,13 +0,0 @@
---
name: Docs Settings
route: /docs-settings
---
import LanguageSwitcher from '../components/languageSwitcher';
# Components Language
You can change the language of the components displayed in this documentation by
choosing an option in the selector below:
<LanguageSwitcher />

View File

@ -1,76 +0,0 @@
---
name: Getting Started
route: /getting-started
---
# Getting Started
## Installation
> This component library is pre- v1.0. All realeases until a v1.0 have the
> possibility of introducing breaking changes. Please depend on an "exact"
> version in your projects to prevent issues caused by loose versioning.
Install `@ohif/ui` from npm:
```shell
// with npm
npm i @ohif/ui --save-exact
// with yarn
yarn add @ohif/ui --exact
```
## Usage
There is some setup required for `@ohif/ui`. We are working to eliminate these
requirements with the goal of providing ready to use components.
### External Dependencies
We have tried to keep the number of external dependencies limited to make setup
as easy as possible. You will, however, need to include either the default font
or the font you set for your theme:
```js
// Google Fonts, Roboto
'https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700&display=swap';
```
`@ohif/ui` also looks for theme CSS variabled defined on `:root`. You can find a
list of variables [on the theming page](/styling-and-theming)
### Import & Use
You can use components in your React app:
```js
import React, { Component } from 'react';
import { LayoutButton } from '@ohif/ui';
class Example extends Component {
constructor(props) {
super(props);
this.state = {
selectedCell: {
className: 'hover',
col: 1,
row: 1,
},
};
}
render() {
return (
<LayoutButton
selectedCell={this.state.selectedCell}
onChange={cell => this.setState({ selectedCell: cell })}
/>
);
}
}
```
> NOTE: It is heavily recommended that you use a CSS reset or normalize. As well
> as the following style: `* { Box-sizing: Border-box }`

View File

@ -1,83 +0,0 @@
<!DOCTYPE html>
<html lang="{{ lang }}">
<head>
<meta charset="UTF-8">
<meta name="description" content="{{ description }}">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{{ title }}</title>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="/public/docz.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700&display=swap">
<style>
/* "Tide" theme */
/* TODO:
- Convert the other themes to CSS Variables
- Allow user to set default CSS theme in JS config
- Export the themes as identical JS objects
- For theme switcher, update root-level CSS properties via JS (https://medium.com/@_bengarrison/accessing-and-modifying-css-variables-with-javascript-2ccb735bbff0)
*/
:root {
/* Interface UI Colors */
--default-color: #9CCEF9;
--hover-color: #ffffff;
--active-color: #20A5D6;
--ui-border-color: #44626F;
--ui-border-color-dark: #3C5D80;
--ui-border-color-active: #00a4d9;
--primary-background-color: #000000;
--box-background-color: #3E5975;
--text-primary-color: #ffffff;
--text-secondary-color: #91B9CD;
--input-background-color: #2c363f;
--input-placeholder-color: #d3d3d3;
--table-hover-color: #2c363f;
--table-text-primary-color: #ffffff;
--table-text-secondary-color: #91b9cd;
--large-numbers-color: #6FBDE2;
--state-error: #FFCCCC;
--state-error-border: #FFCCCC;
--state-error-text: #FFCCCC;
/* Common palette */
--ui-yellow: #E29E4A;
--ui-sky-blue: #6FBDE2;
/* State palette */
--ui-state-error: #FFCCCC;
--ui-state-error-border: #993333;
--ui-state-error-text: #661111;
--ui-gray-lighter: #436270;
--ui-gray-light: #516873;
--ui-gray: #263340;
--ui-gray-dark: #16202B;
--ui-gray-darker: #151A1F;
--ui-gray-darkest: #14202A;
--calendar-day-color: #d3d3d3;
--calendar-day-border-color: #d3d3d3;
--calendar-day-active-hover-background-color: #516873;
--calendar-main-color: #263340;
--viewport-border-thickness: 1px;
}
body {
font-family: 'Roboto', sans-serif !important;
}
</style>
{{ head }}
</head>
<body>
<div id="root" />
{{ footer }}
</body>
</html>

View File

@ -1,76 +0,0 @@
---
name: Introduction
route: /
---
import { Playground, Props } from 'docz';
import { State } from 'react-powerplug';
import { TableList, TableListItem } from './../index.js';
# Introduction
**@ohif/ui is a collection of components and utilities** that power OHIF's
[zero-footprint DICOM viewer](https://github.com/OHIF/Viewers)
([demo][demo-url]). We maintain them as a separate component library to:
- Decouple presentation from business logic
- Test and develop components in isolation
- Provide well documented, reusable components
- Aid rapid application development for context specific viewers
## Quick Example
> This component library is pre- v1.0. All realeases until a v1.0 have the
> possibility of introducing breaking changes. Please depend on an "exact"
> version in your projects to prevent issues caused by loose versioning.
```js
// Add '@ohif/ui' as a dependency
yarn add @ohif/ui
// Import and use components
import { TableList } from '@ohif/ui'
```
<Playground>
<State
initial={{
selectedIndex: null,
listItems: [
{ label: 'Brain Left' },
{ label: 'Brain Right' },
{ label: 'Heart Left' },
{ label: 'Heart Right' },
],
}}
>
{({ state, setState }) => (
<TableList headerTitle="Example Table List Header">
{state.listItems.map((item, index) => {
return (
<TableListItem
key={`item_${index}`}
itemClass={state.selectedIndex === index ? 'selected' : ''}
itemIndex={index}
onItemClick={() => alert('item clicked')}
>
<label>{item.label}</label>
</TableListItem>
);
})}
</TableList>
)}
</State>
</Playground>
## FAQ
...
<!--
Links
-->
<!-- prettier-ignore-start -->
[demo-url]: https://docs.ohif.org/demo/
<!-- prettier-ignore-end -->

View File

@ -1,56 +0,0 @@
---
name: Styling & Theming
route: /styling-and-theming
---
# Styling & Theming
```css
:root {
/* Interface UI Colors */
--default-color: #9ccef9;
--hover-color: #ffffff;
--active-color: #20a5d6;
--ui-border-color: #44626f;
--ui-border-color-dark: #3c5d80;
--ui-border-color-active: #00a4d9;
--primary-background-color: #000000;
--box-background-color: #3e5975;
--text-primary-color: #ffffff;
--text-secondary-color: #91b9cd;
--input-background-color: #2c363f;
--input-placeholder-color: #d3d3d3;
--table-hover-color: #2c363f;
--table-text-primary-color: #ffffff;
--table-text-secondary-color: #91b9cd;
--large-numbers-color: #6fbde2;
--state-error: #ffcccc;
--state-error-border: #ffcccc;
--state-error-text: #ffcccc;
/* Common palette */
--ui-yellow: #e29e4a;
--ui-sky-blue: #6fbde2;
/* State palette */
--ui-state-error: #ffcccc;
--ui-state-error-border: #993333;
--ui-state-error-text: #661111;
--ui-gray-lighter: #436270;
--ui-gray-light: #516873;
--ui-gray: #263340;
--ui-gray-dark: #16202b;
--ui-gray-darker: #151a1f;
--ui-gray-darkest: #14202a;
--calendar-day-color: #d3d3d3;
--calendar-day-border-color: #d3d3d3;
--calendar-day-active-hover-background-color: #516873;
--calendar-main-color: #263340;
--viewport-border-thickness: 1px;
}
```

View File

@ -1,87 +0,0 @@
---
name: Translating
route: /translating
---
# Translating
**React Viewerbase** supports internationalization using
[i18next](https://www.i18next.com/) through the npm package
[@ohif/i18n](https://www.npmjs.com/package/@ohif/i18n), where is the main
instance of i18n containing several languages and tools.
## How to translate components
All you need is to use the internal custom superset of
[withTranslation HOC](https://react.i18next.com/latest/withtranslation-hoc),
available at `src/utils/LanguageProvider.js`
e.g.:
```js
import React from 'react';
import { withTranslation } from '../utils/LanguageProvider.js';
function MyComponent({ t }) {
return <p>{t('my translated text')}</p>;
}
export default withTranslation('MyNameSpace')(MyComponent);
```
---
## Namespaces
For further information about namespaces and how they work, please visit
[OHIF Namespaces docs](https://docs.ohif.org/viewer/internationalization.html#namespaces).
### How to get right the Namespace of a `@ohif/ui` component?
Check the component's page in this site, the Namespace information should be on
the bottom line.
Also, a nameSpace is defined when exporting a component, this information can be
found on exports trough the code, in case of missing in docs.
E.g.
```
export default withTranslation('TheNamespaceYouAreLookingFor')(TheComponentYouWantToUse);
```
### How to use another Namespace inside the current Namespace?
i18next provides a parsing feature able to get translations strings from any
Namespace, like this following example getting data from `InnerSpace` Namespace:
```
$t(InnerSpace:Reset)
export default withTranslation('OuterSpace')(TheComponent);
```
---
## I18next Provider
**@ohif/ui** pass down the `t` function trough the custom
[withTranslation HOC](https://github.com/OHIF/Viewers/blob/master/platform/ui/src/utils/LanguageProvider.js).
---
## Extending and Overriding languages
[How to Extend languages with @ohif/i18n](https://docs.ohif.org/viewer/internationalization.html#extending-languages-in-ohifi18n)
## Changing the language
The language choosing method is the same implemented in
[OHIF Viewers](https://docs.ohif.org/viewer/internationalization.html#changing-the-language)
> You can also change the language of the components displayed in this
> documentation by choosing an option in the
> [Docs Settings](https://react.ohif.org/docs-settings) section.
## Debugging translations
[How to Debug @ohif/i18n](https://docs.ohif.org/viewer/internationalization.html#debugging-translations)

View File

@ -1,22 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import LanguageSwitcher from '../components/languageSwitcher';
import './wrapper.styl';
const Wrapper = ({ children }) => (
<React.Fragment>
<div className="sidebarLanguageSwitcher">
<strong>Display components in:</strong>
<LanguageSwitcher />
</div>
{children}
</React.Fragment>
);
Wrapper.propTypes = {
children: PropTypes.node.isRequired,
};
export default Wrapper;

View File

@ -1,15 +0,0 @@
[class*="Sidebar__Menus"]
@media (min-width: 1120px)
margin-top: 80px
.sidebarLanguageSwitcher
position: fixed
top: 155px
left 0
z-index: 1049
padding: 0 24px 20px
border-bottom: 1px dotted #CED4DE
background: #F5F6F7
transition: all 200ms ease
@media (max-width: 1119px)
left -100%

View File

@ -0,0 +1,34 @@
import React from 'react';
import t from 'prop-types';
const kinds = {
info: '#5352ED',
positive: '#2ED573',
negative: '#FF4757',
warning: '#FFA502',
};
export const Alert = ({ children, kind, ...rest }) => (
<div
style={{
padding: 20,
borderRadius: 3,
color: 'white',
background: kinds[kind],
}}
{...rest}
>
{children}
</div>
);
Alert.propTypes = {
/**
* The kind prop is used to set the alert's background color
*/
kind: t.oneOf(['info', 'positive', 'negative', 'warning']),
};
Alert.defaultProps = {
kind: 'info',
};

View File

@ -0,0 +1,28 @@
---
name: Alert
menu: Components
---
import { Playground, Props } from 'docz'
import { Alert } from './Alert'
# Alert
## Properties
<Props of={Alert} />
## Basic usage
<Playground>
<Alert>Some message</Alert>
</Playground>
## Using different kinds
<Playground>
<Alert kind="info">Some message</Alert>
<Alert kind="positive">Some message</Alert>
<Alert kind="negative">Some message</Alert>
<Alert kind="warning">Some message</Alert>
</Playground>

View File

@ -0,0 +1,124 @@
import React from 'react';
import PropTypes from 'prop-types';
const Button = ({
children,
variant = defaults.variant,
color = defaults.color,
size = defaults.size,
radius = defaults.radius,
disabled = defaults.disabled,
elevation,
type = defaults.type,
startIcon: startIconProp,
endIcon: endIconProp,
className,
...rest
}) => {
const hasIcon = startIconProp || endIconProp;
const getClasses = () => {
const classes = [];
let buttonElevation;
if (variant === 'text') {
if (elevation) {
buttonElevation = elevation;
} else {
buttonElevation = false;
}
} else {
buttonElevation = defaults.elevation;
}
classes.push(variantClasses[variant][color]);
classes.push(radiusClasses[radius]);
classes.push(sizeClasses[size]);
classes.push(disabledClasses[disabled]);
classes.push(elevationClasses[buttonElevation]);
classes.push(className);
if (hasIcon) {
classes.push(baseIconClasses);
}
return classes.join(' ');
};
const baseIconClasses = 'inline-flex items-center';
const baseClasses =
'outline-none transition duration-300 ease-in-out rounded font-bold focus:outline-none';
const startIcon = startIconProp && (
<div className="mr-2">{startIconProp}</div>
);
const endIcon = endIconProp && <div className="ml-2">{endIconProp}</div>;
return (
<button className={`${baseClasses} ${getClasses()}`} type={type} {...rest}>
{startIcon}
{children}
{endIcon}
</button>
);
};
const defaults = {
variant: 'outlined',
color: 'default',
size: 'medium',
radius: 'medium',
disabled: false,
elevation: true,
type: 'button',
};
const radiusClasses = {
small: 'rounded-sm',
medium: 'rounded-md',
large: 'rounded-lg',
full: 'rounded-full',
};
const disabledClasses = {
true: 'cursor-not-allowed',
false: '',
};
const elevationClasses = {
true: 'shadow',
false: 'shadow-none',
};
const variantClasses = {
text: {
default: 'hover:bg-gray-200 text-black',
primary: 'hover:bg-blue-100 text-blue-900',
secondary: 'hover:bg-blue-100 text-blue-300',
},
outlined: {
default: 'border border-black text-black hover:bg-gray-200',
primary: 'border border-blue-900 text-blue-900 hover:bg-blue-100',
secondary: 'border border-blue-300 text-blue-300 hover:bg-blue-100',
},
contained: {
default: 'bg-black hover:bg-gray-800 text-white',
primary: 'bg-blue-900 hover:bg-blue-800 text-white',
secondary: 'bg-blue-300 hover:bg-blue-500 text-black',
},
};
const sizeClasses = {
small: 'py-1 px-2 text-sm',
medium: 'py-2 px-4 text-base',
large: 'py-3 px-6 text-lg',
};
Button.propTypes = {
size: PropTypes.oneOf(['small', 'medium', 'large']),
radius: PropTypes.oneOf(['small', 'medium', 'large', 'full']),
variant: PropTypes.oneOf(['text', 'outlined', 'contained']),
color: PropTypes.oneOf(['default', 'primary', 'secondary']),
};
export default Button;

View File

@ -0,0 +1,180 @@
---
name: Button
menu: Components
route: components/button
---
import { Playground, Props } from 'docz';
import Button from './';
# Button
Buttons are used to execute actions when users interacts with them.
## Properties
<Props of={Button} />
## Contained Buttons
<Playground>
<>
<Button variant="contained" style={{ margin: 5 }}>
Default
</Button>
<Button variant="contained" color="primary" style={{ margin: 5 }}>
Primary
</Button>
<Button variant="contained" color="secondary" style={{ margin: 5 }}>
Secondary
</Button>
</>
</Playground>
## Outlined Buttons
<Playground>
<>
<Button variant="outlined" style={{ margin: 5 }}>
Default
</Button>
<Button variant="outlined" color="primary" style={{ margin: 5 }}>
Primary
</Button>
<Button variant="outlined" color="secondary" style={{ margin: 5 }}>
Secondary
</Button>
</>
</Playground>
## Text Buttons
<Playground>
<>
<Button variant="text" style={{ margin: 5 }}>
Default
</Button>
<Button variant="text" color="primary" style={{ margin: 5 }}>
Primary
</Button>
<Button variant="text" color="secondary" style={{ margin: 5 }}>
Secondary
</Button>
</>
</Playground>
## Sizes
<Playground>
<>
<div>
<Button variant="text" size="small" style={{ margin: 5 }}>
Small
</Button>
<Button variant="contained" size="medium" style={{ margin: 5 }}>
Medium
</Button>
<Button variant="outlined" size="large" style={{ margin: 5 }}>
Large
</Button>
</div>
<div>
<Button
variant="text"
size="small"
color="primary"
style={{ margin: 5 }}
>
Small
</Button>
<Button
variant="contained"
size="medium"
color="primary"
style={{ margin: 5 }}
>
Medium
</Button>
<Button
variant="outlined"
size="large"
color="primary"
style={{ margin: 5 }}
>
large
</Button>
</div>
<div>
<Button
variant="text"
size="small"
color="secondary"
style={{ margin: 5 }}
>
Small
</Button>
<Button
variant="contained"
size="medium"
color="secondary"
style={{ margin: 5 }}
>
Medium
</Button>
<Button
variant="outlined"
size="large"
color="secondary"
style={{ margin: 5 }}
>
Large
</Button>
</div>
</>
</Playground>
## Buttons with icons
<Playground>
{() => {
return (
<div className="flex">
<Button
variant="contained"
color="primary"
style={{ margin: 5 }}
startIcon={
<svg
class="fill-current w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
>
<path d="M13 8V2H7v6H2l8 8 8-8h-5zM0 18h20v2H0v-2z" />
</svg>
}
>
Download
</Button>
<Button
variant="contained"
color="primary"
style={{ margin: 5 }}
endIcon={
<svg
class="fill-current w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
>
<path d="M13 8V2H7v6H2l8 8 8-8h-5zM0 18h20v2H0v-2z" />
</svg>
}
>
Download
</Button>
</div>
)
}}
</Playground>

View File

@ -0,0 +1,2 @@
import Button from './Button';
export default Button;

View File

@ -0,0 +1,17 @@
import React from 'react';
import Label from '../Label';
const Input = ({ label, ...rest }) => {
return (
<>
<Label text="Label">
<input
className="transition duration-300 shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
{...rest}
/>
</Label>
</>
);
};
export default Input;

View File

@ -0,0 +1,30 @@
---
name: Input
menu: Components
route: components/input
---
import { Playground, Props } from 'docz';
import Input from './';
# Input
## Properties
<Props of={Input} />
## Basic usage
<Playground>
{() => {
const [inputValue, setInputValue] = React.useState('');
return (
<Input
label="Label"
value={inputValue}
placeholder="Type something..."
onChange={e => setInputValue(e.target.value)}
/>
);
}}
</Playground>

View File

@ -0,0 +1,2 @@
import Input from './Input';
export default Input;

View File

@ -0,0 +1,34 @@
import React from 'react';
import t from 'prop-types';
const Label = ({ children, spacing, className, text, ...rest }) => {
const baseClasses = 'block text-gray-700 text-sm font-bold';
const getClasses = () => {
const classes = [];
classes.push(baseClasses);
classes.push(className);
return classes.join(' ');
};
return (
<div>
<label className={getClasses()}>
{text}
{children}
</label>
</div>
);
};
const spacing = {
small: 'mb-1',
medium: 'mb-2',
large: 'mb-3',
};
Label.defaultProps = {};
Label.prototypes = {};
export default Label;

View File

@ -0,0 +1,20 @@
---
name: Label
menu: Components
route: components/label
---
import { Playground, Props } from 'docz';
import Label from './';
# Label
## Properties
<Props of={Label} />
## Basic usage
<Playground>
<Label text="Label" />
</Playground>

View File

@ -0,0 +1,2 @@
import Label from './Label';
export default Label;

View File

@ -1,42 +0,0 @@
---
name: Checkbox
menu: Elements
route: /components/checkbox
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { Checkbox } from './../index'
import NameSpace from '../../../__docs__/NameSpace'
# Checkbox
## Basic usage
<Playground>
<State
initial={{
checked : false,
label : 'Sample check'
}}
>
{({ state, setState }) => (
<React.Fragment>
<div>
<pre>{JSON.stringify(state, null, 2)}</pre>
</div>
<div style={{ maxWidth: '300px', margin: '0 auto' }}>
<Checkbox {...state} />
</div>
</React.Fragment>
)}
</State>
</Playground>
## API
<Props of={Checkbox} />
## Translation Namespace
<NameSpace name="Checkbox" />

View File

@ -1,56 +0,0 @@
.ohif-check-container {
--check-button-dim: 15px;
}
.ohif-check-container input {
position: absolute;
opacity: 0;
height: inherit;
width: inherit;
cursor: default;
}
.ohif-check-container {
display: block;
position: relative;
padding-left: 35px;
margin-bottom: 12px;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.ohif-checkbox {
width: var(--check-button-dim);
height: var(--check-button-dim);
position: absolute;
top: 20%;
left: 5%;
cursor: pointer;
background-color: var(--ui-gray-lighter);
}
.ohif-checkbox:hover {
background-color: var(--default-color);
}
.ohif-checkbox:after {
content: '';
position: absolute;
width: 70%;
height: 70%;
}
.ohif-checkbox.ohif-checked:after {
display: block;
top: 14%;
left: 14%;
background: white;
}
.ohif-check-label {
font-size: 12px;
font-weight: 500;
}

View File

@ -1,58 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './checkbox.css';
export class Checkbox extends Component {
static propTypes = {
label: PropTypes.string.isRequired,
checked: PropTypes.bool,
onChange: PropTypes.func,
};
constructor(props) {
super(props);
this.state = { checked: !!props.checked, label: props.label };
}
handleChange(e) {
const checked = e.target.checked;
this.setState({ checked });
if (this.props.onChange) this.props.onChange(checked);
}
componentDidUpdate(props) {
const { checked = false, label } = props;
if (this.state.checked !== checked || this.state.label !== label) {
this.setState({
checked,
label,
});
}
}
render() {
let checkbox;
if (this.state.checked) {
checkbox = <span className="ohif-checkbox ohif-checked" />;
} else {
checkbox = <span className="ohif-checkbox" />;
}
return (
<div className="ohif-check-container">
<form>
<label className="ohif-check-label">
<input
type="checkbox"
checked={this.state.checked}
onChange={this.handleChange.bind(this)}
/>
{checkbox}
{this.state.label}
</label>
</form>
</div>
);
}
}

View File

@ -1 +0,0 @@
export { Checkbox } from './checkbox.js';

View File

@ -1,197 +0,0 @@
import './CineDialog.styl';
import React, { PureComponent } from 'react';
import { withTranslation } from '../../contextProviders';
import { Icon } from './../../elements/Icon';
import PropTypes from 'prop-types';
class CineDialog extends PureComponent {
constructor(props) {
super(props);
this.state = {
cineFrameRate: props.cineFrameRate,
isPlaying: props.isPlaying,
};
}
static propTypes = {
/** Minimum value for range slider */
cineMinFrameRate: PropTypes.number.isRequired,
/** Maximum value for range slider */
cineMaxFrameRate: PropTypes.number.isRequired,
/** Increment range slider can "step" in either direction. */
cineStepFrameRate: PropTypes.number.isRequired,
cineFrameRate: PropTypes.number.isRequired,
/** 'True' if playing, 'False' if paused. */
isPlaying: PropTypes.bool.isRequired,
onPlayPauseChanged: PropTypes.func,
onFrameRateChanged: PropTypes.func,
onClickNextButton: PropTypes.func,
onClickBackButton: PropTypes.func,
onClickSkipToStart: PropTypes.func,
onClickSkipToEnd: PropTypes.func,
/** i18next translation function */
t: PropTypes.func.isRequired,
};
static defaultProps = {
cineMinFrameRate: 1,
cineMaxFrameRate: 90,
cineStepFrameRate: 1,
cineFrameRate: 24,
isPlaying: false,
};
componentDidUpdate(prevProps) {
// TODO: Not sure if we should just switch this to a stateless
// fully-controlled component instead
if (
this.props.isPlaying !== prevProps.isPlaying ||
this.props.isPlaying !== this.state.isPlaying
) {
this.setState({
isPlaying: this.props.isPlaying,
});
}
if (
this.props.cineFrameRate !== prevProps.cineFrameRate ||
this.props.cineFrameRate !== this.state.cineFrameRate
) {
this.setState({
cineFrameRate: this.props.cineFrameRate,
});
}
}
handleInputChange = event => {
const target = event.target;
let value = target.value;
if (target.type === 'range') {
value = parseFloat(target.value);
}
const name = target.name;
this.setState({
[name]: value,
});
if (name === 'cineFrameRate' && this.props.onFrameRateChanged) {
this.props.onFrameRateChanged(parseFloat(value));
}
};
onClickPlayPause = () => {
const value = !this.state.isPlaying;
this.setState({
isPlaying: value,
});
if (this.props.onPlayPauseChanged) {
this.props.onPlayPauseChanged(value);
}
};
onClickNextButton = event => {
if (this.props.onClickNextButton) {
this.props.onClickNextButton(event);
}
};
onClickBackButton = event => {
if (this.props.onClickBackButton) {
this.props.onClickBackButton(event);
}
};
onClickSkipToStart = event => {
if (this.props.onClickSkipToStart) {
this.props.onClickSkipToStart(event);
}
};
onClickSkipToEnd = event => {
if (this.props.onClickSkipToEnd) {
this.props.onClickSkipToEnd(event);
}
};
render() {
const { t } = this.props;
return (
<div className="CineDialog">
<div className="noselect double-row-style">
<div className="cine-controls">
<div className="btn-group">
<button
title={t('Skip to first image')}
className="btn"
data-toggle="tooltip"
onClick={this.onClickSkipToStart}
>
<Icon name="fast-backward" />
</button>
<button
title={t('Previous image')}
className="btn"
data-toggle="tooltip"
onClick={this.onClickBackButton}
>
<Icon name="step-backward" />
</button>
<button
title={t('Play / Stop')}
className="btn"
data-toggle="tooltip"
onClick={this.onClickPlayPause}
>
<Icon name={this.state.isPlaying ? 'stop' : 'play'} />
</button>
<button
title={t('Next image')}
className="btn"
data-toggle="tooltip"
onClick={this.onClickNextButton}
>
<Icon name="step-forward" />
</button>
<button
title={t('Skip to last image')}
className="btn"
data-toggle="tooltip"
onClick={this.onClickSkipToEnd}
>
<Icon name="fast-forward" />
</button>
</div>
</div>
<div className="cine-options">
<div className="fps-section">
<input
type="range"
name="cineFrameRate"
min={this.props.cineMinFrameRate}
max={this.props.cineMaxFrameRate}
step={this.props.cineStepFrameRate}
value={this.state.cineFrameRate}
onChange={this.handleInputChange}
/>
</div>
<span className="fps">
{this.state.cineFrameRate.toFixed(1)} {t('fps')}
</span>
</div>
</div>
</div>
);
}
}
const connectedComponent = withTranslation('CineDialog')(CineDialog);
export { connectedComponent as CineDialog };
export default connectedComponent;

View File

@ -1,172 +0,0 @@
@import './../../design/styles/common/global.styl'
@import './../../design/styles/common/form.styl'
.CineDialog
background: var(--ui-gray-darkest);
color: var(--text-secondary-color);
filter: drop-shadow(0 0 3px var(--ui-gray-darkest))
border: none
border-radius: 8px
width: inherit;
h5
font-size: 20px
line-height: 35px
margin: 0
h5, label
font-weight: 400
.cine-navigation, .cine-controls, .cine-options
cursor: default
.fps-section
input[type="range"]
background-color: transparent
border: 0 none
outline: 0 none
&::-ms-tooltip
display: none
.double-row-style
box-sizing: border-box
width: 290px
height: 70px
padding: 10px
top: 2%
left: 35%
.cine-navigation
position: absolute
right: 16px
top: 10px
.btn
padding: 0 4px
i
font-size: 32px
line-height: 32px
.cine-controls
left: 0px
.cine-options
padding: 0px 0
.fps-section
width: 175px
float: left
.fps
float: right
.single-row-style
box-sizing: content-box
width: 425px
height: 45px
padding: 4px 8px
top: 2%
left: 35%
.cine-navigation
float: right
overflow: hidden
padding-right: 12px
position: relative
.btn
padding: 0 2px
margin: 0 0 0 2px
border: 0 none
&:first-of-type
margin-left: 0
i
font-size: 32px
line-height: 45px
.cine-controls
position: relative
float: left
overflow: hidden
.btn
font-size: 22px
line-height: 45px
min-width: 28px
padding: 0 0px
margin: 0 4px
border: 0 none
.cine-options
display: block
position: relative
width: 150px
float: left
overflow: hidden
padding: 0
margin: 0 0 0 10px
.fps-section
display: block
float: left
width: 80px
input[type="range"]
line-height: 45px
height: 45px
.fps
display: block
width: 68px
float: left
padding: 0
margin: 0 0 0 2px
text-align: center
line-height: 45px
font-size: 14px;
line-height: 1.42857143;
.btn
color: var(--text-secondary-color);
background-color: transparent
border: 0
border-radius: 4px
font-size: 15px
width: 45px
font-weight: normal
height: 37px
line-height: 37px
padding: 0 12px
&:hover, &:active, &.active
color: var(--hover-color);
&[disabled]
&:hover
color: inherit
&:active
color: var(--text-secondary-color);
&.btn-primary, &.btn-secondary, &.btn-danger
transition: background-color 0.3s ease
&:hover, &:active, &:focus
opacity: 0.8
&.btn-primary
background-color: var(--active-color);
color: var(--primary-background-color)
&.btn-secondary
background-color: var(--text-secondary-color);
color: var(--primary-background-color)
&.btn-danger
background-color: var(--text-destructive-color);
color: var(--text-primary-color);
&.active
box-shadow: inset 1px 1px 2px var(--ui-gray-darkest)

View File

@ -1,57 +0,0 @@
---
name: CINE Dialog
menu: Components
route: /components/cine-dialog
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { CineDialog } from './../index'
import NameSpace from '../../../__docs__/NameSpace'
# CINE Dialog
## Basic usage
<Playground>
<State
initial={{
isPlaying: false,
cineFrameRate: 24,
isLoopEnabled: true,
lastChange: '',
}}
>
{({ state, setState }) => (
<React.Fragment>
<div>
<pre>{JSON.stringify(state, null, 2)}</pre>
</div>
<div style={{ maxWidth: '300px', margin: '0 auto' }}>
<CineDialog
{...state}
onClickSkipToStart={() =>
setState({ lastChange: 'Clicked SkipToStart' })
}
onClickSkipToEnd={() =>
setState({ lastChange: 'Clicked SkipToEnd' })
}
onClickNextButton={() => setState({ lastChange: 'Clicked Next' })}
onClickBackButton={() => setState({ lastChange: 'Clicked Back' })}
onLoopChanged={value => setState({ isLoopEnabled: value })}
onFrameRateChanged={value => setState({ cineFrameRate: value })}
onPlayPauseChanged={() => setState({ isPlaying: !state.isPlaying })}
/>
</div>
</React.Fragment>
)}
</State>
</Playground>
## API
<Props of={CineDialog} />
## Translation Namespace
<NameSpace name="CineDialog" />

View File

@ -1,2 +0,0 @@
import CineDialog from './CineDialog';
export { CineDialog };

View File

@ -1,107 +0,0 @@
import React from 'react';
import detect from 'browser-detect';
import { useTranslation } from 'react-i18next';
import './AboutContent.styl';
const AboutContent = () => {
const { t } = useTranslation('AboutContent');
const { os, version, name } = detect();
const capitalize = s =>
s.substr(0, 1).toUpperCase() + s.substr(1).toLowerCase();
const itemsPreset = () => {
return [
{
name: t('Repository URL'),
value: 'https://github.com/OHIF/Viewers/',
link: 'https://github.com/OHIF/Viewers/',
},
{
name: t('Latest Master Commits'),
value: 'https://github.com/OHIF/Viewers/commits/master',
link: 'https://github.com/OHIF/Viewers/commits/master',
},
{
name: 'Version Number',
value: process.env.VERSION_NUMBER,
},
{
name: t('Build Number'),
value: process.env.BUILD_NUM,
},
{
name: t('Browser'),
value: `${capitalize(name)} ${version}`,
},
{
name: t('OS'),
value: os,
},
];
};
const renderTableRow = ({ name, value, link }) => (
<tr key={name} style={{ backgroundColor: 'transparent' }}>
<td>{name}</td>
<td>
{link ? (
<a target="_blank" rel="noopener noreferrer" href={link}>
{value}
</a>
) : (
value
)}
</td>
</tr>
);
return (
<div className="AboutContent" data-cy="about-modal">
<div className="btn-group">
<a
className="btn btn-default"
target="_blank"
rel="noopener noreferrer"
href="https://groups.google.com/forum/#!forum/cornerstone-platform"
>
{t('Visit the forum')}
</a>
{` `}
<a
className="btn btn-default"
target="_blank"
rel="noopener noreferrer"
href="https://github.com/OHIF/Viewers/issues/new/choose"
>
{t('Report an issue')}
</a>
{` `}
<a
className="btn btn-default"
target="_blank"
rel="noopener noreferrer"
href="http://ohif.org"
>
{t('More details')}
</a>
</div>
<div>
<h3>{t('Version Information')}</h3>
<table className="table table-responsive">
<thead>
<tr>
<th>{t('Name')}</th>
<th>{t('Value')}</th>
</tr>
</thead>
<tbody>{itemsPreset().map(item => renderTableRow(item))}</tbody>
</table>
</div>
</div>
);
};
export { AboutContent };
export default AboutContent;

View File

@ -1,20 +0,0 @@
@import '../../../design/styles/common/button.styl'
@import '../../../design/styles/common/table.styl'
.AboutContent
.btn
border-color: #ccc;
.table
thead:first-child
tr:first-child
th, td
border-top: 0;
thead, tbody, tfoot
tr
th, td
padding: 8px;
line-height: 1.42857143;
vertical-align: top;
border-top: 1px solid #ddd;

View File

@ -1,431 +0,0 @@
import React, {
useRef,
useCallback,
useEffect,
useState,
createRef,
} from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import './ViewportDownloadForm.styl';
import { TextInput, Select, Icon } from '@ohif/ui';
import classnames from 'classnames';
const FILE_TYPE_OPTIONS = [
{
key: 'jpg',
value: 'jpg',
},
{
key: 'png',
value: 'png',
},
];
const DEFAULT_FILENAME = 'image';
const REFRESH_VIEWPORT_TIMEOUT = 1000;
const ViewportDownloadForm = ({
activeViewport,
onClose,
updateViewportPreview,
enableViewport,
disableViewport,
toggleAnnotations,
loadImage,
downloadBlob,
defaultSize,
minimumSize,
maximumSize,
canvasClass,
}) => {
const [t] = useTranslation('ViewportDownloadForm');
const [filename, setFilename] = useState(DEFAULT_FILENAME);
const [fileType, setFileType] = useState('jpg');
const [dimensions, setDimensions] = useState({
width: defaultSize,
height: defaultSize,
});
const [showAnnotations, setShowAnnotations] = useState(true);
const [keepAspect, setKeepAspect] = useState(true);
const [aspectMultiplier, setAspectMultiplier] = useState({
width: 1,
height: 1,
});
const [viewportElement, setViewportElement] = useState();
const [viewportElementDimensions, setViewportElementDimensions] = useState({
width: defaultSize,
height: defaultSize,
});
const [downloadCanvas, setDownloadCanvas] = useState({
ref: createRef(),
width: defaultSize,
height: defaultSize,
});
const [viewportPreview, setViewportPreview] = useState({
src: null,
width: defaultSize,
height: defaultSize,
});
const [error, setError] = useState({
width: false,
height: false,
filename: false,
});
const hasError = Object.values(error).includes(true);
const refreshViewport = useRef(null);
const downloadImage = () => {
downloadBlob(
filename || DEFAULT_FILENAME,
fileType,
viewportElement,
downloadCanvas.ref.current
);
};
/**
* @param {object} event - Input change event
* @param {string} dimension - "height" | "width"
*/
const onDimensionsChange = (event, dimension) => {
const oppositeDimension = dimension === 'height' ? 'width' : 'height';
const sanitizedTargetValue = event.target.value.replace(/\D/, '');
const isEmpty = sanitizedTargetValue === '';
const newDimensions = { ...dimensions };
const updatedDimension = isEmpty
? ''
: Math.min(sanitizedTargetValue, maximumSize);
if (updatedDimension === dimensions[dimension]) {
return;
}
newDimensions[dimension] = updatedDimension;
if (keepAspect && newDimensions[oppositeDimension] !== '') {
newDimensions[oppositeDimension] = Math.round(
newDimensions[dimension] * aspectMultiplier[oppositeDimension]
);
}
// In current code, keepAspect is always `true`
// And we always start w/ a square width/height
setDimensions(newDimensions);
// Only update if value is non-empty
if (!isEmpty) {
setViewportElementDimensions(newDimensions);
setDownloadCanvas(state => ({
...state,
...newDimensions,
}));
}
};
const error_messages = {
width: t('minWidthError'),
height: t('minHeightError'),
filename: t('emptyFilenameError'),
};
const renderErrorHandler = errorType => {
if (!error[errorType]) {
return null;
}
return <div className="input-error">{error_messages[errorType]}</div>;
};
const onKeepAspectToggle = () => {
const { width, height } = dimensions;
const aspectMultiplier = { ...aspectMultiplier };
if (!keepAspect) {
const base = Math.min(width, height);
aspectMultiplier.width = width / base;
aspectMultiplier.height = height / base;
setAspectMultiplier(aspectMultiplier);
}
setKeepAspect(!keepAspect);
};
const validSize = value => (value >= minimumSize ? value : minimumSize);
const loadAndUpdateViewports = useCallback(async () => {
const { width: scaledWidth, height: scaledHeight } = await loadImage(
activeViewport,
viewportElement,
dimensions.width,
dimensions.height
);
toggleAnnotations(showAnnotations, viewportElement);
const scaledDimensions = {
height: validSize(scaledHeight),
width: validSize(scaledWidth),
};
setViewportElementDimensions(scaledDimensions);
setDownloadCanvas(state => ({
...state,
...scaledDimensions,
}));
const {
dataUrl,
width: viewportElementWidth,
height: viewportElementHeight,
} = await updateViewportPreview(
viewportElement,
downloadCanvas.ref.current,
fileType
);
setViewportPreview(state => ({
...state,
src: dataUrl,
width: validSize(viewportElementWidth),
height: validSize(viewportElementHeight),
}));
}, [
activeViewport,
viewportElement,
showAnnotations,
loadImage,
toggleAnnotations,
updateViewportPreview,
fileType,
downloadCanvas.ref,
minimumSize,
maximumSize,
viewportElementDimensions,
]);
useEffect(() => {
enableViewport(viewportElement);
return () => {
disableViewport(viewportElement);
};
}, [disableViewport, enableViewport, viewportElement]);
useEffect(() => {
if (refreshViewport.current !== null) {
clearTimeout(refreshViewport.current);
}
refreshViewport.current = setTimeout(() => {
refreshViewport.current = null;
loadAndUpdateViewports();
}, REFRESH_VIEWPORT_TIMEOUT);
}, [
activeViewport,
viewportElement,
showAnnotations,
dimensions,
loadImage,
toggleAnnotations,
updateViewportPreview,
fileType,
downloadCanvas.ref,
minimumSize,
maximumSize,
]);
useEffect(() => {
const { width, height } = dimensions;
const hasError = {
width: width < minimumSize,
height: height < minimumSize,
filename: !filename,
};
setError({ ...hasError });
}, [dimensions, filename, minimumSize]);
return (
<div className="ViewportDownloadForm">
<div className="title">{t('formTitle')}</div>
<div className="file-info-container" data-cy="file-info-container">
<div className="dimension-wrapper">
<div className="dimensions">
<div className="width">
<TextInput
type="number"
min={minimumSize}
max={maximumSize}
value={dimensions.width}
label={t('imageWidth')}
onChange={evt => onDimensionsChange(evt, 'width')}
data-cy="image-width"
/>
{renderErrorHandler('width')}
</div>
<div className="height">
<TextInput
type="number"
min={minimumSize}
max={maximumSize}
value={dimensions.height}
label={t('imageHeight')}
onChange={evt => onDimensionsChange(evt, 'height')}
data-cy="image-height"
/>
{renderErrorHandler('height')}
</div>
</div>
<div className="keep-aspect-wrapper">
<button
id="keep-aspect"
className={classnames(
'form-button btn',
keepAspect ? 'active' : ''
)}
data-cy="keep-aspect"
alt={t('keepAspectRatio')}
onClick={onKeepAspectToggle}
>
<Icon
name={keepAspect ? 'link' : 'unlink'}
alt={keepAspect ? 'Dismiss Aspect' : 'Keep Aspect'}
/>
</button>
</div>
</div>
<div className="col">
<div className="file-name">
<TextInput
type="text"
data-cy="file-name"
value={filename}
onChange={event => setFilename(event.target.value)}
label={t('filename')}
id="file-name"
/>
{renderErrorHandler('filename')}
</div>
<div className="file-type">
<Select
value={fileType}
data-cy="file-type"
onChange={event => setFileType(event.target.value)}
options={FILE_TYPE_OPTIONS}
label={t('fileType')}
/>
</div>
</div>
<div className="col">
<div className="show-annotations">
<label htmlFor="show-annotations" className="form-check-label">
<input
id="show-annotations"
data-cy="show-annotations"
type="checkbox"
className="form-check-input"
checked={showAnnotations}
onChange={event => setShowAnnotations(event.target.checked)}
/>
{t('showAnnotations')}
</label>
</div>
</div>
</div>
<div
style={{
height: viewportElementDimensions.height,
width: viewportElementDimensions.width,
position: 'absolute',
left: '9999px',
}}
ref={ref => setViewportElement(ref)}
>
<canvas
className={canvasClass}
style={{
height: downloadCanvas.height,
width: downloadCanvas.width,
display: 'block',
}}
width={downloadCanvas.width}
height={downloadCanvas.height}
ref={downloadCanvas.ref}
></canvas>
</div>
{viewportPreview.src ? (
<div className="preview" data-cy="image-preview">
<div className="preview-header"> {t('imagePreview')}</div>
<img
className="viewport-preview"
src={viewportPreview.src}
alt={t('imagePreview')}
data-cy="image-preview"
data-cy="viewport-preview-img"
/>
</div>
) : (
<div className="loading-image">
<Icon name="circle-notch" className="icon-spin" />
{t('loadingPreview')}
</div>
)}
<div className="actions">
<div className="action-cancel">
<button
type="button"
data-cy="cancel-btn"
className="btn btn-danger"
onClick={onClose}
>
{t('Buttons:Cancel')}
</button>
</div>
<div className="action-save">
<button
disabled={hasError}
onClick={downloadImage}
className="btn btn-primary"
data-cy="download-btn"
>
{t('Buttons:Download')}
</button>
</div>
</div>
</div>
);
};
ViewportDownloadForm.propTypes = {
onClose: PropTypes.func.isRequired,
activeViewport: PropTypes.object,
updateViewportPreview: PropTypes.func.isRequired,
enableViewport: PropTypes.func.isRequired,
disableViewport: PropTypes.func.isRequired,
toggleAnnotations: PropTypes.func.isRequired,
loadImage: PropTypes.func.isRequired,
downloadBlob: PropTypes.func.isRequired,
/** A default width & height, between the minimum and maximum size */
defaultSize: PropTypes.number.isRequired,
minimumSize: PropTypes.number.isRequired,
maximumSize: PropTypes.number.isRequired,
canvasClass: PropTypes.string.isRequired,
};
export default ViewportDownloadForm;

View File

@ -1,140 +0,0 @@
@import '../../../design/styles/common/global.styl'
@import '../../../design/styles/common/form.styl'
@import '../../../design/styles/common/button.styl'
.ViewportDownloadForm
display: flex;
flex-direction: column;
z-index: 1080 !important;
input, select
max-height: 30px;
#keep-aspect svg
margin-top: 3px;
.title
margin: 0;
font-weight: bold;
.file-info-container
display: flex;
flex-direction: row;
justify-content: space-around;
margin: 20px 0;
border-radius: 5px;
padding: 20px 10px;
background-color: var(--ui-gray-dark);
@media screen and (max-width: 1023px)
flex-direction: column;
align-items: flex-start;
.width,
.height,
.file-name,
.file-type
height: 56px;
.input-ohif
margin: 0 5px;
.file-name,
.file-type
.select-ohif, .input-ohif
width: 170px;
.input-ohif-label, .select-ohif-label
width: 90px;
display: inline-block;
@media screen and (max-width: 1023px)
width: 120px;
.dimension-wrapper
display: flex;
flex-direction: row;
.dimensions
display: flex;
flex-direction: column;
.input-ohif-label
width: 120px;
display: inline-block;
.input-ohif
@media screen and (max-width: 1023px)
width: 170px;
.keep-aspect-wrapper
display: flex;
justify-content: center;
align-items: center;
padding: 0 10px;
height: 86px;
.show-annotations
font-weight: bold;
line-height: 30px;
input
margin-right: 7px;
vertical-align: middle;
label
display: flex;
justify-content: center;
align-items: center;
.loading-image
height: 580px;
display: flex;
justify-content: center;
align-items: center;
color: var(--active-color);
font-size: 20px;
.icon-spin
margin-right: 15px;
.preview
display: flex;
flex-direction: column;
background-color: var(--ui-gray-dark);
padding: 10px;
border-radius: 5px;
justify-content: flex-start;
align-items: center;
height: 580px;
.viewport-preview
max-height: 512px;
max-width: 512px;
.preview-header
width: 100%;
text-align center;
font-size: 1.3em;
margin: 0 0 10px;
.actions
display: flex;
flex-wrap: nowrap;
justify-content: flex-end;
align-items: center;
margin-top: 20px;
.btn
margin: 0 10px;
.input-error
font-size: 12px;
color: red;
text-align: center;
margin: 3px 0;
.modal-dialog
height: 100%;
.modal-body
display: flex;
flex-direction: column;

View File

@ -1,39 +0,0 @@
---
name: Viewport Download Form
menu: Components
route: /components/viewport-download-form
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { ViewportDownloadForm } from './../index'
import NameSpace from '../../../__docs__/NameSpace'
# Viewport Download Form
## Basic usage
<Playground>
<State
initial={{}}
>
{({ state, setState }) => (
<React.Fragment>
<div>
<pre>{JSON.stringify(state, null, 2)}</pre>
</div>
<div style={{ maxWidth: '400px', margin: '0 auto' }}>
<ViewportDownloadForm />
</div>
</React.Fragment>
)}
</State>
</Playground>
## API
<Props of={ViewportDownloadForm} />
## Translation Namespace
<NameSpace name="ViewportDownloadForm" />

View File

@ -1,2 +0,0 @@
import ViewportDownloadForm from './ViewportDownloadForm';
export { ViewportDownloadForm };

View File

@ -1,33 +0,0 @@
.ContextMenu {
position: relative;
background-color: white;
border-radius: 5px;
z-index: 1000;
display: block;
width: 170px;
}
.ContextMenu>ul {
list-style-type: none;
padding-left: 0;
margin: 0;
}
.ContextMenu>ul>li>button {
padding: 10px;
font-size: 14px;
border: none;
color: #516873;
border-radius: 3px;
outline: none;
cursor: pointer;
background: none;
}
.ContextMenu>ul>li>button:hover {
color: #16202b;
}
.ContextMenu>ul>li>button:active {
color: #79f9fe;
}

View File

@ -1 +0,0 @@
export { default as ContextMenu } from './ContextMenu.js';

View File

@ -1,4 +0,0 @@
.language-select
color: var(--primary-background-color)
display: block
min-width: 150px

View File

@ -1,64 +0,0 @@
import React, { PureComponent } from 'react';
import { LayoutChooser } from './LayoutChooser.js';
import PropTypes from 'prop-types';
import ToolbarButton from '../../viewer/ToolbarButton';
export class LayoutButton extends PureComponent {
static defaultProps = {
dropdownVisible: false,
};
static propTypes = {
dropdownVisible: PropTypes.bool.isRequired,
/** Called with the selectedCell number when grid sell is selected */
onChange: PropTypes.func,
/** The cell to show as selected */
selectedCell: PropTypes.object,
};
state = {
dropdownVisible: this.props.dropdownVisible,
};
componentDidUpdate(prevProps) {
if (this.props.dropdownVisible !== prevProps.dropdownVisible) {
this.setState({
dropdownVisible: this.props.dropdownVisible,
});
}
}
onClick = () => {
this.setState({
dropdownVisible: !this.state.dropdownVisible,
});
};
onChange = selectedCell => {
if (this.props.onChange) {
this.props.onChange(selectedCell);
}
};
render() {
return (
<div className="btn-group">
<ToolbarButton
isActive={this.state.dropdownVisible}
label={'Layout'}
icon="th"
onClick={this.onClick}
/>
<LayoutChooser
visible={this.state.dropdownVisible}
onChange={this.onChange}
onClick={this.onClick}
selectedCell={this.props.selectedCell}
/>
</div>
);
}
}
export default LayoutButton;

View File

@ -1,47 +0,0 @@
@import './../../design/styles/common/button.styl'
@import './../../design/styles/common/global.styl'
$borderColor = rgba(77, 99, 110, 0.81)
.layoutChooser-dropdown-menu
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
display: none;
float: left;
min-width: 160px;
padding: 5px 0;
margin: 2px 0 0;
font-size: 14px;
text-align: left;
list-style: none;
background-color: #fff;
-webkit-background-clip: padding-box;
background-clip: padding-box;
border: 1px solid #ccc;
border: 1px solid rgba(0,0,0,.15);
border-radius: 4px;
-webkit-box-shadow: 0 6px 12px rgba(0,0,0,.175);
box-shadow: 0 6px 12px rgba(0,0,0,.175);
.layoutChooser
.selectedBefore
background-color: #5cc3eb
border: 1px solid $borderColor;
border-radius: 8px
padding: 5px 0;
position: absolute;
z-index: 5000
table
margin: 0 auto
border-spacing: 0;
border-collapse: collapse;
td
cursor: pointer
transition(background-color 0.1s ease)
&:hover, &.hover // Add the hover class here to be triggered by mouseenter/mouseleave
background-color: #209ac9

View File

@ -1,10 +0,0 @@
.LayoutExample {
background-color: var(--primary-background-color);
width: 160px;
height: 160px;
}
.GridLayout td {
padding: 5px;
border: 1px solid black;
}

View File

@ -1,49 +0,0 @@
---
name: Layout Button
menu: Components
route: /components/layout-button
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { LayoutButton } from './../index.js'
import './layoutButton.css'
import NameSpace from '../../../__docs__/NameSpace'
# Layout Button
## Basic usage
<Playground>
<State initial={{
selectedCell: {
className: 'hover',
col: 1,
row: 1
},
}}>
{({ state, setState }) => (
<React.Fragment>
{/* STATE */}
<div><pre>{JSON.stringify(state, null, 2) }</pre></div>
{/* COMPONENT */}
<div className="LayoutExample">
<LayoutButton
{...state}
onChange={cell => setState({ selectedCell: cell })}
/>
</div>
</React.Fragment>
)}
</State>
</Playground>
## API
<Props of={LayoutButton} />
## Translation Namespace
<NameSpace name="Buttons" />

View File

@ -1,2 +0,0 @@
export { LayoutButton } from './LayoutButton.js';
export { LayoutChooser } from './LayoutChooser.js';

View File

@ -1,218 +0,0 @@
import './MeasurementTable.styl';
import React, { Component } from 'react';
import { withTranslation } from '../../contextProviders';
import { Icon } from './../../elements/Icon';
import { MeasurementTableItem } from './MeasurementTableItem.js';
import { OverlayTrigger } from './../overlayTrigger';
import PropTypes from 'prop-types';
import { ScrollableArea } from './../../ScrollableArea/ScrollableArea.js';
import { TableList } from './../tableList';
import { Tooltip } from './../tooltip';
class MeasurementTable extends Component {
static propTypes = {
measurementCollection: PropTypes.array.isRequired,
timepoints: PropTypes.array.isRequired,
overallWarnings: PropTypes.object.isRequired,
readOnly: PropTypes.bool,
onItemClick: PropTypes.func,
onRelabelClick: PropTypes.func,
onDeleteClick: PropTypes.func,
onEditDescriptionClick: PropTypes.func,
selectedMeasurementNumber: PropTypes.number,
t: PropTypes.func,
saveFunction: PropTypes.func,
onSaveComplete: PropTypes.func,
};
static defaultProps = {
overallWarnings: {
warningList: [],
},
readOnly: false,
};
state = {
selectedKey: null,
};
render() {
const { overallWarnings, saveFunction, t } = this.props;
const hasOverallWarnings = overallWarnings.warningList.length > 0;
return (
<div className="measurementTable">
<div className="measurementTableHeader">
{hasOverallWarnings && (
<OverlayTrigger
key={'overwall-warning'}
placement="left"
overlay={
<Tooltip
placement="left"
className="in tooltip-warning"
id="tooltip-left"
style={{}}
>
<div className="warningTitle">
{t('Criteria nonconformities')}
</div>
<div className="warningContent">
{this.getWarningContent()}
</div>
</Tooltip>
}
>
<span className="warning-status">
<span className="warning-border">
<Icon name="exclamation-triangle" />
</span>
</span>
</OverlayTrigger>
)}
{this.getTimepointsHeader()}
</div>
<ScrollableArea>
<div>{this.getMeasurementsGroups()}</div>
</ScrollableArea>
<div className="measurementTableFooter">
{saveFunction && (
<button
onClick={this.saveFunction}
className="saveBtn"
data-cy="save-measurements-btn"
>
<Icon name="save" width="14px" height="14px" />
Save measurements
</button>
)}
</div>
</div>
);
}
saveFunction = async event => {
const { saveFunction, onSaveComplete } = this.props;
if (saveFunction) {
try {
const result = await saveFunction();
if (onSaveComplete) {
onSaveComplete({
title: 'STOW SR',
message: result.message,
type: 'success',
});
}
} catch (error) {
if (onSaveComplete) {
onSaveComplete({
title: 'STOW SR',
message: error.message,
type: 'error',
});
}
}
}
};
getMeasurementsGroups = () => {
return this.props.measurementCollection.map((measureGroup, index) => {
return (
<TableList
key={index}
customHeader={this.getCustomHeader(measureGroup)}
>
{this.getMeasurements(measureGroup)}
</TableList>
);
});
};
getMeasurements = measureGroup => {
const selectedKey = this.props.selectedMeasurementNumber
? this.props.selectedMeasurementNumber
: this.state.selectedKey;
return measureGroup.measurements.map((measurement, index) => {
const key = measurement.measurementNumber;
const itemIndex = measurement.itemNumber || index + 1;
const itemClass =
selectedKey === key && !this.props.readOnly ? 'selected' : '';
return (
<MeasurementTableItem
key={key}
itemIndex={itemIndex}
itemClass={itemClass}
measurementData={measurement}
onItemClick={this.onItemClick}
onRelabel={this.props.onRelabelClick}
onDelete={this.props.onDeleteClick}
onEditDescription={this.props.onEditDescriptionClick}
/>
);
});
};
onItemClick = (event, measurementData) => {
if (this.props.readOnly) return;
this.setState({
selectedKey: measurementData.measurementNumber,
});
if (this.props.onItemClick) {
this.props.onItemClick(event, measurementData);
}
};
getCustomHeader = measureGroup => {
return (
<React.Fragment>
<div className="tableListHeaderTitle">
{this.props.t(measureGroup.groupName)}
</div>
{measureGroup.maxMeasurements && (
<div className="maxMeasurements">
{this.props.t('MAX')} {measureGroup.maxMeasurements}
</div>
)}
<div className="numberOfItems">{measureGroup.measurements.length}</div>
</React.Fragment>
);
};
getTimepointsHeader = () => {
const { timepoints, t } = this.props;
return timepoints.map((timepoint, index) => {
return (
<div key={index} className="measurementTableHeaderItem">
<div className="timepointLabel">{t(timepoint.key)}</div>
<div className="timepointDate">{timepoint.date}</div>
</div>
);
});
};
getWarningContent = () => {
const { warningList = '' } = this.props.overallWarnings;
if (Array.isArray(warningList)) {
const listedWarnings = warningList.map((warn, index) => {
return <li key={index}>{warn}</li>;
});
return <ol>{listedWarnings}</ol>;
} else {
return <React.Fragment>{warningList}</React.Fragment>;
}
};
}
const connectedComponent = withTranslation(['MeasurementTable', 'Common'])(
MeasurementTable
);
export { connectedComponent as MeasurementTable };
export default connectedComponent;

View File

@ -1,116 +0,0 @@
.measurementTable
background-color: var(--primary-background-color)
height: 100%
width: 100%
display: flex
flex-direction: column
.maxMeasurements
border-radius: 3px
color: var(--primary-background-color)
font-size: 12px
font-weight: 500
height: 19px
line-height: 17px
margin-top: 22px
margin-right: 15px
padding: 2px 6px 0
text-transform: uppercase
transition: all 0.3s ease
background-color: var(--default-color)
.measurementTableHeader
padding-left: 45px
.measurementTableHeaderItem
display: inline-block
margin-top: 9px
margin-left: 9px
padding-left: 9px
width: 90px
font-size: 14px
line-height: 20px
border-left: 1px solid var(--text-secondary-color)
color: var(--text-primary-color)
.timepointLabel
color: var(--text-secondary-color)
font-size: 12px
.warning-status
display: block
position: absolute
width: 50px
height: 40px
padding-left: 7px
left: 15px
top: 7px
.warning-border
display: block
border: 2px solid #e29e4a
border-radius: 36px
padding: 6px 7px
width: 40px
height: 40px
svg
color: #e29e4a
width: 22px
height: 20px
pointer-events: inherit
.measurementTableFooter
padding: 10px
color: #fff
text-align: center
z-index: 1
.saveBtn
border: 1px solid var(--ui-border-color-active)
border-radius: 25px
background-color: var(--active-color)
color: var(--ui-gray-dark)
line-height: 25px
padding: 0 20px
outline: none
cursor: pointer
&:hover, &:active
background-color: var(--ui-sky-blue)
svg
margin-right: 4px
position: relative
top: 2px
.tooltip-warning
&.in
opacity 1
.tooltip-inner
padding: 0
.warningTitle
text-align: left
background-color: var(--ui-gray-darkest)
padding: 7px 15px
border-top-left-radius: 7px
border-top-right-radius: 7px
.warningContent
text-align center
background-color: var(--ui-gray)
border-bottom-left-radius: 7px
border-bottom-right-radius: 7px
ol
margin-top: auto
padding-inline-start: 20px
padding-top: 10px
padding-bottom: 10px
padding-left: 25px
text-align: left
li
padding-right: 10px

View File

@ -1,165 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withTranslation } from '../../contextProviders';
import { Icon } from './../../elements/Icon';
import { OverlayTrigger } from './../overlayTrigger';
import { Tooltip } from './../tooltip';
import { TableListItem } from './../tableList/TableListItem.js';
import './MeasurementTableItem.styl';
class MeasurementTableItem extends Component {
static propTypes = {
measurementData: PropTypes.object.isRequired,
onItemClick: PropTypes.func.isRequired,
onRelabel: PropTypes.func,
onDelete: PropTypes.func,
onEditDescription: PropTypes.func,
itemClass: PropTypes.string,
itemIndex: PropTypes.number,
t: PropTypes.func,
};
render() {
const { warningTitle = '', hasWarnings } = this.props.measurementData;
return (
<React.Fragment>
{hasWarnings ? (
<OverlayTrigger
key={this.props.itemIndex}
placement="left"
overlay={
<Tooltip
placement="left"
className="in tooltip-warning"
id="tooltip-left"
>
<div className="warningTitle">{this.props.t(warningTitle)}</div>
<div className="warningContent">{this.getWarningContent()}</div>
</Tooltip>
}
>
<div>{this.getTableListItem()}</div>
</OverlayTrigger>
) : (
<React.Fragment>{this.getTableListItem()}</React.Fragment>
)}
</React.Fragment>
);
}
getActionButton = (btnLabel, onClickCallback) => {
return (
<button key={btnLabel} className="btnAction" onClick={onClickCallback}>
<span style={{ marginRight: '4px' }}>
<Icon name="edit" width="14px" height="14px" />
</span>
{this.props.t(btnLabel)}
</button>
);
};
getTableListItem = () => {
const hasWarningClass = this.props.measurementData.hasWarnings
? 'hasWarnings'
: '';
const actionButtons = [];
if (typeof this.props.onRelabel === 'function') {
const relabelButton = this.getActionButton(
'Relabel',
this.onRelabelClick
);
actionButtons.push(relabelButton);
}
if (typeof this.props.onEditDescription === 'function') {
const descriptionButton = this.getActionButton(
'Description',
this.onEditDescriptionClick
);
actionButtons.push(descriptionButton);
}
if (typeof this.props.onDelete === 'function') {
const deleteButton = this.getActionButton('Delete', this.onDeleteClick);
actionButtons.push(deleteButton);
}
return (
<TableListItem
itemKey={this.props.measurementData.measurementNumber}
itemClass={`measurementItem ${this.props.itemClass} ${hasWarningClass}`}
itemIndex={this.props.itemIndex}
onItemClick={this.onItemClick}
>
<div>
<div className="measurementLocation">
{this.props.t(this.props.measurementData.label, {
keySeparator: '>',
nsSeparator: '|',
})}
</div>
<div className="displayTexts">{this.getDataDisplayText()}</div>
<div className="rowActions">{actionButtons}</div>
</div>
</TableListItem>
);
};
onItemClick = event => {
this.props.onItemClick(event, this.props.measurementData);
};
onRelabelClick = event => {
// Prevent onItemClick from firing
event.stopPropagation();
this.props.onRelabel(event, this.props.measurementData);
};
onEditDescriptionClick = event => {
// Prevent onItemClick from firing
event.stopPropagation();
this.props.onEditDescription(event, this.props.measurementData);
};
onDeleteClick = event => {
// Prevent onItemClick from firing
event.stopPropagation();
this.props.onDelete(event, this.props.measurementData);
};
getDataDisplayText = () => {
return this.props.measurementData.data.map((data, index) => {
return (
<div key={`displayText_${index}`} className="measurementDisplayText">
{data.displayText ? data.displayText : '...'}
</div>
);
});
};
getWarningContent = () => {
const { warningList = '' } = this.props.measurementData;
if (Array.isArray(warningList)) {
const listedWarnings = warningList.map((warn, index) => {
return <li key={index}>{warn}</li>;
});
return <ol>{listedWarnings}</ol>;
} else {
return <React.Fragment>{warningList}</React.Fragment>;
}
};
}
const connectedComponent = withTranslation('MeasurementTable')(
MeasurementTableItem
);
export { connectedComponent as MeasurementTableItem };
export default connectedComponent;

View File

@ -1,49 +0,0 @@
---
name: Measurement Table
menu: Components
route: /components/measurement-table
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { MeasurementTable } from './../index.js'
import NameSpace from '../../../__docs__/NameSpace'
//
import measurements from './measurements.js'
import timepoints from './timepoints.js'
import warnings from './warnings.js'
# Measurement Table
## Basic usage
<Playground>
<State
initial={{
// Spelling error: overwallWarnings
overwallWarnings: warnings,
timepoints,
measurementCollection: measurements,
}}
>
{({ state, setState }) => (
<div style={{ height: '340px' }}>
<MeasurementTable
{...state}
onItemClick={() => alert('onItemClick')}
onRelabelClick={() => alert('onRelabelClick')}
onDeleteClick={() => alert('onDeleteClick')}
onEditDescriptionClick={() => alert('onEditDescriptionClick')}
/>
</div>
)}
</State>
</Playground>
## API
<Props of={MeasurementTable} />
## Translation Namespace
<NameSpace name="MeasurementTable" />

View File

@ -1,93 +0,0 @@
export default [
{
maxMeasurements: 5,
groupName: 'Targets',
measurements: [
{
measurementId: '123',
measurementNumber: '123',
itemNumber: 1,
label: 'Chest Wall Posterior',
hasWarnings: true,
warningTitle: 'Criteria nonconformities',
isSplitLesion: false,
warningList: [
'All measurements should have a location',
'Nodal lesions must be >= 15mm short axis AND >= double the acquisition slice thickness by CT and MR',
],
data: [
{
displayText: '25.7 x 12.9',
},
{
displayText: '24.7 x 11.5',
},
{},
],
},
{
measurementId: '124',
measurementNumber: '124',
itemNumber: 2,
label: 'Bone Extremity',
data: [
{
displayText: '24.7 x 11.1',
},
{
displayText: '21.2 x 10.9',
},
{},
],
},
],
},
{
maxMeasurements: 3,
groupName: 'NonTargets',
measurements: [
{
measurementId: '125',
measurementNumber: '125',
itemNumber: 1,
label: 'Chest Wall Single Site',
data: [
{
displayText: 'MD',
},
{
displayText: 'NM',
},
{},
],
},
{
measurementId: '126',
measurementNumber: '126',
itemNumber: 2,
label: 'Extremity Multiple Sites',
data: [
{
displayText: 'CR',
},
{},
{},
],
},
{
measurementId: '127',
measurementNumber: '127',
label: 'Extremity Site',
data: [
{
displayText: 'CR',
},
{},
{
displayText: 'NM',
},
],
},
],
},
];

View File

@ -1,14 +0,0 @@
export default [
{
label: 'Follow-up 2',
date: '20-Dec-18',
},
{
label: 'Follow-up 1',
date: '15-Jun-18',
},
{
label: 'Baseline',
date: '10-Apr-18',
},
];

View File

@ -1,6 +0,0 @@
export default {
warningList: [
'All measurements should have a location',
'Nodal lesions must be >= 15mm short axis AND >= double the acquisition slice thickness by CT and MR',
],
};

View File

@ -1,2 +0,0 @@
export { MeasurementTable } from './MeasurementTable.js';
export { MeasurementTableItem } from './MeasurementTableItem.js';

View File

@ -1,76 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import Modal from 'react-modal';
import classNames from 'classnames';
import './OHIFModal.styl';
const customStyle = {
overlay: {
zIndex: 1071,
backgroundColor: 'rgb(0, 0, 0, 0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
};
Modal.setAppElement(document.getElementById('root'));
const OHIFModal = ({
className,
closeButton,
shouldCloseOnEsc,
isOpen,
title,
onClose,
children,
}) => {
const renderHeader = () => {
return (
title && (
<div className="OHIFModal__header" data-cy="modal-header">
<h4>{title}</h4>
{closeButton && (
<button data-cy="close-button" onClick={onClose}>
×
</button>
)}
</div>
)
);
};
return (
<Modal
className={classNames('OHIFModal', className)}
data-cy="modal"
shouldCloseOnEsc={shouldCloseOnEsc}
isOpen={isOpen}
title={title}
style={customStyle}
>
<>
{renderHeader()}
<div className="OHIFModal__content" data-cy="modal-content">
{children}
</div>
</>
</Modal>
);
};
OHIFModal.propTypes = {
className: PropTypes.string,
closeButton: PropTypes.bool,
shouldCloseOnEsc: PropTypes.bool,
isOpen: PropTypes.bool,
title: PropTypes.string,
onClose: PropTypes.func,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
};
export default OHIFModal;

View File

@ -1,67 +0,0 @@
.OHIFModal
background-color: var(--ui-gray-darker)
border-color: var(--ui-border-color)
color: var(--text-secondary-color)
border-radius: 6px
border: 0
color: var(--text-primary-color)
position: relative
-webkit-box-shadow: 0 3px 9px rgba(0,0,0,.5)
box-shadow: 0 3px 9px rgba(0,0,0,.5);
background-clip: padding-box
outline: 0
@media (min-width: 320px)
width: 78%
min-width: 300px
@media (min-width: 768px)
width: 600px
@media (min-width: 992px)
width: 900px
&__content
padding: 20px
max-height: 90vh;
overflow-x: hidden;
overflow-y: auto;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar
display: none;
&__header
display: flex
justify-content: space-between
align-items: center
border-bottom-width: 3px
border-bottom-style: solid
border-bottom-color: #000000
padding: 20px
position: relative
h4
color: var(--text-secondary-color)
font-size: 20px
font-weight: 500
line-height: 24px
padding-right: 24px
margin: 0
button
cursor: pointer
margin: -10px 0 0 0
padding: 0
background-color: transparent
border: none
color: var(--text-secondary-color)
font-size: 25px
font-weight: lighter
&:active,
&:focus,
&:focus:active
background-image: none
outline: 0
box-shadow: none

View File

@ -1,39 +0,0 @@
---
name: OHIFModal
menu: Components
route: /components/ohif-modal
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { OHIFModal } from './../index'
import NameSpace from '../../../__docs__/NameSpace'
# OHIF Modal
## Basic usage
<Playground>
<State
initial={{}}
>
{({ state, setState }) => (
<React.Fragment>
<div>
<pre>{JSON.stringify(state, null, 2)}</pre>
</div>
<div style={{ maxWidth: '400px', margin: '0 auto' }}>
<OHIFModal />
</div>
</React.Fragment>
)}
</State>
</Playground>
## API
<Props of={OHIFModal} />
## Translation Namespace
<NameSpace name="OHIFModal" />

View File

@ -1,2 +0,0 @@
import OHIFModal from './OHIFModal';
export { OHIFModal };

View File

@ -1,102 +0,0 @@
import classNames from 'classnames';
import React from 'react';
import PropTypes from 'prop-types';
import Transition, {
ENTERED,
ENTERING,
} from 'react-transition-group/Transition';
const propTypes = {
/**
* Show the component; triggers the fade in or fade out animation
*/
in: PropTypes.bool,
/**
* Wait until the first "enter" transition to mount the component (add it to the DOM)
*/
mountOnEnter: PropTypes.bool,
/**
* Unmount the component (remove it from the DOM) when it is faded out
*/
unmountOnExit: PropTypes.bool,
/**
* Run the fade in animation when the component mounts, if it is initially
* shown
*/
appear: PropTypes.bool,
/**
* Duration of the fade animation in milliseconds, to ensure that finishing
* callbacks are fired even if the original browser transition end events are
* canceled
*/
timeout: PropTypes.number,
/**
* Callback fired before the component fades in
*/
onEnter: PropTypes.func,
/**
* Callback fired after the component starts to fade in
*/
onEntering: PropTypes.func,
/**
* Callback fired after the has component faded in
*/
onEntered: PropTypes.func,
/**
* Callback fired before the component fades out
*/
onExit: PropTypes.func,
/**
* Callback fired after the component starts to fade out
*/
onExiting: PropTypes.func,
/**
* Callback fired after the component has faded out
*/
onExited: PropTypes.func,
};
const defaultProps = {
in: false,
timeout: 300,
mountOnEnter: false,
unmountOnExit: false,
appear: false,
};
const fadeStyles = {
[ENTERING]: 'in',
[ENTERED]: 'in',
};
class Fade extends React.Component {
render() {
const { className, children, ...props } = this.props;
return (
<Transition {...props}>
{(status, innerProps) =>
React.cloneElement(children, {
...innerProps,
className: classNames(
'fade',
className,
children.props.className,
fadeStyles[status]
),
})
}
</Transition>
);
}
}
Fade.propTypes = propTypes;
Fade.defaultProps = defaultProps;
export default Fade;

View File

@ -1,102 +0,0 @@
import classNames from 'classnames';
import React, { cloneElement } from 'react';
import PropTypes from 'prop-types';
import { Overlay as BaseOverlay } from 'react-overlays';
import elementType from 'prop-types-extra/lib/elementType';
import { withTranslation } from '../../contextProviders';
import Fade from './Fade';
const propTypes = {
/**
* Set the visibility of the Overlay
*/
show: PropTypes.bool,
/**
* Specify whether the overlay should trigger onHide when the user clicks outside the overlay
*/
rootClose: PropTypes.bool,
/**
* A callback invoked by the overlay when it wishes to be hidden. Required if
* `rootClose` is specified.
*/
onHide: PropTypes.func,
/**
* Use animation
*/
animation: PropTypes.oneOfType([PropTypes.bool, elementType]),
/**
* Callback fired before the Overlay transitions in
*/
onEnter: PropTypes.func,
/**
* Callback fired as the Overlay begins to transition in
*/
onEntering: PropTypes.func,
/**
* Callback fired after the Overlay finishes transitioning in
*/
onEntered: PropTypes.func,
/**
* Callback fired right before the Overlay transitions out
*/
onExit: PropTypes.func,
/**
* Callback fired as the Overlay begins to transition out
*/
onExiting: PropTypes.func,
/**
* Callback fired after the Overlay finishes transitioning out
*/
onExited: PropTypes.func,
/**
* Sets the direction of the Overlay.
*/
placement: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
};
const defaultProps = {
animation: Fade,
rootClose: false,
show: false,
placement: 'right',
};
class Overlay extends React.Component {
render() {
const { animation, children, ...props } = this.props;
const transition = animation === true ? Fade : animation || null;
let child;
if (!transition) {
child = cloneElement(children, {
className: classNames(children.props.className, 'in'),
});
} else {
child = children;
}
return (
<BaseOverlay {...props} transition={transition}>
{child}
</BaseOverlay>
);
}
}
Overlay.propTypes = propTypes;
Overlay.defaultProps = defaultProps;
const connectedComponent = withTranslation()(Overlay);
export { connectedComponent as Overlay };
export default connectedComponent;

View File

@ -1,330 +0,0 @@
import contains from 'dom-helpers/query/contains';
import React, { cloneElement } from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import warning from 'warning';
import { Overlay } from './Overlay';
import createChainedFunction from './createChainedFunction';
/**
* Check if value one is inside or equal to the of value
*
* @param {string} one
* @param {string|array} of
* @returns {boolean}
*/
function isOneOf(one, of) {
if (Array.isArray(of)) {
return of.indexOf(one) >= 0;
}
return one === of;
}
const triggerType = PropTypes.oneOf(['click', 'hover', 'focus']);
const propTypes = {
/**
* Specify which action or actions trigger Overlay visibility
*/
trigger: PropTypes.oneOfType([triggerType, PropTypes.arrayOf(triggerType)]),
/**
* A millisecond delay amount to show and hide the Overlay once triggered
*/
delay: PropTypes.number,
/**
* A millisecond delay amount before showing the Overlay once triggered.
*/
delayShow: PropTypes.number,
/**
* A millisecond delay amount before hiding the Overlay once triggered.
*/
delayHide: PropTypes.number,
// FIXME: This should be `defaultShow`.
/**
* The initial visibility state of the Overlay. For more nuanced visibility
* control, consider using the Overlay component directly.
*/
defaultOverlayShown: PropTypes.bool,
/**
* An element or text to overlay next to the target.
*/
overlay: PropTypes.node.isRequired,
/**
* A function to be called once the hide is triggered
*/
handleHide: PropTypes.func,
/**
* @private
*/
onBlur: PropTypes.func,
/**
* @private
*/
onClick: PropTypes.func,
/**
* @private
*/
onFocus: PropTypes.func,
/**
* @private
*/
onMouseOut: PropTypes.func,
/**
* @private
*/
onMouseOver: PropTypes.func,
// Overridden props from `<Overlay>`.
/**
* @private
*/
target: PropTypes.oneOf([null]),
/**
* @private
*/
onHide: PropTypes.oneOf([null]),
/**
* @private
*/
show: PropTypes.oneOf([null]),
};
const defaultProps = {
defaultOverlayShown: false,
trigger: ['hover', 'focus'],
};
class OverlayTrigger extends React.Component {
constructor(props, context) {
super(props, context);
this.handleToggle = this.handleToggle.bind(this);
this.handleDelayedShow = this.handleDelayedShow.bind(this);
this.handleDelayedHide = this.handleDelayedHide.bind(this);
this.handleHide = createChainedFunction(
this.handleHide.bind(this),
props.handleHide
);
this.handleMouseOver = e =>
this.handleMouseOverOut(this.handleDelayedShow, e, 'fromElement');
this.handleMouseOut = e =>
this.handleMouseOverOut(this.handleDelayedHide, e, 'toElement');
this._mountNode = null;
this.state = {
show: props.defaultOverlayShown,
};
}
componentDidMount() {
this._mountNode = document.createElement('div');
this.renderOverlay();
}
componentDidUpdate() {
this.renderOverlay();
}
componentWillUnmount() {
ReactDOM.unmountComponentAtNode(this._mountNode);
this._mountNode = null;
clearTimeout(this._hoverShowDelay);
clearTimeout(this._hoverHideDelay);
}
handleDelayedHide() {
if (this._hoverShowDelay != null) {
clearTimeout(this._hoverShowDelay);
this._hoverShowDelay = null;
return;
}
if (!this.state.show || this._hoverHideDelay != null) {
return;
}
const delay =
this.props.delayHide != null ? this.props.delayHide : this.props.delay;
if (!delay) {
this.hide();
return;
}
this._hoverHideDelay = setTimeout(() => {
this._hoverHideDelay = null;
this.hide();
}, delay);
}
handleDelayedShow() {
if (this._hoverHideDelay != null) {
clearTimeout(this._hoverHideDelay);
this._hoverHideDelay = null;
return;
}
if (this.state.show || this._hoverShowDelay != null) {
return;
}
const delay =
this.props.delayShow != null ? this.props.delayShow : this.props.delay;
if (!delay) {
this.show();
return;
}
this._hoverShowDelay = setTimeout(() => {
this._hoverShowDelay = null;
this.show();
}, delay);
}
handleHide() {
this.hide();
}
// Simple implementation of mouseEnter and mouseLeave.
// React's built version is broken: https://github.com/facebook/react/issues/4251
// for cases when the trigger is disabled and mouseOut/Over can cause flicker
// moving from one child element to another.
handleMouseOverOut(handler, e, relatedNative) {
const target = e.currentTarget;
const related = e.relatedTarget || e.nativeEvent[relatedNative];
if ((!related || related !== target) && !contains(target, related)) {
handler(e);
}
}
handleToggle() {
if (this.state.show) {
this.hide();
} else {
this.show();
}
}
hide() {
this.setState({ show: false });
}
makeOverlay(overlay, props) {
return (
<Overlay
{...props}
show={this.state.show}
onHide={this.handleHide}
target={this}
>
{overlay}
</Overlay>
);
}
show() {
this.setState({ show: true });
}
renderOverlay() {
ReactDOM.unstable_renderSubtreeIntoContainer(
this,
this._overlay,
this._mountNode
);
}
render() {
const {
trigger,
overlay,
children,
onBlur,
onClick,
onFocus,
onMouseOut,
onMouseOver,
...props
} = this.props;
delete props.delay;
delete props.delayShow;
delete props.delayHide;
delete props.defaultOverlayShown;
const child = React.Children.only(children);
const childProps = child.props;
const triggerProps = {};
if (this.state.show) {
triggerProps['aria-describedby'] = overlay.props.id;
}
// FIXME: The logic here for passing through handlers on this component is
// inconsistent. We shouldn't be passing any of these props through.
triggerProps.onClick = createChainedFunction(childProps.onClick, onClick);
if (isOneOf('click', trigger)) {
triggerProps.onClick = createChainedFunction(
triggerProps.onClick,
this.handleToggle
);
}
if (isOneOf('hover', trigger)) {
warning(
!(trigger === 'hover'),
'[react-bootstrap] Specifying only the `"hover"` trigger limits the ' +
'visibility of the overlay to just mouse users. Consider also ' +
'including the `"focus"` trigger so that touch and keyboard only ' +
'users can see the overlay as well.'
);
triggerProps.onMouseOver = createChainedFunction(
childProps.onMouseOver,
onMouseOver,
this.handleMouseOver
);
triggerProps.onMouseOut = createChainedFunction(
childProps.onMouseOut,
onMouseOut,
this.handleMouseOut
);
}
if (isOneOf('focus', trigger)) {
triggerProps.onFocus = createChainedFunction(
childProps.onFocus,
onFocus,
this.handleDelayedShow
);
triggerProps.onBlur = createChainedFunction(
childProps.onBlur,
onBlur,
this.handleDelayedHide
);
}
this._overlay = this.makeOverlay(overlay, props);
return cloneElement(child, triggerProps);
}
}
OverlayTrigger.propTypes = propTypes;
OverlayTrigger.defaultProps = defaultProps;
export { OverlayTrigger };

View File

@ -1,84 +0,0 @@
---
name: Overlay Trigger
menu: Components
route: /components/overlay-trigger
---
import { Playground, Props } from 'docz'
import { Tooltip } from './../../tooltip'
import { OverlayTrigger } from './../index.js'
# Overlay Trigger
## Basic usage
### On Hover
<Playground>
<div style={{
display: 'flex',
justifyContent: 'space-between'
}}>
{
['right', 'left', 'bottom', 'top'].map((placement) => {
return (
<React.Fragment key={`example-${placement}`}>
<OverlayTrigger
key={`example-${placement}`}
placement={placement}
overlay={
<Tooltip
placement={placement}
className='in'
id={`tooltip-${placement}`}
>
Here I am!
</Tooltip>
}
>
<button> Tooltip on {placement}</button>
</OverlayTrigger>
</React.Fragment>
)
})
}
</div>
</Playground>
### On Click
<Playground>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
}}
>
{['right', 'left', 'bottom', 'top'].map(placement => {
return (
<OverlayTrigger
key={`example-${placement}`}
placement={placement}
trigger="click"
overlay={
<Tooltip
placement={placement}
className="in"
id={`tooltip-${placement}`}
>
Here I am!
</Tooltip>
}
>
<button> Tooltip on {placement}</button>
</OverlayTrigger>
)
})}
</div>
</Playground>
## API
<Props of={OverlayTrigger} />

View File

@ -1,31 +0,0 @@
/**
* Safe chained function
*
* Will only create a new function if needed,
* otherwise will pass back existing functions or null.
*
* @param {function} functions to chain
* @returns {function|null}
*/
function createChainedFunction(...funcs) {
return funcs
.filter(f => f != null)
.reduce((acc, f) => {
if (typeof f !== 'function') {
throw new Error(
'Invalid Argument Type, must only provide functions, undefined, or null.'
);
}
if (acc === null) {
return f;
}
return function chainedFunction(...args) {
acc.apply(this, args);
f.apply(this, args);
};
}, null);
}
export default createChainedFunction;

View File

@ -1,2 +0,0 @@
export { Overlay } from './Overlay.js';
export { OverlayTrigger } from './OverlayTrigger.js';

View File

@ -1,209 +0,0 @@
.series-quick-switch
position: relative;
&:before
box-shadow: 0 0 0 5000px rgba(0, 0, 0, 0.8)
transition: opacity 0.3s ease
opacity: 0
content: ''
&:hover
z-index: 50
&:before
display: table;
opacity: 1
content: ''
&:not(.left):not(.right)
left: 50%
transform: translateX(-50%)
&.left
border-right: 1px solid var(--ui-border-color)
right: 0
&.right
left: 0
.series-switch
float: right
.study-switch
float: left
.study-browser
left: auto
right: 0
transform-origin: calc(100% - 1em) calc(1em + 17px)
.series-browser
left: 0
right: auto
transform-origin: 1em calc(1em + 15px)
.thumbnails-wrapper
transform-origin: 0% 0%
.thumbnail
float: left
.series-item
float: left
.title-label
color: var(--text-secondary-color)
font-size: 12px
font-weight: 500
line-height: 12px
opacity: 1
padding-bottom: 3px
text-align: center
transition: opacity 0.3s ease
.series-switch
padding-right: 10px;
.series-switch,
.study-switch
float: left
position: relative
.study-switch:hover:after
content: ''
display: block
height: 100px
left: 0
position: absolute
top: 0
width: 300px
.study-browser
left: 0
padding-top: 60px;
max-height: calc(100vh - 60px);
transform-origin: 1em calc(1em + 17px)
width: 300px
.study-browser-list
padding: 0 10px
.series-browser
max-height: 100vh
max-width: 720px
right: 0
transform-origin: calc(100% - 1em) calc(1em + 15px)
.thumbnails-wrapper
transition: transform 0.3s ease
transform-origin: 100% 0%
.thumbnail
float: right
.series-details
opacity: 0
transform: translateY(-100%)
transition: transform 0.3s ease, opacity 0.3s ease
.scrollable
padding-top: 15px
transition: padding-bottom 0.3s ease
.study-browser-series {
overflow hidden
}
.series-browser,
.study-browser
min-height: 120px
opacity: 0
position: absolute
top: 0
transform: scale(0)
transition: transform 0.3s ease , opacity 0.3s ease
z-index: 3
.series-box,
.study-box
height: 57px
width: 57px
.study-box
background-color: var(--ui-gray-dark)
border: solid 2px var(--ui-border-color-dark)
border-radius: 11px
.series-item
background-color: var(--box-background-color)
border-radius: 3px
float: right
height: 15px
margin: 2px
transition: opacity 0.3s ease
width: 15px
&.count
color: var(--text-primary-color)
background-color: transparent
font-size: 12px
font-weight: 500
line-height: 17px
text-align: center
&.active
background-color: var(--active-color)
&.series-triggered .series-browser,
.series-switch:hover .series-browser,
.study-switch:hover .study-browser
opacity: 1
transform: scale(1)
&.series-triggered .series-item,
.series-switch:hover .title-label
opacity: 0
&.series-triggered
.thumbnails-wrapper
transform: scale(0.9) translateY(80px)
.series-browser:not(:hover)
&>.scrollable
padding-bottom: 80px
&>.scrollNav
opacity: 0
.series-switch:hover .series-browser
.thumbnails-wrapper
transform: scale(1) translateY(0)
.thumbnail .series-details
opacity: 1
transform: translateY(0)
transition-delay: 0.3s
.study-browser-item.active .study-item-box
box-shadow: inset 0 0 0 3px var(--active-color)
@media screen and (max-width: 1599px)
.series-quick-switch
.series-box,
.study-box
width: 36px
height: 36px
.study-box
border-radius: 7px
.series-item
height: 10px
margin: 1px
width: 10px
&.count
font-size: 10px
font-weight: 300
line-height: 10px

View File

@ -1 +0,0 @@
export { QuickSwitch } from './QuickSwitch.js';

View File

@ -1,62 +0,0 @@
.ohif-radio-button-container {
--radio-button-dim: 20px;
}
.ohif-radio-button-container input {
position: absolute;
opacity: 0;
z-index: 10000;
cursor: pointer;
height: inherit;
width: inherit;
}
.ohif-radio-button-container {
display: block;
position: relative;
padding-left: 35px;
margin-bottom: 12px;
cursor: pointer;
font-size: 22px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.ohif-radio-button {
width: var(--radio-button-dim);
height: var(--radio-button-dim);
position: absolute;
top: 25%;
left: 0;
background-color: var(--ui-gray-lighter);
border-radius: 50%;
z-index: 1;
}
.ohif-radio-button:hover {
background-color: var(--default-color);
border-radius: 100%;
}
.ohif-radio-button:after {
content: '';
position: absolute;
display: none;
width: 70%;
height: 70%;
}
.ohif-radio-button-container .ohif-radio-button.ohif-selected:after {
display: block;
top: 14%;
left: 14%;
border-radius: 50%;
background: white;
}
.ohif-radio-button-label {
font-size: 12px;
font-weight: 500;
}

View File

@ -1,74 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './RadioButtonList.css';
export class RadioButtonList extends Component {
static className = 'RadioButtonList';
//TODO: Add fields to propTypes.description?
//These would be label (required), id (required), and checked (optional).
static propTypes = {
description: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
checked: PropTypes.bool,
})
),
};
constructor(props) {
super(props);
this.state = {};
for (let button of props.description) {
if (button.checked) {
this.state.checked = button.id;
}
}
}
handleChange(e) {
this.setState({ checked: e.target.value });
}
render() {
let buttons = this.props.description.map(button => {
let input = (
<input
type="radio"
checked={this.state.checked === button.id}
onChange={e => {
this.handleChange(e);
}}
value={button.id}
/>
);
//needed to style the custom radio check
let inputSpan;
if (this.state.checked === button.id) {
inputSpan = (
<span className="ohif-radio-button ohif-selected">{input}</span>
);
} else {
inputSpan = <span className="ohif-radio-button">{input}</span>;
}
return (
<span className="ohif-radio-button-container" key={button.id}>
<label className="ohif-radio-button-label">
{inputSpan}
{button.label}
</label>
</span>
);
});
return (
<div className="ohif-radio-button-group">
<form>{buttons}</form>
</div>
);
}
}

View File

@ -1,74 +0,0 @@
---
name: Radio Button List
menu: Elements
route: /components/radio-button-list
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { RadioButtonList } from './../index'
import NameSpace from '../../../__docs__/NameSpace'
#Radio Button List
## Basic usage
<Playground>
<State
initial={{
description : [
{
'label' : 'Button 1',
'id' : 'button1',
'checked' : false
},
{
'label' : 'Button 2',
'id' : 'button2',
'checked' : false,
},
{
'label' : 'Button 3',
'id' : 'button3',
'checked' : true,
}
]
}}
>
{({ state, setState }) => (
<React.Fragment>
<div>
<pre>{JSON.stringify(state, null, 2)}</pre>
</div>
<div style={{ maxWidth: '300px', margin: '0 auto' }}>
<RadioButtonList description={[
{
'label' : 'Button 1',
'id' : 'button1',
'checked' : false
},
{
'label' : 'Button 2',
'id' : 'button2',
'checked' : false,
},
{
'label' : 'Button 3',
'id' : 'coolButton3',
'checked' : true,
}
]}/>
</div>
</React.Fragment>
)}
</State>
</Playground>
## API
<Props of={RadioButtonList} />
## Translation Namespace
<NameSpace name="RadioButtonList" />

View File

@ -1 +0,0 @@
export { RadioButtonList } from './RadioButtonList.js';

View File

@ -1,84 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Icon } from './../../elements/Icon';
import './RoundedButtonGroup.css';
// TODO: Rename to Toggle?
class RoundedButtonGroup extends Component {
static className = 'RoundedButtonGroup';
static propTypes = {
options: PropTypes.arrayOf(
PropTypes.shape({
value: PropTypes.any,
label: PropTypes.string,
icon: PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({
name: PropTypes.string.isRequired,
}),
]),
})
),
value: PropTypes.string,
onValueChanged: PropTypes.func,
};
static defaultProps = {
options: [],
value: null,
};
onClickOption = value => {
let newValue = value;
if (this.props.value === value) {
newValue = null;
}
if (this.props.onValueChanged) {
this.props.onValueChanged(newValue);
}
};
render() {
let className = classnames(
RoundedButtonGroup.className,
'clearfix center-table'
);
const buttons = this.props.options.map((option, index) => {
const className = classnames({
roundedButtonWrapper: true,
noselect: true,
active: this.props.value === option.value,
});
const optionText = option.label && <span>{option.label}</span>;
const iconProps =
typeof option.icon === 'string' ? { name: option.icon } : option.icon;
const bottomLabel = option.bottomLabel && (
<div className="bottomLabel">{option.bottomLabel}</div>
);
return (
<div
key={index}
className={className}
onClick={() => this.onClickOption(option.value)}
>
<div className="roundedButton">
{optionText}
{iconProps && <Icon {...iconProps} />}
</div>
{bottomLabel}
</div>
);
});
return <div className={className}>{buttons}</div>;
}
}
export { RoundedButtonGroup };

View File

@ -1,51 +0,0 @@
---
name: Rounded Button Group
menu: Components
route: /components/rounded-button-group
---
import { Playground, Props } from 'docz';
import { State } from 'react-powerplug';
import { RoundedButtonGroup } from './../index.js';
# Rounded Button Group
A basic styled toggle switch
## Basic usage
<Playground>
<State
initial={{
value: null,
options: [
{
value: 'studies',
icon: 'th',
bottomLabel: 'Series',
},
{
value: 'measurements',
icon: 'th-list',
bottomLabel: 'Measurements',
},
],
}}
>
{({ state, setState }) => (
<React.Fragment>
<div>
<pre>value: {JSON.stringify(state.value, null, 2)}</pre>
</div>
<RoundedButtonGroup
{...state}
onValueChanged={value => setState({ value })}
/>
</React.Fragment>
)}
</State>
</Playground>
## API
<Props of={RoundedButtonGroup} />

View File

@ -1 +0,0 @@
export { RoundedButtonGroup } from './RoundedButtonGroup.js';

View File

@ -1,37 +0,0 @@
import { Component } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
export default class InputRadio extends Component {
static propTypes = {
value: PropTypes.string,
label: PropTypes.string.isRequired,
itemData: PropTypes.object.isRequired,
labelClass: PropTypes.string,
id: PropTypes.string.isRequired,
onSelected: PropTypes.func.isRequired,
};
render() {
const labelClass = this.props.labelClass ? this.props.labelClass : '';
return (
<label
className={'wrapperLabel radioLabel ' + labelClass}
htmlFor={this.props.id}
>
<input
type="radio"
id={this.props.id}
className="radioInput"
value={this.props.value}
onChange={this.onSelected}
/>
<span className="wrapperText">{this.props.label}</span>
</label>
);
}
onSelected = evt => {
this.props.onSelected(evt, this.props.itemData);
};
}

View File

@ -1,192 +0,0 @@
import './SelectTree.styl';
import React, { Component } from 'react';
import { Icon } from './../../elements/Icon';
import InputRadio from './InputRadio.js';
import PropTypes from 'prop-types';
import SelectTreeBreadcrumb from './SelectTreeBreadcrumb.js';
import cloneDeep from 'lodash.clonedeep';
export class SelectTree extends Component {
static propTypes = {
autoFocus: PropTypes.bool,
searchEnabled: PropTypes.bool,
selectTreeFirstTitle: PropTypes.string,
selectTreeSecondTitle: PropTypes.string,
/** Called when 'componentDidUpdate' is triggered */
onComponentChange: PropTypes.func,
/** [{ label, value, items[]}] - An array of items than can be expanded to show child items */
items: PropTypes.array.isRequired,
/** fn(evt, item) - Called when a child item is selected; receives event and selected item */
onSelected: PropTypes.func.isRequired,
};
static defaultProps = {
searchEnabled: true,
autoFocus: true,
selectTreeFirstTitle: 'First Level itens',
items: [],
};
constructor(props) {
super(props);
this.state = {
searchTerm: null,
currentNode: null,
value: null,
};
}
render() {
const treeItems = this.getTreeItems();
return (
<div className="selectTree selectTreeRoot">
<div className="treeContent">
{this.headerItem()}
<div className="treeOptions">
{this.state.currentNode && (
<SelectTreeBreadcrumb
onSelected={this.onBreadcrumbSelected}
label={this.state.currentNode.label}
value={this.state.currentNode.value}
/>
)}
<div className="treeInputsWrapper">
<div className="treeInputs">{treeItems}</div>
</div>
</div>
</div>
</div>
);
}
componentDidUpdate = () => {
if (this.props.onComponentChange) {
this.props.onComponentChange();
}
};
isLeafSelected = item => item && !Array.isArray(item.items);
getLabelClass = item => {
let labelClass = 'treeLeaf';
if (this.state.searchTerm || Array.isArray(item.items)) {
labelClass = 'treeNode';
}
return labelClass;
};
filterItems() {
const filteredItems = [];
const rawItems = cloneDeep(this.props.items);
rawItems.forEach(item => {
if (Array.isArray(item.items)) {
item.items.forEach(item => {
const label = item.label.toLowerCase();
const searchTerm = this.state.searchTerm.toLowerCase();
if (label.indexOf(searchTerm) !== -1) {
filteredItems.push(item);
}
});
} else {
const label = item.label.toLowerCase();
const searchTerm = this.state.searchTerm.toLowerCase();
if (label.indexOf(searchTerm) !== -1) {
filteredItems.push(item);
}
}
});
return filteredItems;
}
getTreeItems() {
const storageKey = 'SelectTree';
let treeItems;
if (this.state.searchTerm) {
treeItems = this.filterItems();
} else if (this.state.currentNode) {
treeItems = cloneDeep(this.state.currentNode.items);
} else {
treeItems = cloneDeep(this.props.items);
}
return treeItems.map((item, index) => {
let itemKey = index;
if (this.state.currentNode) {
itemKey += `_${this.state.currentNode.value}`;
}
return (
<InputRadio
key={itemKey}
id={`${storageKey}_${item.value}`}
name={index}
itemData={item}
value={item.value}
label={item.label}
labelClass={this.getLabelClass(item)}
onSelected={this.onSelected}
/>
);
});
}
headerItem = () => {
let title = this.props.selectTreeFirstTitle;
if (this.state.currentNode && this.props.selectTreeSecondTitle) {
title = this.props.selectTreeSecondTitle;
}
return (
<div className="wrapperLabel treeHeader">
<div className="wrapperText">{title}</div>
{this.props.searchEnabled && (
<div className="wrapperSearch">
<div className="searchIcon">
<Icon name="search" />
</div>
<input
type="text"
className="searchInput"
placeholder="Search labels"
autoFocus={this.props.autoFocus}
onChange={this.searchLocations}
value={this.state.searchTerm ? this.state.searchTerm : ''}
/>
</div>
)}
</div>
);
};
searchLocations = evt => {
this.setState({
currentNode: null,
searchTerm: evt.currentTarget.value,
});
};
onSelected = (event, item) => {
if (this.isLeafSelected(item)) {
this.setState({
searchTerm: null,
currentNode: null,
value: null,
});
} else {
this.setState({
currentNode: item,
});
}
return this.props.onSelected(event, item);
};
onBreadcrumbSelected = () => {
this.setState({
currentNode: null,
});
};
}

View File

@ -1,108 +0,0 @@
@import './../../design/styles/common/global.styl'
.selectTreeRoot
text-align: initial;
width: 320px;
max-height: inherit;
font-size: 14px;
line-height: 26px;
.treeContent
max-height: inherit;
overflow: hidden;
border: 1px solid #c4c4c4;
border-radius: 5px;
background-color: #ffffff;
display: flex;
flex-direction: column;
.treeHeader
background-color: #c0c0c0;
border-bottom: 1px solid #c4c4c4;
color: #303030;
padding: 5px;
text-align: center;
.wrapperText
font-weight: bold;
padding-bottom: 2px;
.wrapperSearch
width: 100%;
.searchIcon
position: absolute;
width: 18px;
height: 15px;
margin: 7px 10px 13px 10px;
color: #a3a3a3;
.searchInput
width: 100%;
padding: 6px 12px 6px 35px;
background-color: #fff;
background-image: none;
border: 1px solid #9b9b9b;
border-radius: 4px;
font-size: 14px;
line-height: 1.42857143;
color: #555;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
&:focus
outline: 0;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
0 0 8px rgba(102, 175, 233, 0.6);
.wrapperText
white-space: nowrap;
cursor: pointer;
.selectTreeBreadcrumb
.wrapperLabel
cursor: pointer;
padding: 0 12px;
height: 41px;
width: 100%;
line-height: 41px;
.backIcon
padding-right: 10px;
svg
width: 14px;
height: 14px;
position: relative;
top: 2px;
color: #457fb0;
.treeOptions
overflow: auto;
height: 100%;
.selectTreeBreadcrumb + .treeInputsWrapper .treeInputs .treeLeaf
border: none;
height: initial;
line-height: 26px;
& > .treeInputsWrapper > .treeInputs .treeLeaf
border-bottom: 1px solid #ccc;
height: 41px;
line-height: 41px;
.treeNode, .treeLeaf
cursor: pointer;
display: block;
overflow: hidden;
font-weight: normal;
box-shadow: 0 0 0 200px transparent;
padding: 0 12px;
.treeNode
border-bottom: 1px solid #ccc;
height: 41px;
line-height: 41px;
.radioLabel .radioInput
display: none;

View File

@ -1,37 +0,0 @@
import { Component } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { Icon } from './../../elements/Icon';
export default class SelectTreeBreadcrumb extends Component {
static propTypes = {
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
onSelected: PropTypes.func.isRequired,
};
render() {
return (
<div className="selectTreeBreadcrumb">
<label
className="wrapperLabel radioLabel"
htmlFor="selectTreeBreadcrumb"
>
<input
type="radio"
id="selectTreeBreadcrumb"
className="treeNode radioInput"
value={this.props.value}
onChange={this.props.onSelected}
/>
<span className="wrapperText">
<span className="backIcon">
<Icon name="fast-backward" />
</span>
{this.props.label}
</span>
</label>
</div>
);
}
}

View File

@ -1,42 +0,0 @@
---
name: Select Tree
menu: Components
route: /components/select-tree
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { SelectTree } from './../index.js'
import selectTreeItems from './selectTreeItems.js'
# SelectTree
## Basic usage
<Playground>
<State initial={{
items: selectTreeItems,
}}>
{({ state, setState }) => (
<React.Fragment>
<pre style={{ maxHeight: '135px', overflowX: 'auto' }}>
{JSON.stringify(state.items, null, 2)}
</pre>
<SelectTree
items={state.items}
onSelected={(event, item) => setState({ itemLabelSelected: item.label }) }
selectTreeFirstTitle="Select Tree Example Header"
autoFocus={false}
/>
</React.Fragment>
)}
</State>
</Playground>
## API
<Props of={SelectTree} />

View File

@ -1,30 +0,0 @@
export default [
{
label: 'A',
value: 'A',
items: [
{
label: 'Abdominal Wall',
value: 'AbdominalWall',
},
{
label: 'Adrenal Left',
value: 'AdrenalLeft',
},
],
},
{
label: 'B',
value: 'B',
items: [
{
label: 'Brain',
value: 'Brain',
},
{
label: 'Breast',
value: 'Breast',
},
],
},
];

View File

@ -1 +0,0 @@
export { SelectTree } from './SelectTree';

Some files were not shown because too many files have changed in this diff Show More