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 -->
|
||||
<!-- markdownlint-disable -->
|
||||
<div align="center">
|
||||
<h1>@ohif/ui</h1>
|
||||
# Basic Docz example
|
||||
|
||||
<p><strong>@ohif/ui</strong> is a collection of components and utilities that power OHIF's <a href="https://github.com/OHIF/Viewers">zero-footprint DICOM viewer</a>.</p>
|
||||
</div>
|
||||
## Using `create-docz-app`
|
||||
|
||||
<div align="center">
|
||||
<a href="https://react.ohif.org/"><strong>Read The Docs</strong></a> |
|
||||
<a href="https://react.ohif.org/contributing">Edit the docs</a>
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
[![NPM version][npm-version-image]][npm-url]
|
||||
[![NPM downloads][npm-downloads-image]][npm-url]
|
||||
[](#contributors)
|
||||
[![MIT License][license-image]][license-url]
|
||||
<!-- markdownlint-enable -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
This set of "Medical Imaging Viewer" React components are maintained separately
|
||||
to:
|
||||
|
||||
- Decouple presentation from business logic
|
||||
- Test and develop components in isolation
|
||||
- Provide well documented, reusable components
|
||||
- Aid rapid application development for context specific viewers
|
||||
|
||||
## Install
|
||||
|
||||
> This component library is pre- v1.0. All realeases until a v1.0 have the
|
||||
> possibility of introducing breaking changes. Please depend on an "exact"
|
||||
> version in your projects to prevent issues caused by loose versioning.
|
||||
|
||||
For full installation instructions, be sure to check out our
|
||||
[getting started](https://react.ohif.org/getting-started#installation) guide.
|
||||
|
||||
```bash
|
||||
// with npm
|
||||
npm i @ohif/ui --save-exact
|
||||
|
||||
// with yarn
|
||||
yarn add @ohif/ui --exact
|
||||
```sh
|
||||
npx create-docz-app docz-app-basic
|
||||
# or
|
||||
yarn create docz-app docz-app-basic
|
||||
```
|
||||
|
||||
## Usage
|
||||
## Download manually
|
||||
|
||||
```jsx
|
||||
import React, { Component } from 'react';
|
||||
import { LayoutButton } from '@ohif/ui';
|
||||
|
||||
class Example extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selectedCell: {
|
||||
className: 'hover',
|
||||
col: 1,
|
||||
row: 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<LayoutButton
|
||||
selectedCell={this.state.selectedCell}
|
||||
onChange={cell => this.setState({ selectedCell: cell })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
```sh
|
||||
curl https://codeload.github.com/doczjs/docz/tar.gz/master | tar -xz --strip=2 docz-master/examples/basic
|
||||
mv basic docz-basic-example
|
||||
cd docz-basic-example
|
||||
```
|
||||
|
||||
## Developing Locally
|
||||
|
||||
_Restore dependencies after cloning:_
|
||||
|
||||
```bash
|
||||
# Restore workspace dependencies (from repository root)
|
||||
yarn install
|
||||
|
||||
# From project root
|
||||
yarn run dev:ui
|
||||
|
||||
# OR from this project's directory
|
||||
yarn run dev
|
||||
## Setup
|
||||
|
||||
```sh
|
||||
yarn # npm i
|
||||
```
|
||||
|
||||
## Contributors ✨
|
||||
## Run
|
||||
|
||||
Thanks goes to these wonderful people
|
||||
([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||
```sh
|
||||
yarn dev # npm run dev
|
||||
```
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
<!-- prettier-ignore -->
|
||||
<table><tr><td align="center"><a href="https://github.com/swederik"><img src="https://avatars3.githubusercontent.com/u/607793?v=4" width="100px;" alt="Erik Ziegler"/><br /><sub><b>Erik Ziegler</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=swederik" title="Code">💻</a> <a href="#maintenance-swederik" title="Maintenance">🚧</a></td><td align="center"><a href="http://dannyrb.com/"><img src="https://avatars1.githubusercontent.com/u/5797588?v=4" width="100px;" alt="Danny Brown"/><br /><sub><b>Danny Brown</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=dannyrb" title="Code">💻</a> <a href="#maintenance-dannyrb" title="Maintenance">🚧</a></td><td align="center"><a href="https://github.com/galelis"><img src="https://avatars3.githubusercontent.com/u/2378326?v=4" width="100px;" alt="Gustavo André Lelis"/><br /><sub><b>Gustavo André Lelis</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=galelis" title="Code">💻</a> <a href="#maintenance-galelis" title="Maintenance">🚧</a></td><td align="center"><a href="https://github.com/maltempi"><img src="https://avatars3.githubusercontent.com/u/7017182?v=4" width="100px;" alt="Thiago Maltempi"/><br /><sub><b>Thiago Maltempi</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=maltempi" title="Code">💻</a></td><td align="center"><a href="https://www.linkedin.com/in/siliconvalleynextgeneration/"><img src="https://avatars0.githubusercontent.com/u/1230575?v=4" width="100px;" alt="Esref Durna"/><br /><sub><b>Esref Durna</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=EsrefDurna" title="Code">💻</a></td><td align="center"><a href="http://www.isomics.com"><img src="https://avatars0.githubusercontent.com/u/126077?v=4" width="100px;" alt="Steve Pieper"/><br /><sub><b>Steve Pieper</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=pieper" title="Code">💻</a></td><td align="center"><a href="http://www.biharck.com.br"><img src="https://avatars0.githubusercontent.com/u/1713255?v=4" width="100px;" alt="Biharck Araujo"/><br /><sub><b>Biharck Araujo</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=biharck" title="Code">💻</a></td></tr><tr><td align="center"><a href="https://rodrigoea.com/"><img src="https://avatars3.githubusercontent.com/u/1905961?v=4" width="100px;" alt="Rodrigo Antinarelli"/><br /><sub><b>Rodrigo Antinarelli</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=rodrigolabs" title="Code">💻</a></td><td align="center"><a href="https://github.com/jfmedeiros1820"><img src="https://avatars1.githubusercontent.com/u/2211708?v=4" width="100px;" alt="João Felipe de Medeiros Moreira"/><br /><sub><b>João Felipe de Medeiros Moreira</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=jfmedeiros1820" title="Code">💻</a></td><td align="center"><a href="https://github.com/jamesg1"><img src="https://avatars3.githubusercontent.com/u/3621147?v=4" width="100px;" alt="James Gosbell"/><br /><sub><b>James Gosbell</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=jamesg1" title="Code">💻</a></td><td align="center"><a href="https://github.com/evren217"><img src="https://avatars1.githubusercontent.com/u/4920551?v=4" width="100px;" alt="Evren Ozkan"/><br /><sub><b>Evren Ozkan</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=evren217" title="Code">💻</a></td><td align="center"><a href="https://github.com/zsaltzman"><img src="https://avatars1.githubusercontent.com/u/19156530?v=4" width="100px;" alt="Zach S."/><br /><sub><b>Zach S.</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=zsaltzman" title="Code">💻</a></td><td align="center"><a href="https://github.com/muakdogan"><img src="https://avatars0.githubusercontent.com/u/19971240?v=4" width="100px;" alt="Mete Ugur Akdogan"/><br /><sub><b>Mete Ugur Akdogan</b></sub></a><br /><a href="https://github.com/OHIF/react-viewerbase/commits?author=muakdogan" title="Code">💻</a></td></tr></table>
|
||||
## Build
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
```sh
|
||||
yarn build # npm run build
|
||||
```
|
||||
|
||||
This project follows the
|
||||
[all-contributors](https://github.com/all-contributors/all-contributors)
|
||||
specification. Contributions of any kind welcome!
|
||||
## Serve built app
|
||||
|
||||
## License
|
||||
|
||||
MIT © [OHIF](https://github.com/OHIF)
|
||||
|
||||
<!--
|
||||
Links:
|
||||
-->
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
[all-contributors-image]: https://img.shields.io/badge/all_contributors-0-orange.svg?style=flat-square
|
||||
[npm-url]: https://npmjs.org/package/@ohif/ui
|
||||
[npm-downloads-image]: https://img.shields.io/npm/dm/@ohif/ui.svg?style=flat-square
|
||||
[npm-version-image]: https://img.shields.io/npm/v/@ohif/ui.svg?style=flat-square
|
||||
[license-image]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square
|
||||
[license-url]: LICENSE
|
||||
<!-- prettier-ignore-end -->
|
||||
```sh
|
||||
yarn serve # npm run serve
|
||||
```
|
||||
File diff suppressed because one or more lines are too long
@ -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",
|
||||
"version": "1.4.4",
|
||||
"version": "2.0.0",
|
||||
"description": "A set of React components for Medical Imaging Viewers",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
"repository": "OHIF/Viewers",
|
||||
"main": "dist/index.umd.js",
|
||||
"module": "src/index.js",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
@ -20,51 +18,24 @@
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "docz dev",
|
||||
"dev:ui": "yarn run dev",
|
||||
"docs": "docz build",
|
||||
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
||||
"build:package": "yarn run build",
|
||||
"prepublishOnly": "yarn run build",
|
||||
"test:unit": "jest --watchAll",
|
||||
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/i18n": "^0.2.3",
|
||||
"i18next": "^17.0.3",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-i18next": "^10.11.0"
|
||||
"dev": "concurrently \"npm run watch:css\" \"docz dev\"",
|
||||
"build": "concurrently \"npm run build:css\" \"docz build\"",
|
||||
"serve": "docz serve",
|
||||
"build:css": "postcss src/styles/tailwind.css -o src/gatsby-theme-docz/app.css",
|
||||
"watch:css": "postcss src/styles/tailwind.css -o src/gatsby-theme-docz/app.css -w"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"@ohif/i18n": "^0.2.3",
|
||||
"browser-detect": "^0.2.28",
|
||||
"classnames": "2.2.6",
|
||||
"i18next": "^17.0.3",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.isequal": "4.5.0",
|
||||
"moment": "2.24.0",
|
||||
"prop-types": "15.6.2",
|
||||
"react-bootstrap-modal": "4.2.0",
|
||||
"react-dates": "21.2.1",
|
||||
"react-dnd": "9.4.0",
|
||||
"react-dnd-html5-backend": "^9.4.0",
|
||||
"react-dnd-touch-backend": "^9.4.0",
|
||||
"react-draggable": "^4.1.0",
|
||||
"react-i18next": "^10.11.0",
|
||||
"react-modal": "^3.11.1",
|
||||
"react-with-direction": "1.3.0"
|
||||
"concurrently": "^5.1.0",
|
||||
"docz": "latest",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.11.0",
|
||||
"react-dom": "^16.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mdx-js/tag": "^0.20.3",
|
||||
"autoprefixer": "^9.6.0",
|
||||
"docz": "1.2.0",
|
||||
"docz-core": "1.2.0",
|
||||
"docz-plugin-css": "0.11.0",
|
||||
"docz-theme-default": "1.2.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"react-powerplug": "1.0.0"
|
||||
"autoprefixer": "^9.7.4",
|
||||
"postcss-cli": "^7.1.0",
|
||||
"stylelint": "^13.1.0",
|
||||
"stylelint-config-standard": "^20.0.0",
|
||||
"tailwindcss": "^1.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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