initial structure for components rewrite
This commit is contained in:
parent
944afe4a3e
commit
c391c007ff
@ -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
|
|
||||||
}
|
|
||||||
@ -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
2
platform/ui/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.docz
|
||||||
|
node_modules
|
||||||
3
platform/ui/.stylelintrc.json
Normal file
3
platform/ui/.stylelintrc.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "stylelint-config-standard"
|
||||||
|
}
|
||||||
@ -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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -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.
|
|
||||||
@ -1,125 +1,41 @@
|
|||||||
<!-- prettier-ignore-start -->
|
# Basic Docz example
|
||||||
<!-- markdownlint-disable -->
|
|
||||||
<div align="center">
|
|
||||||
<h1>@ohif/ui</h1>
|
|
||||||
|
|
||||||
<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>
|
## Using `create-docz-app`
|
||||||
</div>
|
|
||||||
|
|
||||||
<div align="center">
|
```sh
|
||||||
<a href="https://react.ohif.org/"><strong>Read The Docs</strong></a> |
|
npx create-docz-app docz-app-basic
|
||||||
<a href="https://react.ohif.org/contributing">Edit the docs</a>
|
# or
|
||||||
|
yarn create docz-app docz-app-basic
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
[![NPM version][npm-version-image]][npm-url]
|
|
||||||
[![NPM downloads][npm-downloads-image]][npm-url]
|
|
||||||
[](#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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Download manually
|
||||||
|
|
||||||
```jsx
|
```sh
|
||||||
import React, { Component } from 'react';
|
curl https://codeload.github.com/doczjs/docz/tar.gz/master | tar -xz --strip=2 docz-master/examples/basic
|
||||||
import { LayoutButton } from '@ohif/ui';
|
mv basic docz-basic-example
|
||||||
|
cd docz-basic-example
|
||||||
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 })}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Developing Locally
|
## Setup
|
||||||
|
|
||||||
_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
|
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn # npm i
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contributors ✨
|
## Run
|
||||||
|
|
||||||
Thanks goes to these wonderful people
|
```sh
|
||||||
([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
yarn dev # npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
## Build
|
||||||
<!-- 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>
|
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
```sh
|
||||||
|
yarn build # npm run build
|
||||||
|
```
|
||||||
|
|
||||||
This project follows the
|
## Serve built app
|
||||||
[all-contributors](https://github.com/all-contributors/all-contributors)
|
|
||||||
specification. Contributions of any kind welcome!
|
|
||||||
|
|
||||||
## License
|
```sh
|
||||||
|
yarn serve # npm run serve
|
||||||
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 -->
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
module.exports = require("../../babel.config.js");
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -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
|
|
||||||
};
|
|
||||||
@ -1,12 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/ui",
|
"name": "@ohif/ui",
|
||||||
"version": "1.4.4",
|
"version": "2.0.0",
|
||||||
"description": "A set of React components for Medical Imaging Viewers",
|
"description": "A set of React components for Medical Imaging Viewers",
|
||||||
"author": "OHIF Contributors",
|
"author": "OHIF Contributors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": "OHIF/Viewers",
|
"repository": "OHIF/Viewers",
|
||||||
"main": "dist/index.umd.js",
|
|
||||||
"module": "src/index.js",
|
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
@ -20,51 +18,24 @@
|
|||||||
"README.md"
|
"README.md"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "docz dev",
|
"dev": "concurrently \"npm run watch:css\" \"docz dev\"",
|
||||||
"dev:ui": "yarn run dev",
|
"build": "concurrently \"npm run build:css\" \"docz build\"",
|
||||||
"docs": "docz build",
|
"serve": "docz serve",
|
||||||
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
"build:css": "postcss src/styles/tailwind.css -o src/gatsby-theme-docz/app.css",
|
||||||
"build:package": "yarn run build",
|
"watch:css": "postcss src/styles/tailwind.css -o src/gatsby-theme-docz/app.css -w"
|
||||||
"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"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.5.5",
|
"concurrently": "^5.1.0",
|
||||||
"@ohif/i18n": "^0.2.3",
|
"docz": "latest",
|
||||||
"browser-detect": "^0.2.28",
|
"prop-types": "^15.7.2",
|
||||||
"classnames": "2.2.6",
|
"react": "^16.11.0",
|
||||||
"i18next": "^17.0.3",
|
"react-dom": "^16.11.0"
|
||||||
"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"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@mdx-js/tag": "^0.20.3",
|
"autoprefixer": "^9.7.4",
|
||||||
"autoprefixer": "^9.6.0",
|
"postcss-cli": "^7.1.0",
|
||||||
"docz": "1.2.0",
|
"stylelint": "^13.1.0",
|
||||||
"docz-core": "1.2.0",
|
"stylelint-config-standard": "^20.0.0",
|
||||||
"docz-plugin-css": "0.11.0",
|
"tailwindcss": "^1.2.0"
|
||||||
"docz-theme-default": "1.2.0",
|
|
||||||
"lodash.clonedeep": "^4.5.0",
|
|
||||||
"react-powerplug": "1.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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')],
|
||||||
|
};
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -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)
|
|
||||||
@ -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 |
@ -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.
|
|
||||||
@ -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 />
|
|
||||||
@ -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 }`
|
|
||||||
@ -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>
|
|
||||||
@ -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 -->
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@ -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)
|
|
||||||
@ -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;
|
|
||||||
@ -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%
|
|
||||||
34
platform/ui/src/components/Alert.jsx
Normal file
34
platform/ui/src/components/Alert.jsx
Normal 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',
|
||||||
|
};
|
||||||
28
platform/ui/src/components/Alert.mdx
Normal file
28
platform/ui/src/components/Alert.mdx
Normal 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>
|
||||||
124
platform/ui/src/components/Button/Button.jsx
Normal file
124
platform/ui/src/components/Button/Button.jsx
Normal 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;
|
||||||
180
platform/ui/src/components/Button/Button.mdx
Normal file
180
platform/ui/src/components/Button/Button.mdx
Normal 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>
|
||||||
2
platform/ui/src/components/Button/index.js
Normal file
2
platform/ui/src/components/Button/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import Button from './Button';
|
||||||
|
export default Button;
|
||||||
17
platform/ui/src/components/Input/Input.jsx
Normal file
17
platform/ui/src/components/Input/Input.jsx
Normal 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;
|
||||||
30
platform/ui/src/components/Input/Input.mdx
Normal file
30
platform/ui/src/components/Input/Input.mdx
Normal 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>
|
||||||
2
platform/ui/src/components/Input/index.js
Normal file
2
platform/ui/src/components/Input/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import Input from './Input';
|
||||||
|
export default Input;
|
||||||
34
platform/ui/src/components/Label/Label.jsx
Normal file
34
platform/ui/src/components/Label/Label.jsx
Normal 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;
|
||||||
20
platform/ui/src/components/Label/Label.mdx
Normal file
20
platform/ui/src/components/Label/Label.mdx
Normal 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>
|
||||||
2
platform/ui/src/components/Label/index.js
Normal file
2
platform/ui/src/components/Label/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import Label from './Label';
|
||||||
|
export default Label;
|
||||||
@ -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" />
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export { Checkbox } from './checkbox.js';
|
|
||||||
@ -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;
|
|
||||||
@ -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)
|
|
||||||
@ -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" />
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import CineDialog from './CineDialog';
|
|
||||||
export { CineDialog };
|
|
||||||
@ -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;
|
|
||||||
@ -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;
|
|
||||||
@ -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;
|
|
||||||
@ -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;
|
|
||||||
@ -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" />
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import ViewportDownloadForm from './ViewportDownloadForm';
|
|
||||||
export { ViewportDownloadForm };
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export { default as ContextMenu } from './ContextMenu.js';
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
.language-select
|
|
||||||
color: var(--primary-background-color)
|
|
||||||
display: block
|
|
||||||
min-width: 150px
|
|
||||||
@ -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;
|
|
||||||
@ -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
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
.LayoutExample {
|
|
||||||
background-color: var(--primary-background-color);
|
|
||||||
width: 160px;
|
|
||||||
height: 160px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.GridLayout td {
|
|
||||||
padding: 5px;
|
|
||||||
border: 1px solid black;
|
|
||||||
}
|
|
||||||
@ -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" />
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
export { LayoutButton } from './LayoutButton.js';
|
|
||||||
export { LayoutChooser } from './LayoutChooser.js';
|
|
||||||
@ -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;
|
|
||||||
@ -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
|
|
||||||
@ -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;
|
|
||||||
@ -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" />
|
|
||||||
@ -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',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@ -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',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@ -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',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
export { MeasurementTable } from './MeasurementTable.js';
|
|
||||||
export { MeasurementTableItem } from './MeasurementTableItem.js';
|
|
||||||
@ -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;
|
|
||||||
@ -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
|
|
||||||
@ -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" />
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import OHIFModal from './OHIFModal';
|
|
||||||
export { OHIFModal };
|
|
||||||
@ -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;
|
|
||||||
@ -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;
|
|
||||||
@ -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 };
|
|
||||||
@ -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} />
|
|
||||||
@ -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;
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
export { Overlay } from './Overlay.js';
|
|
||||||
export { OverlayTrigger } from './OverlayTrigger.js';
|
|
||||||
@ -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
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export { QuickSwitch } from './QuickSwitch.js';
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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" />
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export { RadioButtonList } from './RadioButtonList.js';
|
|
||||||
@ -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 };
|
|
||||||
@ -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} />
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export { RoundedButtonGroup } from './RoundedButtonGroup.js';
|
|
||||||
@ -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);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -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,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -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;
|
|
||||||
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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} />
|
|
||||||
@ -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',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export { SelectTree } from './SelectTree';
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user