Compare commits

..

No commits in common. "master" and "v1.1.15" have entirely different histories.

121 changed files with 20346 additions and 6729 deletions

29
.circleci/config.yml Normal file
View File

@ -0,0 +1,29 @@
version: 2
jobs:
build:
docker:
- image: node:12.18.4
steps:
- checkout
- restore_cache:
keys:
- yarn-packages-v4-{{ checksum "yarn.lock" }}
- run: yarn install --frozen-lockfile --ignore-optional --non-interactive
- save_cache:
key: yarn-packages-v4-{{ checksum "yarn.lock" }}
paths: [~/.cache/yarn, node_modules]
- run: yarn build
- run: yarn lint
- run: yarn tsc -p src/tsconfig.json
- run:
name: Install JUnit coverage reporter
command: yarn add --dev jest-junit
- run:
name: Run tests with JUnit as reporter
command: yarn test --ci --runInBand --reporters=default --reporters=jest-junit
environment:
JEST_JUNIT_OUTPUT_DIR: ./reports/junit/
- store_test_results:
path: ./reports/junit/
- store_artifacts:
path: ./reports/junit

3
.dockerignore Normal file
View File

@ -0,0 +1,3 @@
node_modules
.git
Dockerfile

View File

@ -1,31 +0,0 @@
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["@typescript-eslint", "react"],
"rules": {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-non-null-assertion": "off"
},
"overrides": [
{
"files": "./stories/*.tsx",
"rules": {
"react/display-name": "off",
"@typescript-eslint/no-unused-vars": "off"
}
}
],
"settings": {
"react": {
"version": "detect"
}
}
}

View File

@ -1,30 +0,0 @@
version: 2
updates:
# prod dependencies
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "saturday"
# ignore all non-security updates: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#open-pull-requests-limit
open-pull-requests-limit: 0
labels:
- type/dependencies
- javascript
commit-message:
prefix: chore(deps)
prefix-development: chore(deps-dev)
# build / CI dependencies
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "saturday"
# ignore all non-security updates: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#open-pull-requests-limit
open-pull-requests-limit: 0
labels:
- type/dependencies
- github_actions
commit-message:
prefix: chore(deps)

View File

@ -1,4 +1,4 @@
name: Build Storybook Docs v2 name: Build Storybook Docs
on: on:
push: push:
@ -7,9 +7,9 @@ jobs:
build-docs: build-docs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
- uses: actions/setup-node@v4 - uses: actions/setup-node@v1
with: with:
node-version-file: ".nvmrc" node-version: 12
- run: yarn install - run: yarn --cwd v2 install
- run: yarn build-v2 - run: yarn --cwd v2 build-storybook-docs

View File

@ -1,22 +0,0 @@
name: Build v1
on:
push:
pull_request:
branches:
- "master"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
- run: yarn install
- run: yarn deduplicate
- run: yarn build
- run: yarn lint
- run: yarn tsc -p tsconfig.json
- run: yarn test --ci --runInBand

View File

@ -1,28 +0,0 @@
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions
name: Approve and enable auto-merge for dependabot
on: pull_request
permissions:
pull-requests: write
contents: write
jobs:
review:
runs-on: ubuntu-latest
if: ${{ github.actor == 'dependabot[bot]' }}
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v1.6.0
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Approve PR
run: gh pr review --approve "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
- name: Enable auto-merge for Dependabot PRs
run: gh pr merge --auto --squash "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

View File

@ -1,18 +0,0 @@
name: Release NPM package v1
on:
push:
tags:
- v1.*
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}

View File

@ -1,4 +1,4 @@
name: Release NPM package v2 name: Release NPM package
on: on:
workflow_dispatch: workflow_dispatch:
@ -11,12 +11,12 @@ jobs:
publish: publish:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
with: with:
ref: ${{ github.event.inputs.tag }} ref: ${{ github.event.inputs.tag }}
- uses: actions/setup-node@v4 - uses: actions/setup-node@v1
with: with:
node-version-file: ".nvmrc" node-version: 12
- run: cd v2 && npm publish - run: cd v2 && npm publish
env: env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}

View File

@ -1,41 +0,0 @@
# https://github.com/actions/stale
name: Mark stale issues and pull requests
on:
schedule:
- cron: '0 2 * * *' # once a day at 2am
permissions:
contents: read
jobs:
stale:
permissions:
issues: write # for commenting on an issue and editing labels
pull-requests: write # for commenting on a PR and editing labels
runs-on: ubuntu-latest
steps:
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
# timing
days-before-stale: 14 # 2 weeks of inactivity
days-before-close: 14 # 2 more weeks of inactivity
# labels to watch for, add, and remove
only-labels: 'problem/more information needed' # only mark issues/PRs as stale if they have this label
labels-to-remove-when-unstale: 'problem/more information needed' # remove label when unstale -- should be manually added back if information is insufficient
stale-issue-label: 'problem/stale'
stale-pr-label: 'problem/stale'
# automated messages to issue/PR authors
stale-issue-message: >
This issue has been automatically marked as stale because it has not had recent activity and needs more information.
It will be closed if no further activity occurs.
stale-pr-message: >
This PR has been automatically marked as stale because it has not had recent activity and needs further changes.
It will be closed if no further activity occurs.
close-issue-message: >
This issue has been closed due to inactivity and lack of information.
If you still encounter this issue, please add the requested information and re-open.
close-pr-message:
This PR has been closed due to inactivity and lack of changes.
If you would like to still work on this PR, please address the review comments and re-open.

2
.gitignore vendored
View File

@ -2,8 +2,6 @@ node_modules
dist dist
bundle bundle
.vscode .vscode
.idea
*.log
coverage/ coverage/
.DS_STORE .DS_STORE
docs/ docs/

3
.jshintrc Normal file
View File

@ -0,0 +1,3 @@
{
"esversion": 6
}

2
.nvmrc
View File

@ -1 +1 @@
v20 v12.18.4

9
.storybook/config.js Normal file
View File

@ -0,0 +1,9 @@
import { configure } from '@storybook/react';
// automatically import all files ending in *.stories.js
const req = require.context('../stories', true, /.stories.tsx$/);
function loadStories() {
req.keys().forEach((filename) => req(filename));
}
configure(loadStories, module);

View File

@ -1,21 +0,0 @@
const path = require('path');
module.exports = {
stories: ['../stories/*.stories.tsx'],
addons: ['@storybook/addon-essentials'],
typescript: {
check: false, // typecheck separately
reactDocgen: false, // substantially improves performance: https://github.com/storybookjs/storybook/issues/22164#issuecomment-1603627308
},
webpackFinal: async (config, {configType}) => {
config.devtool = false; // perf per: https://github.com/storybookjs/storybook/issues/19736#issuecomment-1478103817
config.module.rules.push({
test: /\.scss$/,
exclude: /node_modules/,
include: path.resolve(__dirname, '../'),
sideEffects: true, // get side-effect styles to load per: https://github.com/storybookjs/storybook/issues/4690#issuecomment-435909433
loader: 'style-loader!raw-loader!sass-loader'
});
return config;
},
};

View File

@ -1 +0,0 @@
// import '../src/styles/main.scss'; -- this seems to not work and also makes the Storybook freeze for multiple minutes 😕

View File

@ -0,0 +1,26 @@
const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = ({config}) => {
config.module.rules = [{
test: /\.(ts|tsx)$/,
loader: `ts-loader?configFile=${path.resolve('./stories/tsconfig.json')}`
}, {
test: /\.scss$/,
exclude: /node_modules/,
loader: 'style-loader!raw-loader!sass-loader'
}, {
test: /\.css$/,
loader: 'style-loader!raw-loader'
}];
config.resolve = {
extensions: ['.ts', '.tsx', '.js', '.json']
};
config.plugins.push(new CopyWebpackPlugin([{
from: 'src/assets', to: 'assets'
}, {
from: 'node_modules/@fortawesome/fontawesome-free/webfonts', to: 'assets/fonts'
}]));
return config;
};

6
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,6 @@
Use the storybook to test components:
~~~
yarn storybook
~~~

View File

@ -1,35 +1,11 @@
# Argo UI Components # Argo UI Components
<img src="https://github.com/argoproj/argo-ui/blob/master/src/assets/images/logo.png?raw=true" alt="Argo Image" height="200px"> ![Argo Image](https://github.com/argoproj/argo/blob/master/argo.png?raw=true)
Set of React components used by [Argo Workflows](https://github.com/argoproj/argo-workflows), [Argo CD](https://github.com/argoproj/argo-cd), and [Argo Rollouts](https://github.com/argoproj/argo-rollouts). Set of React components used by https://github.com/argoproj/argo and https://github.com/argoproj/argo-cd
## Build & Run ## Build & Run
1. Install Toolset: [NodeJS](https://nodejs.org/en/download/) and [Yarn v1](https://classic.yarnpkg.com/en/docs) 1. Install Toolset: [NodeJS](https://nodejs.org/en/download/) and [Yarn](https://yarnpkg.com)
1. Install Dependencies: run `yarn install` 2. Install Dependencies: From your command line, navigate to the argo-ui directory and run `yarn install` to install dependencies.
1. Run: `yarn start` - starts the [Storybook v6](https://storybook.js.org/docs/6.5/get-started/install) dev server 3. Run: `yarn start` - starts https://storybook.js.org/ dev server
## Local Development
To test your changes locally against Argo CD or another Argo project, we recommend using [`yalc`](https://github.com/wclr/yalc).
First, install `yalc`:
```sh
npm i -g yalc
```
Next, in your local `argo-ui` directory, run
```sh
yalc publish
```
Finally, in your local `argo-cd/ui` directory, run
```sh
yalc add argo-ui
```
Your local changes to the `argo-ui` package will now be seen by your local `argo-cd`.

View File

@ -11,7 +11,7 @@ module.exports = {
setupFilesAfterEnv: ['<rootDir>src/setupTests.ts'], setupFilesAfterEnv: ['<rootDir>src/setupTests.ts'],
globals: { globals: {
'ts-jest': { 'ts-jest': {
tsconfig: '<rootDir>/tsconfig.json' tsconfig: '<rootDir>/src/tsconfig.json'
} }
} }
}; };

View File

@ -3,96 +3,98 @@
"version": "1.0.0", "version": "1.0.0",
"main": "./src/index.ts", "main": "./src/index.ts",
"types": "./src/index.ts", "types": "./src/index.ts",
"license": "Apache-2.0", "license": "MIT",
"files": [ "files": [
"src", "src"
"v2"
], ],
"scripts": { "scripts": {
"lint": "eslint --ext .tsx .", "build": "build-storybook -o ./dist/storybook",
"deduplicate": "yarn-deduplicate -s fewer yarn.lock", "lint": "tslint -p ./src",
"test": "jest", "test": "jest",
"utils:icons": "rm -f src/assets/fonts/* && node ./scripts/icons/generator.js", "utils:icons": "rm -f src/assets/fonts/* && node ./scripts/icons/generator.js",
"start": "NODE_OPTIONS='--openssl-legacy-provider' start-storybook -p 6006", "start": "start-storybook -p 6006"
"build": "NODE_OPTIONS='--openssl-legacy-provider' build-storybook -o ./dist/storybook",
"start-v2": "NODE_OPTIONS='--openssl-legacy-provider' start-storybook -c ./v2/.storybook -s ./v2/.storybook/images -p 6006",
"build-v2": "NODE_OPTIONS='--openssl-legacy-provider' build-storybook -o ./dist/storybook-v2 -c ./v2/.storybook"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.2.1", "@fortawesome/fontawesome-free": "^5.8.1",
"@tippy.js/react": "^3.1.1", "@tippy.js/react": "^2.1.2",
"classnames": "^2.2.6", "@types/react-autocomplete": "^1.8.5",
"core-js": "^3.32.1", "@types/react-form": "^2.16.1",
"@types/react-helmet": "^6.1.0",
"classnames": "^2.2.5",
"foundation-sites": "^6.4.3", "foundation-sites": "^6.4.3",
"history": "^4.10.1", "history": "^4.7.2",
"prop-types": "^15.8.1", "moment": "^2.20.1",
"react-autocomplete": "1.8.1", "prop-types": "^15.6.0",
"react-autocomplete": "^1.8.1",
"react-form": "^2.16.0", "react-form": "^2.16.0",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-router-dom": "^4.2.2", "react-router-dom": "^4.2.2",
"react-toastify": "9.0.3", "react-toastify": "^5.0.1",
"rxjs": "^7.8.1", "rxjs": "^6.6.6",
"typescript": "^4.9.5", "typescript": "^4.0.3",
"uuid": "^9.0.0", "xterm": "2.4.0"
"xterm": "^4.19.0",
"xterm-addon-fit": "^0.5.0"
}, },
"peerDependencies": { "peerDependencies": {
"@types/react": "^16.8.5",
"react": "^16.9.3", "react": "^16.9.3",
"react-dom": "^16.9.3" "react-dom": "^16.9.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.21.3", "@babel/core": "^7.11.6",
"@storybook/addon-actions": "6.5.0-beta.1", "@dump247/storybook-state": "^1.5.0",
"@storybook/addon-controls": "6.5.0-beta.1", "@storybook/addon-actions": "^6.0.22",
"@storybook/addon-essentials": "6.5.0-beta.1", "@storybook/addon-links": "^6.0.22",
"@storybook/addon-links": "6.5.0-beta.1", "@storybook/addons": "^6.0.22",
"@storybook/addons": "6.5.0-beta.1", "@storybook/react": "^6.0.22",
"@storybook/react": "6.5.0-beta.1", "@types/chai": "^4.1.2",
"@types/classnames": "^2.3.1", "@types/classnames": "^2.2.3",
"@types/deep-equal": "^1.0.1", "@types/deep-equal": "^1.0.1",
"@types/enzyme": "^3.10.12", "@types/enzyme": "^3.10.8",
"@types/enzyme-adapter-react-16": "^1.0.6", "@types/enzyme-adapter-react-16": "^1.0.6",
"@types/history": "^4.7.8", "@types/history": "^4.7.8",
"@types/jest": "^26.0.15", "@types/prop-types": "^15.5.2",
"@types/node": "^18.15.3", "@types/react": "^16.9.3",
"@types/prop-types": "^15.7.5",
"@types/react": "^16.8.5",
"@types/react-autocomplete": "^1.8.4",
"@types/react-dom": "^16.9.3", "@types/react-dom": "^16.9.3",
"@types/react-form": "^2.16.1", "@types/react-router-dom": "^4.2.3",
"@types/react-helmet": "^6.1.6", "@types/react-test-renderer": "^16.9.3",
"@types/react-router-dom": "^4.2.2", "@types/storybook__addon-actions": "^3.0.2",
"@types/uuid": "^9.0.3", "@types/storybook__addon-links": "^3.3.0",
"@typescript-eslint/eslint-plugin": "^5.36.1", "@types/storybook__react": "^3.0.7",
"@typescript-eslint/parser": "^5.36.1", "@types/yamljs": "^0.2.30",
"babel-loader": "^8.2.5", "chai": "^4.1.2",
"copy-webpack-plugin": "^4.3.1", "copy-webpack-plugin": "^4.3.1",
"css-loader": "^3.6.0", "copyfiles": "^1.2.0",
"enzyme": "^3.11.0", "enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.7", "enzyme-adapter-react-16": "^1.15.5",
"enzyme-to-json": "^3.6.2", "enzyme-to-json": "^3.6.1",
"eslint": "^8.23.0", "foreman": "^3.0.1",
"eslint-plugin-react": "^7.31.1", "glob": "^7.1.2",
"glob": "^8.0.3", "html-webpack-plugin": "^3.2.0",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"jest": "^26.6.3", "jest": "^26.6.2",
"raw-loader": "^4.0.2", "jscs": "^3.0.7",
"node-sass": "^4.12.0",
"nodemon": "^1.14.11",
"raw-loader": "^0.5.1",
"react": "^16.9.3", "react": "^16.9.3",
"react-dom": "^16.9.3", "react-dom": "^16.9.3",
"sass": "^1.55.0", "react-hot-loader": "^3.1.3",
"sass-loader": "^v10.1.0", "react-test-renderer": "^16.9.3",
"storybook": "6.5.0-beta.1", "sass-loader": "^6.0.6",
"style-loader": "^2.0.0", "source-map-loader": "^0.2.3",
"ts-jest": "^26.5.6", "style-loader": "^0.20.1",
"ts-node": "^10.9.1", "ts-jest": "^26.4.3",
"ts-loader": "^8.0.4",
"ts-node": "^4.1.0",
"tslint": "^5.9.1",
"tslint-react": "^3.4.0",
"webfonts-generator": "^0.4.0", "webfonts-generator": "^0.4.0",
"webpack": "^4.46.0", "webpack": "^4.44.2",
"yarn-deduplicate": "^6.0.2" "webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"
}, },
"resolutions": { "resolutions": {
"@types/react": "^16.8.5", "@types/react": "16.9.3",
"@types/node": "14.11.2" "@types/node": "14.11.2",
"@types/history": "4.7.8"
} }
} }

Binary file not shown.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Binary file not shown.

View File

@ -1,4 +1,4 @@
import {default as classNames} from 'classnames'; import * as classNames from 'classnames';
import * as React from 'react'; import * as React from 'react';
import * as ReactForm from 'react-form'; import * as ReactForm from 'react-form';
@ -18,7 +18,7 @@ export const AutocompleteField = ReactForm.FormField((props: AutocompleteProps &
}} }}
inputProps={{ inputProps={{
className: props.className, className: props.className,
style: {borderBottom: 'none', position: 'unset'}, style: {borderBottom: 'none'},
}} }}
value={value} value={value}
renderInput={(inputProps) => ( renderInput={(inputProps) => (

View File

@ -1,7 +0,0 @@
.autocomplete {
&__items__item {
&:last-child {
border-bottom: none;
}
}
}

View File

@ -1,9 +1,7 @@
import {default as classNames} from 'classnames'; import * as classNames from 'classnames';
import {CSSProperties, ReactNode} from 'react';
import * as React from 'react'; import * as React from 'react';
import ReactAutocomplete from 'react-autocomplete'; import * as ReactAutocomplete from 'react-autocomplete';
require('./autocomplete.scss');
export interface AutocompleteApi { export interface AutocompleteApi {
refresh(): any; refresh(): any;
} }
@ -25,8 +23,6 @@ export interface AutocompleteProps {
autoCompleteRef?: (api: AutocompleteApi) => any; autoCompleteRef?: (api: AutocompleteApi) => any;
filterSuggestions?: boolean; filterSuggestions?: boolean;
qeid?: string; qeid?: string;
/** @default true */ // per https://github.com/reactjs/react-autocomplete/blob/41388f7d7760bf6cf38e7946e43d4fddd9c7c176/lib/Autocomplete.js#L188
autoHighlight?: ReactAutocomplete.Props['autoHighlight'];
} }
export const Autocomplete = (props: AutocompleteProps) => { export const Autocomplete = (props: AutocompleteProps) => {
@ -59,7 +55,7 @@ export const Autocomplete = (props: AutocompleteProps) => {
wrapperProps.className = classNames('select', wrapperProps.className); wrapperProps.className = classNames('select', wrapperProps.className);
return ( return (
<ReactAutocomplete <ReactAutocomplete
autoHighlight={props.autoHighlight} autoHighlight={true}
ref={(el: any) => { ref={(el: any) => {
if (el) { if (el) {
if (el.refs.input) { if (el.refs.input) {
@ -109,16 +105,16 @@ export const Autocomplete = (props: AutocompleteProps) => {
shouldItemRender={(item: AutocompleteOption, val: string) => { shouldItemRender={(item: AutocompleteOption, val: string) => {
return !props.filterSuggestions || item.label.toLowerCase().includes(val.toLowerCase()); return !props.filterSuggestions || item.label.toLowerCase().includes(val.toLowerCase());
}} }}
renderMenu={function(menuItems: ReactNode[], _: string, style: CSSProperties) { renderMenu={function(menuItems, _, style) {
if (menuItems.length === 0) { if (menuItems.length === 0) {
return <div style={{display: 'none'}} />; return <div style={{display: 'none'}} />;
} }
return <div style={{...style, ...this.menuStyle, background: 'white', zIndex: 20, maxHeight: '20em'}}>{menuItems}</div>; return <div style={{...style, ...this.menuStyle, background: 'white', zIndex: 10, maxHeight: '20em'}} children={menuItems} />;
}} }}
getItemValue={(item: any) => item.label} getItemValue={(item) => item.label}
items={items} items={items}
value={props.value} value={props.value}
renderItem={(item: any, isSelected: boolean) => ( renderItem={(item, isSelected) => (
<div className={classNames('select__option', {selected: isSelected})} key={item.label}> <div className={classNames('select__option', {selected: isSelected})} key={item.label}>
{(props.renderItem && props.renderItem(item)) || item.label} {(props.renderItem && props.renderItem(item)) || item.label}
</div> </div>

View File

@ -31,7 +31,7 @@ interface LoaderState<TInput, TResult> {
inputChanged: boolean; inputChanged: boolean;
} }
export class DataLoader<D = any, I = undefined> extends React.Component<LoaderProps<I, D> | LoaderPropsNoInput<D>, LoaderState<I, D>> { export class DataLoader<D = {}, I = undefined> extends React.Component<LoaderProps<I, D> | LoaderPropsNoInput<D>, LoaderState<I, D>> {
public static contextTypes = { public static contextTypes = {
router: PropTypes.object, router: PropTypes.object,
apis: PropTypes.object, apis: PropTypes.object,

View File

@ -1,4 +1,4 @@
import {default as classNames} from 'classnames'; import * as classNames from 'classnames';
import * as React from 'react'; import * as React from 'react';
import * as ReactDOM from 'react-dom'; import * as ReactDOM from 'react-dom';
import { BehaviorSubject, fromEvent, merge, Subscription } from 'rxjs'; import { BehaviorSubject, fromEvent, merge, Subscription } from 'rxjs';
@ -9,7 +9,6 @@ export interface DropDownProps {
anchor: React.ComponentType; anchor: React.ComponentType;
children: React.ReactNode | (() => React.ReactNode); children: React.ReactNode | (() => React.ReactNode);
qeId?: string; qeId?: string;
onOpenStateChange?: (open: boolean) => void;
} }
export interface DropDownState { export interface DropDownState {
@ -59,12 +58,11 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> {
); );
} }
public UNSAFE_componentWillMount() { public componentWillMount() {
this.subscriptions = [merge( this.subscriptions = [merge(
dropDownOpened.pipe(filter((dropdown) => dropdown !== this)), dropDownOpened.pipe(filter((dropdown) => dropdown !== this)),
fromEvent(document, 'click').pipe(filter((event: Event) => { fromEvent(document, 'click').pipe(filter((event: Event) => {
const targetAttached = (event.target as Node).parentElement; return this.content && this.state.opened && !this.content.contains(event.target as Node) && !this.el.contains(event.target as Node);
return targetAttached && this.content && this.state.opened && !this.content.contains(event.target as Node) && !this.el.contains(event.target as Node);
})), })),
).subscribe(() => { ).subscribe(() => {
this.close(); this.close();
@ -82,9 +80,6 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> {
public close() { public close() {
this.setState({ opened: false }); this.setState({ opened: false });
if (this.props.onOpenStateChange) {
this.props.onOpenStateChange(false);
}
} }
private refreshState() { private refreshState() {
@ -117,8 +112,6 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> {
const newState = this.refreshState(); const newState = this.refreshState();
newState.opened = true; newState.opened = true;
this.setState(newState); this.setState(newState);
if (this.props.onOpenStateChange) { dropDownOpened.next(this);
this.props.onOpenStateChange(true);
}
} }
} }

View File

@ -1,12 +1,20 @@
import * as moment from 'moment';
import * as React from 'react'; import * as React from 'react';
import { formatDuration } from '../../v2'; export const Duration = (props: {durationMs: number, allowNewLines?: boolean}) => {
const momentTimeStart = moment.utc(0);
const momentTime = moment.utc(props.durationMs * 1000);
const duration = moment.duration(momentTime.diff(momentTimeStart));
let formattedTime = '';
/** if (momentTime.diff(momentTimeStart, 'hours') === 0) {
* Output a string duration from a number of seconds formattedTime = ('0' + duration.minutes()).slice(-2) + ':' + ('0' + duration.seconds()).slice(-2) + ' min';
* } else {
* @param {number} props.durationS - The number of seconds. if (momentTime.diff(momentTimeStart, 'days') > 0) {
*/ formattedTime += momentTime.diff(momentTimeStart, 'days') + ' days' + (props.allowNewLines ? '<br>' : ' ');
export function Duration(props: {durationS: number}) { }
return <span>{formatDuration(props.durationS, 2)}</span>;
} formattedTime += ('0' + duration.hours()).slice(-2) + ':' + ('0' + duration.minutes()).slice(-2) + ' hours';
}
return <span dangerouslySetInnerHTML={{__html: formattedTime}}/>;
};

View File

@ -1,10 +1,10 @@
import {default as classNames} from 'classnames'; import * as classNames from 'classnames';
import * as React from 'react'; import * as React from 'react';
import * as ReactForm from 'react-form'; import * as ReactForm from 'react-form';
import { Select as ArgoSelect, SelectOption, SelectProps } from '../select/select'; import { Select as ArgoSelect, SelectOption, SelectProps } from '../select/select';
import { v1 as uuid } from 'uuid'; const uuid = require('uuid/v1');
require('./form-field.scss'); require('./form-field.scss');

View File

@ -2,13 +2,13 @@ require('../styles/main.scss');
export { Utils } from './utils'; export { Utils } from './utils';
export { Layout } from './layout/layout'; export { Layout } from './layout/layout';
export { Page, PageContext, type PageContextProps } from './page/page'; export { Page, PageContext, PageContextProps } from './page/page';
export { MockupList } from './mockup-list/mockup-list'; export { MockupList } from './mockup-list/mockup-list';
export { DropDown } from './dropdown/dropdown'; export { DropDown } from './dropdown/dropdown';
export { DropDownMenu, type DropDownMenuProps, type MenuItem } from './dropdown-menu'; export { DropDownMenu, DropDownMenuProps, MenuItem } from './dropdown-menu';
export { Checkbox } from './checkbox'; export { Checkbox } from './checkbox';
export { type TopBarProps, type Toolbar, type TopBarFilter } from './top-bar/top-bar'; export { TopBarProps, Toolbar, TopBarFilter } from './top-bar/top-bar';
export { type Tab, Tabs } from './tabs/tabs'; export { Tab, Tabs } from './tabs/tabs';
export { Duration } from './duration'; export { Duration } from './duration';
export { SlidingPanel } from './sliding-panel/sliding-panel'; export { SlidingPanel } from './sliding-panel/sliding-panel';
export { LogsViewer } from './logs-viewer/logs-viewer'; export { LogsViewer } from './logs-viewer/logs-viewer';
@ -16,7 +16,7 @@ export * from './notifications/notifications';
export * from './notifications/notification-manager'; export * from './notifications/notification-manager';
export * from './popup/popup'; export * from './popup/popup';
export * from './popup/popup-manager'; export * from './popup/popup-manager';
export { Select, type SelectOption, type SelectProps } from './select/select'; export { Select, SelectOption, SelectProps } from './select/select';
export { HelpIcon } from './help-icon/help-icon'; export { HelpIcon } from './help-icon/help-icon';
export { Tooltip } from './tooltip/tooltip'; export { Tooltip } from './tooltip/tooltip';
export * from './ticker'; export * from './ticker';

View File

@ -1,15 +1,9 @@
@import '../../styles/config'; @import '../../styles/config';
@import '../../styles/theme';
.layout { .layout {
overflow: hidden; overflow: hidden;
@include themify($themes) {
background-color: themed('background-1');
}
&__loader { &__loader {
@include themify($themes) { background-color: rgba($argo-color-gray-7, 0.4);
background-color: themed('layout-loader-bg');
}
position: fixed; position: fixed;
left: 0; left: 0;
top: 0; top: 0;

View File

@ -1,21 +1,17 @@
import * as React from 'react'; import * as React from 'react';
import {NavBar, NavBarStyle} from '../nav-bar/nav-bar'; import { NavBar } from '../nav-bar/nav-bar';
require('./layout.scss'); require('./layout.scss');
export interface LayoutProps { export interface LayoutProps {
navItems: Array<{ path: string; iconClassName: string; title: string; }>; navItems: Array<{ path: string; iconClassName: string; title: string; }>;
version?: () => React.ReactElement; version?: () => React.ReactElement;
navBarStyle?: NavBarStyle;
theme?: string;
children?: React.ReactNode; children?: React.ReactNode;
} }
export const Layout = (props: LayoutProps) => ( export const Layout = (props: LayoutProps) => (
<div className={props.theme ? 'theme-' + props.theme : 'theme-light'}> <div className='layout'>
<div className='layout'> <NavBar items={props.navItems} version={props.version}/>
<NavBar items={props.navItems} version={props.version} style={props.navBarStyle} />
{props.children} {props.children}
</div>
</div> </div>
); );

View File

@ -1,5 +1,5 @@
@import '../../styles/config'; @import '../../styles/config';
@import 'node_modules/xterm/css/xterm'; @import 'node_modules/xterm/dist/xterm';
.logs-viewer { .logs-viewer {
font: normal 13px/1.2 'Courier', sans-serif; font: normal 13px/1.2 'Courier', sans-serif;
@ -11,3 +11,16 @@
height: 100%; height: 100%;
} }
} }
.xterm-theme-ax {
background-color: transparent;
color: $argo-color-gray-7;
.xterm-viewport {
background-color: transparent;
}
.terminal-cursor {
background-color: transparent !important;
}
}

View File

@ -1,7 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit'; const Terminal = require('xterm');
Terminal.loadAddon('fit');
require('./logs-viewer.scss'); require('./logs-viewer.scss');
@ -16,43 +17,27 @@ export interface LogsViewerProps {
} }
export class LogsViewer extends React.Component<LogsViewerProps> { export class LogsViewer extends React.Component<LogsViewerProps> {
private terminal: Terminal; private terminal: any;
private fitAddon: FitAddon;
private subscription: Subscription | null = null; private subscription: Subscription | null = null;
constructor(props: LogsViewerProps) { constructor(props: LogsViewerProps) {
super(props); super(props);
} }
public UNSAFE_componentWillReceiveProps(nextProps: LogsViewerProps) { public componentWillReceiveProps(nextProps: LogsViewerProps) {
if (this.props.source.key !== nextProps.source.key) { if (this.props.source.key !== nextProps.source.key) {
this.refresh(nextProps.source); this.refresh(nextProps.source);
} }
} }
public initTerminal(container: HTMLElement) { public initTerminal(container: HTMLElement) {
this.fitAddon = new FitAddon();
this.terminal = new Terminal({ this.terminal = new Terminal({
scrollback: 99999, scrollback: 99999,
allowTransparency: true, theme: 'ax',
theme: {
background: 'transparent',
foreground: '#495763',
},
}); });
this.terminal.loadAddon(this.fitAddon);
this.terminal.open(container);
this.fitAddon.fit();
// handle Ctrl+C for copying logs
this.terminal.attachCustomKeyEventHandler((ev) => {
if (ev.ctrlKey && ev.code === "KeyC" && ev.type === "keydown") {
const selection = this.terminal.getSelection();
if (!selection) return true;
navigator.clipboard?.writeText(selection); this.terminal.open(container);
return false this.terminal.fit();
}
});
} }
public componentDidMount() { public componentDidMount() {
@ -71,14 +56,12 @@ export class LogsViewer extends React.Component<LogsViewerProps> {
); );
} }
public shouldComponentUpdate() { public shouldComponentUpdate(prevProps: LogsViewerProps) {
return false; return false;
} }
private refresh(source: LogsSource) { private refresh(source: LogsSource) {
if (this.terminal) { this.terminal.reset();
this.terminal.reset();
}
this.ensureUnsubscribed(); this.ensureUnsubscribed();
const onLoadComplete = () => { const onLoadComplete = () => {
if (source.shouldRepeat()) { if (source.shouldRepeat()) {

View File

@ -1,4 +1,4 @@
import {default as classNames} from 'classnames'; import * as classNames from 'classnames';
import * as PropTypes from 'prop-types'; import * as PropTypes from 'prop-types';
import * as React from 'react'; import * as React from 'react';
@ -10,11 +10,6 @@ require('./nav-bar.scss');
export interface NavBarProps { export interface NavBarProps {
items: Array<{ path: string; iconClassName: string; title: string; }>; items: Array<{ path: string; iconClassName: string; title: string; }>;
version?: () => React.ReactElement; version?: () => React.ReactElement;
style?: NavBarStyle;
}
export interface NavBarStyle {
backgroundColor?: string;
} }
export function isActiveRoute(locationPath: string, path: string) { export function isActiveRoute(locationPath: string, path: string) {
@ -23,13 +18,10 @@ export function isActiveRoute(locationPath: string, path: string) {
export const NavBar: React.FunctionComponent<NavBarProps> = (props: NavBarProps, context: AppContext) => { export const NavBar: React.FunctionComponent<NavBarProps> = (props: NavBarProps, context: AppContext) => {
const locationPath = context.router.route.location.pathname; const locationPath = context.router.route.location.pathname;
const navBarStyle = {
...(props.style?.backgroundColor && {background: `linear-gradient(to bottom, ${props.style?.backgroundColor}, #999`}),
};
return ( return (
<div className={classNames('nav-bar', { <div className={classNames('nav-bar', {
'nav-bar--compact': (props.items || []).length >= 10, 'nav-bar--compact': (props.items || []).length >= 10,
})} style={navBarStyle}> })}>
<div className='nav-bar__logo'> <div className='nav-bar__logo'>
<img src='assets/images/logo.png' alt='Argo'/> <img src='assets/images/logo.png' alt='Argo'/>
<div className='nav-bar__version'>{props.version && props.version()}</div> <div className='nav-bar__version'>{props.version && props.version()}</div>

View File

@ -37,7 +37,9 @@ export class NavigationManager implements NavigationApi {
path = `${path}?${urlQuery}`; path = `${path}?${urlQuery}`;
} }
options = options || {}; options = options || {};
if (options.event && (options.event.metaKey || options.event.ctrlKey || options.event.button === 1)) { if (options.event && options.event.metaKey) {
window.open(path, '__target');
} else if (options.event && options.event.ctrlKey) {
window.open(path, '_blank'); window.open(path, '_blank');
} else { } else {
if (options.replace) { if (options.replace) {

View File

@ -47,10 +47,6 @@ export class Notifications extends React.Component<NotificationsProps> {
sel.removeAllRanges(); sel.removeAllRanges();
sel.addRange(range); sel.addRange(range);
} }
}} style={{
// fit long words by wrapping them instead of overflowing past the width
overflowWrap: 'break-word',
maxWidth: '240px'
}}> }}>
{next.content} {next.content}
</div> </div>

View File

@ -1,4 +1,4 @@
import {default as classNames} from 'classnames'; import * as classNames from 'classnames';
import * as React from 'react'; import * as React from 'react';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@ -12,8 +12,6 @@ require('./page.scss');
interface PageProps extends React.Props<any> { interface PageProps extends React.Props<any> {
title: string; title: string;
toolbar?: Toolbar | Observable<Toolbar>; toolbar?: Toolbar | Observable<Toolbar>;
topBarTitle?: string;
useTitleOnly?: boolean;
} }
export interface PageContextProps { export interface PageContextProps {
@ -34,7 +32,7 @@ export const Page = (props: PageProps) => {
<PageContext.Consumer> <PageContext.Consumer>
{(ctx) => { {(ctx) => {
let titleParts = [ctx.title]; let titleParts = [ctx.title];
if (!props.useTitleOnly && toolbar && toolbar.breadcrumbs && toolbar.breadcrumbs.length > 0) { if (toolbar && toolbar.breadcrumbs && toolbar.breadcrumbs.length > 0) {
titleParts = [toolbar.breadcrumbs.map((item) => item.title).reverse().join(' / ')].concat(titleParts); titleParts = [toolbar.breadcrumbs.map((item) => item.title).reverse().join(' / ')].concat(titleParts);
} else if (props.title) { } else if (props.title) {
titleParts = [props.title].concat(titleParts); titleParts = [props.title].concat(titleParts);
@ -47,7 +45,7 @@ export const Page = (props: PageProps) => {
}} }}
</PageContext.Consumer> </PageContext.Consumer>
<div className='page__top-bar'> <div className='page__top-bar'>
<TopBar title={props.topBarTitle ? props.topBarTitle : props.title} toolbar={toolbar}/> <TopBar title={props.title} toolbar={toolbar}/>
</div> </div>
</React.Fragment> </React.Fragment>
)} )}

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import { Form, FormApi, FormValues, RenderReturn, ValidateValuesFunction } from 'react-form'; import { Form, FormApi, FormValues, RenderReturn, ValidateValuesFunction } from 'react-form';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { PopupProps } from './popup'; import { PopupProps } from './popup';
export interface PopupApi { export interface PopupApi {
confirm(title: string, message: string | React.ComponentType): Promise<boolean>; confirm(title: string, message: string | React.ComponentType): Promise<boolean>;
@ -14,7 +14,7 @@ export interface PopupApi {
}, },
customIcon?: {name: string, color: string}, customIcon?: {name: string, color: string},
titleColor?: string, titleColor?: string,
defaultValues?: FormValues, defaultValues?: {},
): Promise<FormValues | null>; ): Promise<FormValues | null>;
} }
@ -41,8 +41,8 @@ export class PopupManager implements PopupApi {
content, content,
footer: ( footer: (
<div> <div>
<button qe-id='argo-popup-ok-button' className='argo-button argo-button--base' onClick={() => closeAndResolve(true)}>OK</button> <button qe-id='argo-popup-ok-button' className='argo-button argo-button--base' onClick={() => closeAndResolve(true)}>OK</button> <button
<button qe-id='argo-popup-cancel-button' className='argo-button argo-button--base-o' onClick={() => closeAndResolve(false)}>Cancel</button> qe-id='argo-popup-cancel-button' className='argo-button argo-button--base-o' onClick={() => closeAndResolve(false)}>Cancel</button>
</div> </div>
), ),
}); });
@ -58,7 +58,7 @@ export class PopupManager implements PopupApi {
}, },
customIcon?: { name: string, color: string }, customIcon?: { name: string, color: string },
titleColor?: string, titleColor?: string,
defaultValues?: FormValues, defaultValues?: {},
): Promise<FormValues | null> { ): Promise<FormValues | null> {
return new Promise((resolve) => { return new Promise((resolve) => {
const closeAndResolve = (result: FormValues | null) => { const closeAndResolve = (result: FormValues | null) => {
@ -98,8 +98,8 @@ export class PopupManager implements PopupApi {
), ),
footer: ( footer: (
<div> <div>
<button qe-id='prompt-popup-ok-button' className='argo-button argo-button--base' onClick={(e) => formApi.submitForm(e)}>OK</button> <button qe-id='prompt-popup-ok-button' className='argo-button argo-button--base' onClick={(e) => formApi.submitForm(e)}>OK</button> <button
<button qe-id='prompt-popup-cancel-button' className='argo-button argo-button--base-o' onClick={() => closeAndResolve(null)}>Cancel</button> qe-id='prompt-popup-cancel-button' className='argo-button argo-button--base-o' onClick={() => closeAndResolve(null)}>Cancel</button>
</div> </div>
), ),
}); });

View File

@ -1,5 +1,4 @@
@import '../../styles/config'; @import '../../styles/config';
@import '../../styles/theme';
$popup-border-radius: 8px; $popup-border-radius: 8px;
@ -10,17 +9,12 @@ $popup-border-radius: 8px;
width: 100%; width: 100%;
height: 100%; height: 100%;
z-index: 200; z-index: 200;
// Just in case the theme class is not added
background-color: rgba(222, 222, 222, 0.62); /*dim the background*/ background-color: rgba(222, 222, 222, 0.62); /*dim the background*/
@include themify($themes) { border: 1px solid $argo-color-gray-4;
background-color: themed('overlay');
}
.popup-container { .popup-container {
position: relative; position: relative;
@include themify($themes) { box-shadow: 3px 3px 20px #888888;
box-shadow: 3px 3px 20px themed('shadow');
}
margin: 0 auto; margin: 0 auto;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
@ -29,7 +23,6 @@ $popup-border-radius: 8px;
max-height: 600px; max-height: 600px;
z-index: 20; z-index: 20;
border-radius: $popup-border-radius; border-radius: $popup-border-radius;
overflow: auto;
&__header { &__header {
line-height: 60px; line-height: 60px;
@ -48,16 +41,12 @@ $popup-border-radius: 8px;
float: right; float: right;
position: absolute; position: absolute;
right: 20px; right: 20px;
top: 5px; top: 20px;
cursor: pointer; cursor: pointer;
} }
&__normal { &__normal {
// Just in case the theme class is not added
background-color: $argo-color-gray-2; background-color: $argo-color-gray-2;
@include themify($themes) {
background-color: themed('light-argo-gray-2');
}
} }
&__red { &__red {
@ -71,11 +60,7 @@ $popup-border-radius: 8px;
} }
&__footer { &__footer {
// Just in case the theme class is not added
background-color: $argo-color-gray-1; background-color: $argo-color-gray-1;
@include themify($themes) {
background-color: themed('background-2');
}
bottom: 0; bottom: 0;
padding-top: 20px; padding-top: 20px;
padding-left: 30px; padding-left: 30px;
@ -94,41 +79,28 @@ $popup-border-radius: 8px;
} }
&__body { &__body {
// Just in case the theme class is not added
background-color: $argo-color-gray-1; background-color: $argo-color-gray-1;
@include themify($themes) {
background-color: themed('background-2');
}
&__hasNoIcon { &__hasNoIcon {
padding-left: 30px; padding-left: 30px;
} }
h4, p, ul { h4, p {
margin-right: 10px; margin-right: 10px;
font-size: 15px;
word-wrap: break-word; word-wrap: break-word;
// Just in case the theme class is not added
color: $argo-color-gray-6;
@include themify($themes) {
color: themed('light-argo-gray-6');
}
} }
h4 { h4 {
margin-top: 40px; margin-top: 40px;
font-size: 15px;
font-weight: bold; font-weight: bold;
} }
p { p {
font-size: 15px;
margin-top: 10px; margin-top: 10px;
margin-bottom: 10px; margin-bottom: 10px;
} color: $argo-color-gray-6;
ul {
margin-top: 0px;
margin-bottom: 10px;
} }
} }

View File

@ -1,4 +1,4 @@
import {default as classNames} from 'classnames'; import * as classNames from 'classnames';
import * as React from 'react'; import * as React from 'react';
export interface BasePopupProps { export interface BasePopupProps {

View File

@ -1,5 +1,4 @@
@import '../../styles/config'; @import '../../styles/config';
@import '../../styles/theme';
.select { .select {
position: relative; position: relative;
@ -16,22 +15,18 @@
position: relative; position: relative;
padding: 8px 20px 8px 0; padding: 8px 20px 8px 0;
font-size: 15px; font-size: 15px;
border-bottom: 2px solid #ccc;
transition: border .2s; transition: border .2s;
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@include themify($themes) {
border-bottom: 2px solid themed('border');;
}
} }
&__value-arrow { &__value-arrow {
// right most, vertically centered
position: absolute; position: absolute;
top: 50%; top: 8px;
right: 0; right: 0;
transform: translateY(-50%);
} }
&__options { &__options {

View File

@ -1,4 +1,4 @@
import {default as classNames} from 'classnames'; import * as classNames from 'classnames';
import * as React from 'react'; import * as React from 'react';
import { fromEvent, Subscription } from 'rxjs'; import { fromEvent, Subscription } from 'rxjs';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';

View File

@ -1,7 +1,6 @@
@import 'node_modules/foundation-sites/scss/util/util'; @import 'node_modules/foundation-sites/scss/util/util';
@import '../../styles/config'; @import '../../styles/config';
@import '../../styles/theme';
$sliding-panel-header-height: 50px; $sliding-panel-header-height: 50px;
$sliding-panel-footer-height: 64px; $sliding-panel-footer-height: 64px;
@ -51,10 +50,7 @@ $sliding-panel-middle-width: 600px;
right: 0; right: 0;
bottom: 0; bottom: 0;
width: 80%; width: 80%;
@include themify($themes) { background-color: #fff;
background-color: themed('background-2');
color: themed('text-2');
}
transition: right .5s; transition: right .5s;
@include breakpoint(medium down) { @include breakpoint(medium down) {
@ -101,17 +97,13 @@ $sliding-panel-middle-width: 600px;
padding: 0 30px; padding: 0 30px;
line-height: $sliding-panel-header-height; line-height: $sliding-panel-header-height;
color: $argo-color-gray-5; color: $argo-color-gray-5;
@include themify($themes) { background-color: $argo-color-gray-2;
background-color: themed('light-argo-gray-2'); border-bottom: 1px solid #c6cfd1;
border-bottom: 1px solid themed('border');
}
font-weight: 500; font-weight: 500;
font-size: .925em; font-size: .925em;
.sliding-panel--off-canvas & { .sliding-panel--off-canvas & {
@include themify($themes) { background-color: $argo-color-gray-2;
background-color: themed('light-argo-gray-2');
}
} }
strong { strong {
@ -128,9 +120,8 @@ $sliding-panel-middle-width: 600px;
position: relative; position: relative;
height: 100%; height: 100%;
overflow: auto; overflow: auto;
@include themify($themes) { background-color: $argo-color-gray-2;
background-color: themed('light-argo-gray-2');
}
.sliding-panel:not(.sliding-panel--no-padding) & { .sliding-panel:not(.sliding-panel--no-padding) & {
padding: 30px; padding: 30px;
} }

View File

@ -1,6 +1,5 @@
import {default as classNames} from 'classnames'; import * as classNames from 'classnames';
import * as React from 'react'; import * as React from 'react';
import { Key, KeybindingContext, KeybindingProvider } from '../../../v2';
export interface SlidingPanelProps extends React.Props<any> { export interface SlidingPanelProps extends React.Props<any> {
isShown?: boolean; isShown?: boolean;
@ -16,76 +15,36 @@ export interface SlidingPanelProps extends React.Props<any> {
require('./sliding-panel.scss'); require('./sliding-panel.scss');
export const SlidingPanel = (props: SlidingPanelProps) => { export const SlidingPanel = (props: SlidingPanelProps) => (
return ( <div className={classNames('sliding-panel', {
<KeybindingProvider> 'sliding-panel--has-header': !!props.header,
<RenderSlidingPanel {...props} /> 'sliding-panel--has-footer': !!props.footer,
</KeybindingProvider> 'sliding-panel--is-narrow': props.isNarrow,
); 'sliding-panel--is-middle': props.isMiddle,
}; 'sliding-panel--opened': props.isShown,
'sliding-panel--no-padding': props.hasNoPadding,
const RenderSlidingPanel = (props: SlidingPanelProps) => { 'sliding-panel--off-canvas': props.offCanvas,
})}>
const {useKeybinding} = React.useContext(KeybindingContext); <div className='sliding-panel__wrapper'>
<button className='sliding-panel__close' aria-hidden='true' onClick={() => props.onClose && props.onClose()}>
const closeButtonRef = React.useRef(null); <span>
const bodyDivRef = React.useRef(null); <i className='argo-icon-close' aria-hidden='true'/>
const panelHeaderDivRef = React.useRef(null); </span>
const panelFooterDivRef = React.useRef(null); </button>
{props.header && (
React.useEffect(() => { <div className={classNames('sliding-panel__header', {'sliding-panel__header--close-btn-right-padding': props.hasCloseButton})}>
if (closeButtonRef && closeButtonRef.current) { {props.header}
closeButtonRef.current.focus();
}
}, []);
useKeybinding({
keys: Key.ESCAPE,
action: () => {
if (props.isShown && props.onClose) {
props.onClose();
return true;
}
return false;
},
combo: false,
target: [closeButtonRef, bodyDivRef, panelHeaderDivRef, panelFooterDivRef],
});
return (
<div className={classNames('sliding-panel', {
'sliding-panel--has-header': !!props.header,
'sliding-panel--has-footer': !!props.footer,
'sliding-panel--is-narrow': props.isNarrow,
'sliding-panel--is-middle': props.isMiddle,
'sliding-panel--opened': props.isShown,
'sliding-panel--no-padding': props.hasNoPadding,
'sliding-panel--off-canvas': props.offCanvas,
})}>
<div className='sliding-panel__wrapper'>
{props.isShown && (
<button autoFocus={true} className='sliding-panel__close' aria-hidden='true' onClick={() => props.onClose && props.onClose()} ref={closeButtonRef}>
<span>
<i className='argo-icon-close' aria-hidden='true'/>
</span>
</button>
)}
{props.header && (
<div className={classNames('sliding-panel__header', {'sliding-panel__header--close-btn-right-padding': props.hasCloseButton})}
ref={panelHeaderDivRef} tabIndex={-1}>
{props.header}
</div>
)}
<div className='sliding-panel__body' ref={bodyDivRef} tabIndex={-1}>
{props.children}
</div> </div>
{props.footer && ( )}
<div className='sliding-panel__footer' ref={panelFooterDivRef} tabIndex={-1}> <div className='sliding-panel__body'>
{props.footer} {props.children}
</div>
)}
</div> </div>
<div className='sliding-panel__outside' onClick={() => props.onClose && props.onClose()}/> {props.footer && (
<div className='sliding-panel__footer'>
{props.footer}
</div>
)}
</div> </div>
); <div className='sliding-panel__outside' onClick={() => props.onClose && props.onClose()}/>
}; </div>
);

View File

@ -1,6 +1,4 @@
@import '../../styles/config'; @import '../../styles/config';
@import '../../styles/theme';
.tabs { .tabs {
.fixed-width { .fixed-width {
@ -13,9 +11,7 @@
position: relative; position: relative;
z-index: 1; z-index: 1;
padding: 15px 20px 0; padding: 15px 20px 0;
@include themify($themes) { background-color: $white-color;
background-color: themed('background-2');
}
overflow: hidden; overflow: hidden;
list-style: none; list-style: none;
box-shadow: 0 1px 3px rgba(143,164,177,.3); box-shadow: 0 1px 3px rgba(143,164,177,.3);
@ -28,9 +24,7 @@
display: inline-block; display: inline-block;
margin: 0 16px; margin: 0 16px;
padding: 12px 36px; padding: 12px 36px;
@include themify($themes) { color: $argo-color-gray-6;
color: themed('light-argo-gray-6');
}
font-size: 13px; font-size: 13px;
font-weight: 500; font-weight: 500;
text-transform: uppercase; text-transform: uppercase;
@ -57,17 +51,11 @@
box-shadow: none; box-shadow: none;
border-bottom: 1px solid $argo-color-gray-4; border-bottom: 1px solid $argo-color-gray-4;
.theme-dark & {
border-color: $argo-color-gray-7;
}
a { a {
color: $argo-color-teal-5; color: $argo-color-teal-5;
&.active { &.active {
@include themify($themes) { color: $argo-color-gray-8;
color: themed('text-2');
}
} }
} }
} }

View File

@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import {default as classNames} from 'classnames'; import * as classNames from 'classnames';
export interface Tab { export interface Tab {
title: string; title: string;
@ -114,13 +114,7 @@ export class Tabs extends React.Component<TabsProps, TabsState> {
const el = parentEl.querySelector<HTMLElement>('.active'); const el = parentEl.querySelector<HTMLElement>('.active');
if (el) { if (el) {
const newIndicatorPosition = this.getIndicatorPosition(parentEl, el); this.setState({ indicatorPosition: this.getIndicatorPosition(parentEl, el) });
if (JSON.stringify(this.state.indicatorPosition) !== JSON.stringify(newIndicatorPosition)) {
this.setState({
indicatorPosition: this.getIndicatorPosition(parentEl, el),
});
}
} }
} }
} }

View File

@ -1,13 +1,14 @@
import * as moment from 'moment';
import * as React from 'react'; import * as React from 'react';
import {interval, Subscription} from 'rxjs'; import {interval, Subscription} from 'rxjs';
export class Ticker extends React.Component<{intervalMs?: number, disabled?: boolean, children?: ((time: Date) => React.ReactNode)}, {time: Date}> { export class Ticker extends React.Component<{intervalMs?: number, disabled?: boolean, children?: ((time: moment.Moment) => React.ReactNode)}, {time: moment.Moment}> {
private subscription: Subscription | null = null; private subscription: Subscription | null = null;
constructor(props: {intervalMs?: number, children?: ((time: Date) => React.ReactNode)}) { constructor(props: {intervalMs?: number, children?: ((time: moment.Moment) => React.ReactNode)}) {
super(props); super(props);
this.state = { time: new Date() }; this.state = { time: moment() };
this.ensureSubscribed(); this.ensureSubscribed();
} }
@ -27,7 +28,7 @@ export class Ticker extends React.Component<{intervalMs?: number, disabled?: boo
if (this.props.disabled) { if (this.props.disabled) {
this.ensureUnsubscribed(); this.ensureUnsubscribed();
} else if (!this.subscription) { } else if (!this.subscription) {
this.subscription = interval(this.props.intervalMs || 1000).subscribe(() => this.setState({ time: new Date() })); this.subscription = interval(this.props.intervalMs || 1000).subscribe(() => this.setState({ time: moment() }));
} }
} }

View File

@ -1,8 +1,4 @@
import Tippy, { TippyProps } from '@tippy.js/react'; import Tippy from '@tippy.js/react';
import * as React from 'react'; import * as React from 'react';
import 'tippy.js/dist/tippy.css';
import 'tippy.js/themes/light.css';
export const Tooltip = ( props: TippyProps ) => ( export const Tooltip = ( props: any ) => <Tippy animation='fade' arrow='true' {...props} />;
<Tippy animation='fade' appendTo={document.body} theme='light' interactive={true} {...props} />
);

View File

@ -1,19 +1,12 @@
@import '../../styles/config'; @import '../../styles/config';
@import 'node_modules/foundation-sites/scss/util/util'; @import 'node_modules/foundation-sites/scss/util/util';
@import '../../styles/theme';
.top-bar { .top-bar {
line-height: $top-bar-height; line-height: $top-bar-height;
@include themify($themes) { background: $white-color;
background: themed('background-2');
}
transition: right .5s; transition: right .5s;
border-bottom: 1px solid $argo-color-gray-2; border-bottom: 1px solid $argo-color-gray-2;
.theme-dark & {
border-color: $argo-color-gray-7;
}
&__left-side { &__left-side {
padding-left: 20px; padding-left: 20px;
white-space: nowrap; white-space: nowrap;
@ -28,9 +21,7 @@
float: left; float: left;
font-weight: 500; font-weight: 500;
font-size: .925em; font-size: .925em;
@include themify($themes) { color: $argo-color-teal-7;
color: themed('light-argo-teal-7');
}
text-transform: uppercase; text-transform: uppercase;
} }
@ -61,9 +52,7 @@
height: $top-bar-height; height: $top-bar-height;
font-weight: 500; font-weight: 500;
font-size: .8em; font-size: .8em;
@include themify($themes) { color: $argo-color-gray-8;
color: themed('text-2');
}
a { a {
display: inline-block; display: inline-block;

View File

@ -1,4 +1,4 @@
import {default as classNames} from 'classnames'; import * as classNames from 'classnames';
import * as React from 'react'; import * as React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@ -27,7 +27,7 @@ export interface ActionMenu {
export interface Toolbar { export interface Toolbar {
filter?: TopBarFilter<any>; filter?: TopBarFilter<any>;
breadcrumbs?: { title: string | React.ReactNode, path?: string; }[]; breadcrumbs?: { title: string, path?: string; }[];
tools?: React.ReactNode; tools?: React.ReactNode;
actionMenu?: ActionMenu; actionMenu?: ActionMenu;
} }
@ -68,17 +68,17 @@ const renderFilter = (filter: TopBarFilter<any>) => (
</DropDown> </DropDown>
); );
const renderBreadcrumbs = (breadcrumbs: { title: string | React.ReactNode, path?: string; }[]) => ( const renderBreadcrumbs = (breadcrumbs: { title: string, path?: string; }[]) => (
<div className='top-bar__breadcrumbs'> <div className='top-bar__breadcrumbs'>
{(breadcrumbs || []).map((breadcrumb, i) => { {(breadcrumbs || []).map((breadcrumb, i) => {
const nodes = []; const nodes = [];
if (i === breadcrumbs.length - 1) { if (i === breadcrumbs.length - 1) {
nodes.push(<span key={i} className='top-bar__breadcrumbs-last-item'>{breadcrumb.title}</span>); nodes.push(<span key={breadcrumb.title} className='top-bar__breadcrumbs-last-item'>{breadcrumb.title}</span>);
} else { } else {
nodes.push(<Link key={i} to={breadcrumb.path}> {breadcrumb.title} </Link>); nodes.push(<Link key={breadcrumb.title} to={breadcrumb.path}> {breadcrumb.title} </Link>);
} }
if (i < breadcrumbs.length - 1) { if (i < breadcrumbs.length - 1) {
nodes.push(<span key={`${i}_sep`} className='top-bar__sep'/>); nodes.push(<span key={`${breadcrumb.title}_sep`} className='top-bar__sep'/>);
} }
return nodes; return nodes;
})} })}

View File

@ -5,6 +5,45 @@ export function isPromise<T>(obj: any): obj is PromiseLike<T> {
} }
export const Utils = { export const Utils = {
getScrollParent(el: HTMLElement): HTMLElement {
const regex = /(auto|scroll)/;
while (el.parentNode) {
el = el.parentNode as HTMLElement;
const overflow = getComputedStyle(el, null).getPropertyValue('overflow') +
getComputedStyle(el, null).getPropertyValue('overflow-y') +
getComputedStyle(el, null).getPropertyValue('overflow-x');
if (regex.test(overflow)) {
return el;
}
}
return document.body;
},
scrollTo(element: HTMLElement, to: number, duration = 1000) {
function easeInOutQuad(t: number, b: number, c: number, d: number) {
t /= d / 2;
if (t < 1) {
return c / 2 * t * t + b;
}
t--;
return -c / 2 * ( t * ( t - 2 ) - 1) + b;
}
const start = element.scrollTop;
const change = to - start;
let currentTime = 0;
const increment = 20;
const animateScroll = () => {
currentTime += increment;
element.scrollTop = easeInOutQuad(currentTime, start, change, duration);
if (currentTime < duration) {
setTimeout(animateScroll, increment);
}
};
animateScroll();
},
toObservable<T>(val: T | Observable<T> | Promise<T>): Observable<T> { toObservable<T>(val: T | Observable<T> | Promise<T>): Observable<T> {
const observable = val as Observable<T>; const observable = val as Observable<T>;
if (observable && observable.subscribe && observable.forEach) { if (observable && observable.subscribe && observable.forEach) {

View File

@ -1,8 +1,6 @@
import * as H from 'history'; import * as H from 'history';
import { match } from 'react-router'; import { match} from 'react-router';
import { NotificationsApi, PopupApi } from './components';
import { NotificationsApi } from './components/notifications/notification-manager';
import { PopupApi } from './components/popup/popup-manager';
export interface AppContext { export interface AppContext {
router: { router: {

View File

@ -1,4 +1,4 @@
import {configure } from 'enzyme'; import {configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16'; import * as Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() }); configure({ adapter: new Adapter() });

View File

@ -14,10 +14,10 @@ $argo-icon-fonts-root: '/' !default;
@font-face { @font-face {
font-family: "argo-icon"; font-family: "argo-icon";
src: url($argo-icon-fonts-root + "argo-icon.eot?73a3124f147facf3e90960528bdfae73?#iefix") format("embedded-opentype"), src: url($argo-icon-fonts-root + "argo-icon.eot?af0f627a6b22b6f9f7b649206eee4263?#iefix") format("embedded-opentype"),
url($argo-icon-fonts-root + "argo-icon.woff?73a3124f147facf3e90960528bdfae73") format("woff"), url($argo-icon-fonts-root + "argo-icon.woff?af0f627a6b22b6f9f7b649206eee4263") format("woff"),
url($argo-icon-fonts-root + "argo-icon.ttf?73a3124f147facf3e90960528bdfae73") format("truetype"), url($argo-icon-fonts-root + "argo-icon.ttf?af0f627a6b22b6f9f7b649206eee4263") format("truetype"),
url($argo-icon-fonts-root + "argo-icon.svg?73a3124f147facf3e90960528bdfae73#argo-icon") format("svg"); url($argo-icon-fonts-root + "argo-icon.svg?af0f627a6b22b6f9f7b649206eee4263#argo-icon") format("svg");
} }
i { i {
@ -300,117 +300,114 @@ i {
.argo-icon-notification:before { .argo-icon-notification:before {
content: "\f14f"; content: "\f14f";
} }
.argo-icon-oci:before { .argo-icon-pencil:before {
content: "\f150"; content: "\f150";
} }
.argo-icon-pencil:before { .argo-icon-play-2:before {
content: "\f151"; content: "\f151";
} }
.argo-icon-play-2:before { .argo-icon-play:before {
content: "\f152"; content: "\f152";
} }
.argo-icon-play:before { .argo-icon-pod:before {
content: "\f153"; content: "\f153";
} }
.argo-icon-pod:before { .argo-icon-policies:before {
content: "\f154"; content: "\f154";
} }
.argo-icon-policies:before { .argo-icon-policy:before {
content: "\f155"; content: "\f155";
} }
.argo-icon-policy:before { .argo-icon-profile:before {
content: "\f156"; content: "\f156";
} }
.argo-icon-profile:before { .argo-icon-push:before {
content: "\f157"; content: "\f157";
} }
.argo-icon-push:before { .argo-icon-report-card:before {
content: "\f158"; content: "\f158";
} }
.argo-icon-report-card:before { .argo-icon-resubmit-failed:before {
content: "\f159"; content: "\f159";
} }
.argo-icon-resubmit-failed:before { .argo-icon-retry:before {
content: "\f15a"; content: "\f15a";
} }
.argo-icon-retry:before { .argo-icon-right-navigation-toolbar:before {
content: "\f15b"; content: "\f15b";
} }
.argo-icon-right-navigation-toolbar:before { .argo-icon-safe:before {
content: "\f15c"; content: "\f15c";
} }
.argo-icon-safe:before { .argo-icon-sales-channels:before {
content: "\f15d"; content: "\f15d";
} }
.argo-icon-sales-channels:before { .argo-icon-sample:before {
content: "\f15e"; content: "\f15e";
} }
.argo-icon-sample:before { .argo-icon-scale:before {
content: "\f15f"; content: "\f15f";
} }
.argo-icon-scale:before { .argo-icon-search:before {
content: "\f160"; content: "\f160";
} }
.argo-icon-search:before { .argo-icon-settings:before {
content: "\f161"; content: "\f161";
} }
.argo-icon-settings:before { .argo-icon-slack-02:before {
content: "\f162"; content: "\f162";
} }
.argo-icon-slack-02:before { .argo-icon-stop-property:before {
content: "\f163"; content: "\f163";
} }
.argo-icon-stop-property:before { .argo-icon-stop:before {
content: "\f164"; content: "\f164";
} }
.argo-icon-stop:before { .argo-icon-storageclass:before {
content: "\f165"; content: "\f165";
} }
.argo-icon-storageclass:before { .argo-icon-storageprovider:before {
content: "\f166"; content: "\f166";
} }
.argo-icon-storageprovider:before { .argo-icon-tag:before {
content: "\f167"; content: "\f167";
} }
.argo-icon-tag:before { .argo-icon-template:before {
content: "\f168"; content: "\f168";
} }
.argo-icon-template:before { .argo-icon-terminate:before {
content: "\f169"; content: "\f169";
} }
.argo-icon-terminate:before { .argo-icon-test:before {
content: "\f16a"; content: "\f16a";
} }
.argo-icon-test:before { .argo-icon-timeline:before {
content: "\f16b"; content: "\f16b";
} }
.argo-icon-timeline:before { .argo-icon-tools:before {
content: "\f16c"; content: "\f16c";
} }
.argo-icon-tools:before { .argo-icon-user-groups:before {
content: "\f16d"; content: "\f16d";
} }
.argo-icon-user-groups:before { .argo-icon-user-profile:before {
content: "\f16e"; content: "\f16e";
} }
.argo-icon-user-profile:before { .argo-icon-user:before {
content: "\f16f"; content: "\f16f";
} }
.argo-icon-user:before { .argo-icon-users:before {
content: "\f170"; content: "\f170";
} }
.argo-icon-users:before { .argo-icon-volume:before {
content: "\f171"; content: "\f171";
} }
.argo-icon-volume:before { .argo-icon-warning:before {
content: "\f172"; content: "\f172";
} }
.argo-icon-warning:before { .argo-icon-workflow:before {
content: "\f173"; content: "\f173";
} }
.argo-icon-workflow:before { .argo-icon-yaml:before {
content: "\f174"; content: "\f174";
} }
.argo-icon-yaml:before {
content: "\f175";
}

View File

@ -30,9 +30,7 @@ $argo-color-teal-7: #006F8A;
$argo-color-teal-8: #004C67; $argo-color-teal-8: #004C67;
$white-color: #ffffff; $white-color: #ffffff;
$dark-theme-background-1: #100f0f;
$dark-theme-background-2: #303237;
$dark-theme-sliding-panel: #28292a;
// Status colors // Status colors
$argo-failed-color: #E96D76; $argo-failed-color: #E96D76;
$argo-failed-color-dark: #c04b4f; $argo-failed-color-dark: #c04b4f;
@ -52,7 +50,6 @@ $argo-color-yellow: #FFD100;
$argo-running-color-dark: #378398; $argo-running-color-dark: #378398;
$argo-running-color-light: #02C4D3; $argo-running-color-light: #02C4D3;
$argo-running-color: #0DADEA; $argo-running-color: #0DADEA;
$argo-suspended-color: #766F94;
$argo-waiting-color-dark: $argo-color-gray-6; $argo-waiting-color-dark: $argo-color-gray-6;
$argo-waiting-color-light: $argo-color-gray-5; $argo-waiting-color-light: $argo-color-gray-5;

View File

@ -236,7 +236,8 @@
&--special { &--special {
color: #fff; color: #fff;
background: $argo-color-teal-7; background: linear-gradient(-58deg, #49505c 0%, #3f51b5 36%, #00c7d6 100%);
&:not(.disabled) { &:not(.disabled) {
&:hover { &:hover {
color: #fff; color: #fff;

View File

@ -1,5 +1,3 @@
@import '../theme';
.argo-container { .argo-container {
position: relative; position: relative;
width: $basePageWidth + 160; width: $basePageWidth + 160;
@ -37,10 +35,8 @@
position: relative; position: relative;
padding: 30px; padding: 30px;
font-size: 0.8125rem; font-size: 0.8125rem;
@include themify($themes) { color: $argo-color-gray-6;
background-color: themed('background-2'); background-color: #fff;
color: themed('light-argo-gray-6');
}
border-radius: 4px; border-radius: 4px;
box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.1); box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.1);
@ -91,19 +87,15 @@
} }
&-row { &-row {
@include themify($themes) { color: $argo-color-gray-8;
color: themed('text-2');
}
.columns { .columns {
border-bottom: 1px solid $argo-color-gray-3;
padding: 0; padding: 0;
vertical-align: middle; vertical-align: middle;
line-height: 50px; line-height: 50px;
overflow-wrap: break-word; overflow-wrap: break-word;
@include themify($themes) {
border-bottom: 1px solid themed('border');
}
&--narrower-height { &--narrower-height {
line-height: 20px; line-height: 20px;
padding: 14px 0; padding: 14px 0;

View File

@ -1,5 +1,4 @@
@import '../config'; @import '../config';
@import '../theme';
.argo-form-row { .argo-form-row {
position: relative; position: relative;
@ -9,9 +8,7 @@
label { label {
font-size: .9em; font-size: .9em;
@include themify($themes) { color: $argo-color-gray-6;
color: themed('light-argo-gray-6');
}
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -72,11 +69,8 @@
padding: 8px 0; padding: 8px 0;
font-size: 15px; font-size: 15px;
background-color: transparent; background-color: transparent;
@include themify($themes) {
color: themed('text-2');
border-bottom: 2px solid themed('border');;
}
border: 0; border: 0;
border-bottom: 2px solid #ccc;
transition: border .2s; transition: border .2s;
.error & { .error & {

View File

@ -1,5 +1,4 @@
@import '../config'; @import '../config';
@import '../theme';
.argo-table-list { .argo-table-list {
&__head { &__head {
@ -15,10 +14,8 @@
margin: 8px 0; margin: 8px 0;
line-height: 60px; line-height: 60px;
font-size: .8125em; font-size: .8125em;
@include themify($themes) { color: $argo-color-gray-6;
background: themed('background-2'); background-color: #fff;
color: themed('text-1');
}
border-radius: 4px; border-radius: 4px;
box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.1); box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.1);

View File

@ -1,4 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="utf-8"?>
<svg data-name="Layer 1" version="1.1" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg"> <!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<path d="m254.28 303.41c-0.50074-2.8223-0.39948-103.6 0.0975-105.52h27.162v77.765c1.1717 0.0608 2.0912 0.14921 3.0108 0.15005q16.128 0.0147 32.256 7e-3h2.9073v27.596zm-96.707-0.04025v-105.17c1.617-0.52913 61.545-0.73586 65.462-0.20457v22.414c-0.87869 0.063-1.7857 0.18341-2.6928 0.18454q-16.009 0.0199-32.017 9e-3h-2.9677v17.434h33.347v23.192h-33.048c-0.55339 1.9851-0.70531 15.817-0.25622 19.646 0.84488 0.0568 1.75 0.16952 2.6553 0.17059q16.009 0.0186 32.017 8e-3h2.9694v22.321zm-59.941-105.49h27.263c0.55054 1.7525 0.65821 102.97 0.0947 105.52h-27.288c-0.15021-6.7034-0.04736-13.384-0.06622-20.061-0.01871-6.6226-0.0041-13.245-0.0041-20.04h-33.788v39.741c-2.0595 0.61518-25.334 0.67417-27.648 0.123v-105.28h27.538v37.189c1.9679 0.56844 30.923 0.67339 33.872 0.12942 0.0082-2.9771 0.02-6.0265 0.024-9.0758q0.0063-4.7433 9.4e-4 -9.4866 0-4.625 0-9.25c1e-5 -3.0784 0-6.1567 0-9.5178zm253.1-0.12062c2.7871 0 5.4704 0.18919 8.1149-0.0501 2.9951-0.271 5.139 0.8001 7.3235 2.8131 12.613 11.622 25.357 23.101 38.059 34.627 0.63855 0.57948 1.2904 1.1443 2.1101 1.8701 0.76445-0.65718 1.4812-1.2432 2.1648-1.8655q19.639-17.877 39.248-35.787a5.4477 5.4477 0 0 1 4.2036-1.6458c3.2189 0.13038 6.4467 0.0375 9.8412 0.0375v105.37c-1.7231 0.50368-24.876 0.60366-27.639 0.0611v-53.362l-0.53656-0.25427c-9.0108 8.2178-18.022 16.436-27.238 24.841-9.2258-8.3112-18.342-16.523-27.458-24.736l-0.52449 0.19192c-0.023 4.4536-8e-3 8.9083-0.0105 13.362q-4e-3 6.6394-5.8e-4 13.279v26.872h-27.428c-0.51411-1.773-0.75283-99.662-0.22996-105.62zm43.794 150.15a176.64 176.64 0 0 1-23.973 27.163c0.70941 0.59068 1.2859 1.0704 1.8621 1.5506a108.31 108.31 0 0 1 33.683 48.146 34.618 34.618 0 0 1 2.202 14.42 14.885 14.885 0 0 1-0.74786 3.6921 7.2076 7.2076 0 0 1-8.1579 5.0231 22.233 22.233 0 0 1-6.7628-2.0063 51.232 51.232 0 0 1-9.1815-5.8151 107.59 107.59 0 0 1-32.936-46.707c-0.18746-0.51334-0.39218-1.0204-0.72243-1.8769a194.65 194.65 0 0 1-25.012 14.008 181.67 181.67 0 0 1-26.687 9.7244 187.56 187.56 0 0 1-28.304 5.388c0.16807 0.84015 0.26446 1.5098 0.43745 2.1591a109.17 109.17 0 0 1 2.9708 36.443 80.804 80.804 0 0 1-4.4229 22.477 78.25 78.25 0 0 1-4.1648 8.7447 13.39 13.39 0 0 1-2.3386 2.9708c-3.9801 4.109-8.7322 4.144-12.612-0.0737a27.28 27.28 0 0 1-3.907-5.6178c-3.0768-5.7757-4.6604-12.056-5.791-18.46a116.86 116.86 0 0 1-1.3589-26.465 94.48 94.48 0 0 1 2.8847-19.185c0.14009-0.53269 0.268-1.0696 0.37134-1.6103 0.0263-0.13754-0.0634-0.2973-0.17067-0.73825a176.12 176.12 0 0 1-80.969-24.994c-0.40992 0.90921-0.76206 1.6747-1.1017 2.4458a110.48 110.48 0 0 1-30.901 41.42 38.161 38.161 0 0 1-12.047 6.9591 12.09 12.09 0 0 1-6.5152 0.70023 7.1186 7.1186 0 0 1-5.4033-4.4892c-1.4161-3.424-1.1653-6.985-0.68438-10.517a55.453 55.453 0 0 1 4.3077-14.25 112.5 112.5 0 0 1 26.511-37.763c0.459-0.43461 0.92981-0.857 1.3809-1.2995a3.7603 3.7603 0 0 0 0.36534-0.65529 178.9 178.9 0 0 1-28.469-31.317c0.98458-0.0802 1.6433-0.18 2.302-0.1806 10.514-0.01 21.029 0.0274 31.543-0.0436a4.7055 4.7055 0 0 1 3.7034 1.6262 146.95 146.95 0 0 0 39.403 28.885 139.95 139.95 0 0 0 49.704 14.774q70.68 6.8707 121.6-42.855a7.6457 7.6457 0 0 1 5.9926-2.4435c9.8014 0.12121 19.605 0.0499 29.408 0.0499h2.5335zm-258-226.78c-0.572-0.54252-1.1946-1.1264-1.81-1.7176-12.617-12.121-22.381-26.136-28.279-42.702-1.6507-4.6364-2.8969-9.3652-2.6759-14.359 0.021-0.4739 0.0196-0.94915 0.0523-1.4221 0.445-6.4459 4.7591-9.7032 11.058-8.1767a27.325 27.325 0 0 1 5.7315 2.1965c6.8902 3.4554 12.506 8.5636 17.671 14.166a112.53 112.53 0 0 1 21.722 33.43 8.2964 8.2964 0 0 0 0.38946 0.861c0.0712 0.12855 0.22232 0.21282 0.55927 0.51883a176.36 176.36 0 0 1 81.02-24.861c-0.17651-0.8761-0.28195-1.5457-0.44772-2.2a112.49 112.49 0 0 1-2.6529-36.956 84.075 84.075 0 0 1 4.4444-21.764 31.326 31.326 0 0 1 5.4765-10.171 15.687 15.687 0 0 1 3.1634-2.8215 7.026 7.026 0 0 1 8.0326-0.056 17.279 17.279 0 0 1 5.8402 6.7318 53.054 53.054 0 0 1 5.2622 14.677 112.5 112.5 0 0 1 2.1226 33.004 95.598 95.598 0 0 1-3.4905 19.911c7.1217 1.3119 14.21 2.3287 21.147 3.9771a186.38 186.38 0 0 1 20.441 6.0033 188.32 188.32 0 0 1 19.77 8.5693c6.3454 3.1643 12.386 6.9407 18.718 10.538 0.20571-0.433 0.50439-0.94982 0.706-1.5021a108.66 108.66 0 0 1 32.901-46.762 37.758 37.758 0 0 1 11.822-6.883 17.246 17.246 0 0 1 3.6783-0.84512c6.264-0.71729 8.8935 3.2244 9.3565 7.9318a29.944 29.944 0 0 1-0.77381 10.355 87.906 87.906 0 0 1-10.73 24.688c-6.7902 10.972-14.85 20.855-25.093 28.83-0.30234 0.2354-0.56784 0.51814-1.0799 0.99029a177.78 177.78 0 0 1 26.593 30.882 10.962 10.962 0 0 1-1.689 0.29762c-10.595 0.015-21.191-0.0183-31.786 0.0461a4.004 4.004 0 0 1-3.1725-1.69 147.88 147.88 0 0 0-88.178-46.548 143.36 143.36 0 0 0-30.28-1.1692 146.41 146.41 0 0 0-82.537 31.811 140.07 140.07 0 0 0-16.976 15.842 4.7284 4.7284 0 0 1-3.8633 1.7574c-10.121-0.0703-20.242-0.035-30.363-0.0349h-2.1521c0.618-2.408 6.8403-10.938 13.884-18.553 5.2525-5.6788 10.817-11.069 16.468-16.818z"/> <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 620 645.6" style="enable-background:new 0 0 620 645.6;" xml:space="preserve">
<style type="text/css">
.st0{fill:#277A9F;}
.st1{fill:none;stroke:#277A9F;stroke-width:30;}
</style>
<title>logo</title>
<desc>Created with Sketch.</desc>
<g id="Page-1">
<g id="logo_x40_2x" transform="translate(-152.000000, -162.000000)">
<g id="logo" transform="translate(149.000000, 162.000000)">
<g id="helm" transform="translate(22.377622, 0.000000)">
<g id="rungs-bottom" transform="translate(41.500000, 108.500000) scale(1, -1) translate(-41.500000, -108.500000) translate(0.000000, 91.000000)">
<path id="Oval-1" class="st0" d="M417.2-357.5c10.4,7.3,34.3-8.7,53.2-35.8s25.9-55,15.4-62.3c-10.4-7.3-34.3,8.7-53.2,35.8
C413.7-392.7,406.8-364.8,417.2-357.5z"/>
<path id="Oval-1-Copy" class="st0" d="M167.6-357.5c-10.4,7.3-34.3-8.7-53.2-35.8c-19-27.1-25.9-55-15.4-62.3
s34.3,8.7,53.2,35.8S178.1-364.8,167.6-357.5z"/>
<path id="Oval-1-Copy-4" class="st0" d="M292.3-399.9c-12.7,0-23.1-26.8-23.1-59.9s10.3-59.9,23.1-59.9s23.1,26.8,23.1,59.9
S305-399.9,292.3-399.9z"/>
</g>
<g id="rungs-top">
<path id="Oval-1_1_" class="st0" d="M417.2,162.5c10.4,7.3,34.3-8.7,53.2-35.8s25.9-55,15.4-62.3c-10.4-7.3-34.3,8.7-53.2,35.8
S406.8,155.2,417.2,162.5z"/>
<path id="Oval-1-Copy_1_" class="st0" d="M167.6,162.5c-10.4,7.3-34.3-8.7-53.2-35.8S88.5,71.8,99,64.4s34.3,8.7,53.2,35.8
S178.1,155.2,167.6,162.5z"/>
<path id="Oval-1-Copy-4_1_" class="st0" d="M292.3,120.1c-12.7,0-23.1-26.8-23.1-59.9s10.3-59.9,23.1-59.9s23.1,26.8,23.1,59.9
S305,120.1,292.3,120.1z"/>
</g>
<path id="Oval-1_2_" class="st1" d="M470.2,215.6C433,154.4,365.6,113.5,288.7,113.5c-74.9,0-140.7,38.7-178.4,97.2 M114.3,446
c38.3,55.1,102.1,91.1,174.4,91.1c72.4,0,136.3-36.2,174.6-91.4"/>
</g>
<path id="HELM" class="st0" d="M3.6,244.1H48v62.5h50.1v-62.5h44.4V412H98.1v-66.6H48V412H3.6V244.1z M179.8,244.1h108.5v37.2
h-64.1v26.3H279v37.2h-54.8v30h66.6V412H179.8V244.1z M323.4,244.1h44.4v130.7h63.5V412h-108V244.1z M460.3,244.1h46l26.3,69.7
l9.3,27.4h1l9.3-27.4l25.3-69.7h46V412h-41.3v-49.6c0-4.3,0.2-9,0.6-14.2c0.4-5.2,0.9-10.5,1.4-15.9c0.5-5.4,1.1-10.7,1.8-15.9
c0.7-5.2,1.3-9.8,1.8-13.9h-1l-13.9,39.3l-23.2,56.6h-16.5l-23.2-56.6l-13.4-39.3h-1c0.5,4.1,1.1,8.8,1.8,13.9
c0.7,5.2,1.3,10.5,1.8,15.9c0.5,5.4,1,10.7,1.4,15.9c0.4,5.2,0.6,9.9,0.6,14.2V412h-40.8V244.1z"/>
</g>
</g>
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<style type="text/css">
.st0{fill:#808184;}
.st1{fill:#262261;}
</style>
<g>
<g>
<g>
<polygon class="st0" points="326.6,212.6 326.6,132.6 128.6,132.6 128.6,444.6 326.6,444.6 326.6,364.6 208.6,364.6 208.6,212.6
"/>
<g>
<rect x="366.5" y="132.6" class="st1" width="79.9" height="79.9"/>
<rect x="366.5" y="252.6" class="st1" width="79.9" height="192"/>
</g>
</g>
<path class="st1" d="M8.5,9.5v558.2h558.2V9.5H8.5z M486.4,484.7H88.7V92.6h397.8V484.7z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 852 B

View File

@ -5,7 +5,6 @@
@import 'node_modules/@fortawesome/fontawesome-free/scss/fontawesome'; @import 'node_modules/@fortawesome/fontawesome-free/scss/fontawesome';
@import 'node_modules/@fortawesome/fontawesome-free/scss/solid'; @import 'node_modules/@fortawesome/fontawesome-free/scss/solid';
@import 'node_modules/@fortawesome/fontawesome-free/scss/brands'; @import 'node_modules/@fortawesome/fontawesome-free/scss/brands';
@import 'node_modules/@fortawesome/fontawesome-free/scss/regular';
@include foundation-global-styles; @include foundation-global-styles;
@include foundation-flex-classes; @include foundation-flex-classes;

View File

@ -1,56 +0,0 @@
@import './config.scss';
$themes: (
light: (
background-1: $argo-color-gray-3,
text-1: $argo-color-gray-7,
background-2: $white-color,
text-2: $argo-color-gray-8,
light-argo-gray-6: $argo-color-gray-6,
light-argo-gray-2: $argo-color-gray-2,
light-argo-teal-1: $argo-color-teal-1,
light-argo-teal-7: $argo-color-teal-7,
light-argo-teal-5: $argo-color-teal-5,
pod-cyan: lightcyan,
layout-loader-bg: rgba($argo-color-gray-7, 0.4),
overlay: rgba(222, 222, 222, 0.62),
shadow: $argo-color-gray-5,
border: $argo-color-gray-3
),
dark: (
background-1: $dark-theme-background-1,
text-1: $argo-color-gray-3,
background-2: $dark-theme-background-2,
text-2: $white-color,
light-argo-gray-6: $argo-color-gray-2,
light-argo-gray-2: $dark-theme-sliding-panel,
light-argo-teal-1: $argo-color-gray-6,
light-argo-teal-7: $argo-color-teal-5,
light-argo-teal-5: $argo-color-teal-4,
pod-cyan: $argo-color-teal-8,
layout-loader-bg: rgba($argo-color-gray-3, 0.4),
overlay: rgba(70, 70, 70, 0.62),
shadow: $dark-theme-background-1,
border: $argo-color-gray-7
)
);
@mixin themify($themes) {
@each $theme, $map in $themes {
.theme-#{$theme} & {
$theme-map: () !global;
@each $key, $submap in $map {
$value: map-get(map-get($themes, $theme), '#{$key}');
$theme-map: map-merge($theme-map, ($key: $value)) !global;
}
@content;
$theme-map: null !global;
}
}
}
@function themed($key) {
@return map-get($theme-map, $key);
}

View File

@ -1,24 +1,23 @@
{ {
"compilerOptions": { "compilerOptions": {
"outDir": "./../bundle", "outDir": "./../../bundle",
"sourceMap": true, "sourceMap": true,
"noImplicitAny": true, "noImplicitAny": true,
"module": "commonjs", "module": "commonjs",
"target": "es6", "target": "es5",
"jsx": "react", "jsx": "react",
"esModuleInterop": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"declaration": true, "declaration": true,
"skipLibCheck": true,
"lib": [ "lib": [
"es2017", "es2017",
"dom" "dom"
], ],
"typeRoots": [ "typeRoots": [
"./node_modules/@types" "../node_modules/@types"
] ]
}, },
"include": ["./**/*"], "include": [
"exclude": ["node_modules", "./**/*.test.ts", "./**/*.test.tsx", "./**/*.stories.tsx"] "./**/*"
]
} }

22
src/webpack.config.js Normal file
View File

@ -0,0 +1,22 @@
'use strict;';
const config = require('../app/webpack.config');
const webpack = require('webpack');
module.exports = Object.assign({}, config, {
entry: './src/index.ts',
output: {
filename: 'bundle.js',
path: __dirname + '/../../bundle',
library: 'argo-ui',
libraryTarget: 'umd',
umdNamedDefine: true,
},
plugins: [
new webpack.DefinePlugin({
SYSTEM_INFO: JSON.stringify({
version: process.env.ARGO_VERSION || 'latest',
}),
}),
],
});

View File

@ -1,3 +1,5 @@
import { Store, withState } from '@dump247/storybook-state';
import { storiesOf } from '@storybook/react';
import * as React from 'react'; import * as React from 'react';
import { App } from './utils'; import { App } from './utils';
@ -8,18 +10,13 @@ function loadData(input: string): Promise<string> {
return new Promise((resolve) => window.setTimeout(() => resolve(`hello ${input}`), 50)); return new Promise((resolve) => window.setTimeout(() => resolve(`hello ${input}`), 50));
} }
export default { storiesOf('Data Loader', module)
title: 'Data Loader', .add('loading data asynchronously', withState({ input: 'world' })(({store}: { store: Store<any> }) => (
};
export const LoadingDataAsynchronously = () => {
const [input, setInput] = React.useState('world');
return (
<App> <App>
{() => ( {() => (
<React.Fragment> <React.Fragment>
<input value={input} onChange={(e) => setInput(e.target.value)}/> <input value={store.state.input} onChange={(e) => store.set({ input: e.target.value })}/>
<DataLoader input={input} load={loadData}> <DataLoader input={store.state.input} load={(input) => loadData(input)}>
{(data) => ( {(data) => (
<div> <div>
{data} {data}
@ -29,6 +26,4 @@ export const LoadingDataAsynchronously = () => {
</React.Fragment> </React.Fragment>
)} )}
</App> </App>
); )));
};
LoadingDataAsynchronously.storyName = 'loading data asynchronously';

View File

@ -1,33 +0,0 @@
import * as React from 'react';
import { DropDown } from '../src/components/dropdown/dropdown';
import { DropDownMenu } from '../src/components/dropdown-menu';
export default {
title: 'Dropdown',
};
export const Default = () => (<DropDown anchor={() => <a>Click me</a>}><p>Dropdown content here</p></DropDown>);
Default.storyName = 'default';
export const Menu = () => {
return (
<DropDown isMenu={true} anchor={() => <a>Click me</a>}>
<ul>
<li><a>menu item 1</a></li>
<li><a>menu item 2</a></li>
</ul>
</DropDown>
);
}
Menu.storyName = 'menu';
export const MenuWrapper = () => {
return (
<DropDownMenu anchor={() => <a>Click me</a>} items={[{
title: 'menu item 1',
action: () => window.alert('Clicked!'),
}]} />
);
}
MenuWrapper.storyName = 'menu wrapper';

20
stories/dropdown.tsx Normal file
View File

@ -0,0 +1,20 @@
import { storiesOf } from '@storybook/react';
import * as React from 'react';
import { DropDown, DropDownMenu } from '../src/components';
storiesOf('Dropdown', module)
.add('default', () => <DropDown anchor={() => <a>Click me</a>}><p>Dropdown content here</p></DropDown>)
.add('menu', () => (
<DropDown isMenu={true} anchor={() => <a>Click me</a>}>
<ul>
<li><a>menu item 1</a></li>
<li><a>menu item 2</a></li>
</ul>
</DropDown>
)).add('menu wrapper', () => (
<DropDownMenu anchor={() => <a>Click me</a>} items={[{
title: 'menu item 1',
action: () => window.alert('Clicked!'),
}]} />
));

View File

@ -1,14 +1,10 @@
import { storiesOf } from '@storybook/react';
import * as React from 'react'; import * as React from 'react';
import { Form, Text } from 'react-form'; import { Form, Text } from 'react-form';
import { FormField, FormSelect } from '../src/components';
import { FormField, FormSelect } from '../src/components/form-field/form-field'; storiesOf('Forms', module)
.add('default', () => (
export default {
title: 'Forms',
};
export const Default = () => {
return (
<Form> <Form>
{(api) => ( {(api) => (
<form style={{padding: '1em'}}> <form style={{padding: '1em'}}>
@ -19,11 +15,9 @@ export const Default = () => {
<FormField label='Password' formApi={api} field='passwordField' component={Text} componentProps={{type: 'password'}} /> <FormField label='Password' formApi={api} field='passwordField' component={Text} componentProps={{type: 'password'}} />
</div> </div>
<div className='argo-form-row'> <div className='argo-form-row'>
<FormField label='Select' formApi={api} field='selectField' component={FormSelect} componentProps={{options: ['option1', 'option2']}} /> <FormField label='Select' formApi={api} field='selectField' component={FormSelect} componentProps={{options: ['option1', 'option2']}} />
</div> </div>
</form> </form>
)} )}
</Form> </Form>
); ));
}
Default.storyName = 'default';

View File

@ -0,0 +1,9 @@
import './data-loader';
import './dropdown';
import './forms';
import './notifications';
import './page';
import './popup';
import './select';
import './table';
import './tabs';

View File

@ -1,22 +0,0 @@
import * as React from 'react';
import { Observable } from 'rxjs';
import { LogsViewer } from '../src/components/logs-viewer/logs-viewer';
export default {
title: 'LogsViewer',
};
export const Default = () => (
<div>
<LogsViewer source={{
key: 'test',
loadLogs: () => new Observable<string>((observer) => {
const interval = setInterval(() => observer.next('test\n'), 1000);
return () => clearInterval(interval);
}),
shouldRepeat: () => false,
}}/>
</div>
);
Default.storyName = 'default';

View File

@ -1,38 +0,0 @@
import * as React from 'react';
import { NotificationType } from '../src/components/notifications/notifications';
import { App } from './utils';
const messages = [
'Mist enveloped the ship three hours out from port. The recorded voice scratched in the speaker. Silver mist suffused the deck of the ship.',
'Then came the night of the first falling star. A red flare silhouetted the jagged edge of a wing. She stared through the window at the stars.',
'The spectacle before us was indeed sublime. A shining crescent far beneath the flying vessel. She stared through the window at the stars.',
];
function getMessage() {
return messages[Math.floor(Math.random() * messages.length)];
}
export default {
title: 'Notifications',
};
export const Default = () => {
return (
<App>
{(apis) => [
{type: NotificationType.Success, title: 'Success'},
{type: NotificationType.Warning, title: 'Warning'},
{type: NotificationType.Error, title: 'Error'},
].map((item) => (
<button key={item.type} className='argo-button argo-button--base' onClick={() =>
apis.notifications.show({type: item.type, content: <div>{getMessage()}</div>})
}>
{item.title}
</button>
))}
</App>
);
}
Default.storyName = 'default';

34
stories/notifications.tsx Normal file
View File

@ -0,0 +1,34 @@
import { storiesOf } from '@storybook/react';
import * as React from 'react';
import { NotificationType} from '../src/components';
import { App } from './utils';
const messages = [
'Mist enveloped the ship three hours out from port. The recorded voice scratched in the speaker. Silver mist suffused the deck of the ship.',
'Then came the night of the first falling star. A red flare silhouetted the jagged edge of a wing. She stared through the window at the stars.',
'The spectacle before us was indeed sublime. A shining crescent far beneath the flying vessel. She stared through the window at the stars.',
];
function getMessage() {
return messages[Math.floor(Math.random() * messages.length)];
}
storiesOf('Notifications', module)
.add('default', () => (
<App>
{(apis) => (
[
{type: NotificationType.Success, title: 'Success'},
{type: NotificationType.Warning, title: 'Warning'},
{type: NotificationType.Error, title: 'Error'},
].map((item) => (
<button key={item.type} className='argo-button argo-button--base'
onClick={() => apis.notifications.show({type: item.type, content: <div>{getMessage()}</div>})}>
{item.title}
</button>
))
)}
</App>
));

View File

@ -1,186 +0,0 @@
import { createBrowserHistory } from 'history';
import * as React from 'react';
import { Route, Router } from 'react-router';
import { timer } from 'rxjs';
import { map } from 'rxjs/operators';
import { Layout } from '../src/components/layout/layout';
import { Page } from '../src/components/page/page';
const navItems = [{ path: location.pathname, title: 'Sample', iconClassName: 'argo-icon-docs' }];
const breadcrumbs = [{
title: 'breadcrumb parent',
path: location.pathname,
}, {
title: 'this page breadcrumb',
}];
const actionMenu = {
items: [{
title: 'New Item 1',
iconClassName: 'fa fa-history',
action: () => {
// do nothing
},
}, {
title: 'New Item 2',
iconClassName: 'icon argo-icon-deploy',
action: () => {
// do nothing
},
},
{
title: 'New Item 3',
action: () => {
// do nothing
},
},
{
title: 'New Item 4',
iconClassName: 'fa fa-times-circle',
disabled: true,
action: () => {
// do nothing
},
}],
};
const history = createBrowserHistory();
function ensureSelected(vals: string[], selected: string[]): string[] {
const res = new Set(selected);
vals.forEach((item) => res.add(item));
return Array.from(res);
}
export default {
title: 'Page',
};
export const Default = () => {
const [selectedFilter, setSelectedFilter] = React.useState<string[]>([]);
return (
<Router history={history}>
<Route path={location.pathname}>
<Layout navItems={navItems}>
<Page title='Hello world!' toolbar={{breadcrumbs, actionMenu, filter: {
items: [
{
content: (changeSelection) => (
<React.Fragment>
Filter type one: <a onClick={() => changeSelection(ensureSelected(['1', '2'], selectedFilter))}>all</a>
</React.Fragment>
),
},
{ label: 'filter 1', value: '1' },
{ label: 'filter 2', value: '2' },
{
content: (changeSelection) => (
<React.Fragment>
Filter type two: <a onClick={() => changeSelection(ensureSelected(['3', '4'], selectedFilter))}>all</a>
</React.Fragment>
),
},
{ label: 'filter 3', value: '3' },
{ label: 'filter 4', value: '4' },
],
selectedValues: selectedFilter,
selectionChanged: setSelectedFilter,
}}}>
<div style={{ padding: '1em' }}>
<div className='white-box'>
Hello world!
</div>
</div>
</Page>
</Layout>
</Route>
</Router>
)
};
Default.storyName = 'default';
export const DynamicToolbar = () => {
return (
<Router history={history}>
<Route path={location.pathname}>
<Layout navItems={navItems}>
<Page title='Hello world!' toolbar={timer(0, 1000).pipe(map(() => ({breadcrumbs: [
{ title: 'hello ' + new Date().toLocaleTimeString() },
]})))}>
<div style={{ padding: '1em' }}>
<div className='white-box'>Hello world!</div>
</div>
</Page>
</Layout>
</Route>
</Router>
);
}
DynamicToolbar.storyName = 'dynamic toolbar';
export const CompactNavBar = () => {
const manyNavItems = [];
for (let i = 0; i < 10; i++) {
manyNavItems.push({path: location.pathname + '/' + i, title: 'Sample', iconClassName: 'argo-icon-docs'});
}
return (
(
<Router history={history}>
<Route path={location.pathname}>
<Layout navItems={manyNavItems}>
<Page title='Hello world!'>
<div style={{ padding: '1em' }}>
<div className='white-box'>
Hello world!
</div>
</div>
</Page>
</Layout>
</Route>
</Router>
)
);
};
CompactNavBar.storyName = 'compact nav bar';
export const CustomTopBarTitle = () => {
return (
<Router history={history}>
<Route path={location.pathname}>
<Layout navItems={navItems}>
<Page title='helmet title' topBarTitle='Top Bar Title' toolbar={{breadcrumbs: [
{ title: 'Apps ', path: '/applications' },
{ title: 'app name' },
]}}>
<div style={{ padding: '1em' }}>
<div className='white-box'>
Test
</div>
</div>
</Page>
</Layout>
</Route>
</Router>
);
}
CustomTopBarTitle.storyName = 'custom top bar title';
export const BackgroundColor = () => {
return (
<Router history={history}>
<Route path={location.pathname}>
<Layout navItems={navItems} navBarStyle={{ backgroundColor: 'red' }}>
<Page title='Hello world!'>
<div style={{ padding: '1em' }}>
<div className='white-box'>
Hello world!
</div>
</div>
</Page>
</Layout>
</Route>
</Router>
);
}
BackgroundColor.storyName = 'background color';

127
stories/page.tsx Normal file
View File

@ -0,0 +1,127 @@
import { Store, withState } from '@dump247/storybook-state';
import { storiesOf } from '@storybook/react';
import createHistory from 'history/createBrowserHistory';
import * as React from 'react';
import { Route, Router } from 'react-router';
import { timer } from 'rxjs';
import { map } from 'rxjs/operators';
import { Layout, Page } from '../src/components';
const navItems = [{ path: location.pathname, title: 'Sample', iconClassName: 'argo-icon-docs' }];
const breadcrumbs = [{
title: 'breadcrumb parent',
path: location.pathname,
}, {
title: 'this page breadcrumb',
}];
const actionMenu = {
items: [{
title: 'New Item 1',
iconClassName: 'fa fa-history',
action: () => {
// do nothing
},
}, {
title: 'New Item 2',
iconClassName: 'icon argo-icon-deploy',
action: () => {
// do nothing
},
},
{
title: 'New Item 3',
action: () => {
// do nothing
},
},
{
title: 'New Item 4',
iconClassName: 'fa fa-times-circle',
disabled: true,
action: () => {
// do nothing
},
}]
};
const history = createHistory();
function ensureSelected(vals: string[], selected: string[]): string[] {
const res = new Set(selected);
vals.forEach((item) => res.add(item));
return Array.from(res);
}
storiesOf('Page', module)
.add('default', withState({ selectedFilter: [] })(({store}: { store: Store<any> }) => (
<Router history={history}>
<Route path={location.pathname}>
<Layout navItems={navItems}>
<Page title='Hello world!' toolbar={{ breadcrumbs, actionMenu, filter: {
items: [
{ content: (changeSelection) => (
<React.Fragment>
Filter type one: <a onClick={() => changeSelection(ensureSelected(['1', '2'], store.state.selectedFilter))}>all</a>
</React.Fragment>
)},
{label: 'filter 1', value: '1' },
{label: 'filter 2', value: '2' },
{ content: (changeSelection) => (
<React.Fragment>
Filter type two: <a onClick={() => changeSelection(ensureSelected(['3', '4'], store.state.selectedFilter))}>all</a>
</React.Fragment>
)},
{label: 'filter 3', value: '3' },
{label: 'filter 4', value: '4' },
],
selectedValues: store.state.selectedFilter,
selectionChanged: (vals) => {
store.set({ selectedFilter: vals });
},
}}}>
<div style={{padding: '1em'}}>
<div className='white-box'>
Hello world!
</div>
</div>
</Page>
</Layout>
</Route>
</Router>
))).add('dynamic toolbar', () => (
<Router history={history}>
<Route path={location.pathname}>
<Layout navItems={navItems}>
<Page title='Hello world!' toolbar={timer(0, 1000).pipe(map(() => ({ breadcrumbs: [{title: 'hello ' + new Date().toLocaleTimeString()}] })))}>
<div style={{padding: '1em'}}>
<div className='white-box'>
Hello world!
</div>
</div>
</Page>
</Layout>
</Route>
</Router>
)).add('compact nav bar', () => {
const manyNavItems = [];
for (let i = 0; i < 10; i++) {
manyNavItems.push({ path: location.pathname + '/' + i, title: 'Sample', iconClassName: 'argo-icon-docs' });
}
return (
<Router history={history}>
<Route path={location.pathname}>
<Layout navItems={manyNavItems}>
<Page title='Hello world!'>
<div style={{padding: '1em'}}>
<div className='white-box'>
Hello world!
</div>
</div>
</Page>
</Layout>
</Route>
</Router>
);
});

View File

@ -1,18 +1,15 @@
import { Store, withState } from '@dump247/storybook-state';
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import * as React from 'react'; import * as React from 'react';
import { Checkbox as ReactCheckbox } from 'react-form'; import { Checkbox as ReactCheckbox} from 'react-form';
import { Text } from 'react-form'; import { Text } from 'react-form';
import { Checkbox } from '../src/components/checkbox'; import { Checkbox, FormField } from '../src/components';
import { FormField } from '../src/components/form-field/form-field';
import { App } from './utils'; import { App } from './utils';
export default { storiesOf('Popup', module)
title: 'Popup', .add('confirmation', () => (
};
export const Confirmation = () => {
return (
<App> <App>
{(apis) => ( {(apis) => (
<button className='argo-button argo-button--base' onClick={async () => { <button className='argo-button argo-button--base' onClick={async () => {
@ -21,76 +18,57 @@ export const Confirmation = () => {
}}>Click me</button> }}>Click me</button>
)} )}
</App> </App>
); )).add('confirmation with custom form inside', withState({ checked: false })(({store}: { store: Store<any> }) => (
}
Confirmation.storyName = 'confirmation';
export const ConfirmationWithCustomFormInside = () => {
const [checked, setChecked] = React.useState(false);
return (
(
<App> <App>
{(apis) => ( {(apis) => (
<div> <div>
<button className='argo-button argo-button--base' onClick={async () => { <button className='argo-button argo-button--base' onClick={async () => {
const confirmed = await apis.popup.confirm('Do it!', () => ( const confirmed = await apis.popup.confirm('Do it!', () => (
<div> <div>
Click checkbox and confirm <Checkbox checked={checked} onChange={setChecked} /> Click checkbox and confirm <Checkbox checked={store.state.checked} onChange={(val) => store.set({ checked: val })} />
</div> </div>
)); ));
action('Confirmed')(confirmed); action('Confirmed')(confirmed);
}}>Click me</button> }}>Click me</button>
<p>Checked?: {JSON.stringify(checked)}</p> <p>Checked?: {JSON.stringify(store.state.checked)}</p>
</div> </div>
)} )}
</App> </App>
) ),
); )).add('prompt', () => (
}
ConfirmationWithCustomFormInside.storyName = 'confirmation with custom form inside';
export const Prompt = () => {
return (
<App> <App>
{(apis) => ( {(apis) => (
<button className='argo-button argo-button--base' onClick={async () => { <button className='argo-button argo-button--base' onClick={async () => {
const values = await apis.popup.prompt('Enter name', (api) => ( const values = await apis.popup.prompt('Enter name', (api) => (
<React.Fragment> <React.Fragment>
<div className='argo-form-row'> <div className='argo-form-row'>
<FormField label='First Name' formApi={api} field='firstName' component={Text} /> <FormField label='First Name' formApi={api} field='firstName' component={Text} />
</div> </div>
<div className='argo-form-row'> <div className='argo-form-row'>
<FormField label='Last Name' formApi={api} field='lastName' component={Text} /> <FormField label='Last Name' formApi={api} field='lastName' component={Text} />
</div> </div>
</React.Fragment> </React.Fragment>
), { ), { validate: (vals) => ({
validate: (vals) => ({
firstName: !vals.firstName && 'First Name is required', firstName: !vals.firstName && 'First Name is required',
lastName: !vals.lastName && 'Last Name is required', lastName: !vals.lastName && 'Last Name is required',
}) })});
});
action('Prompt values')(values); action('Prompt values')(values);
}}>Click me</button> }}>Click me</button>
)} )}
</App> </App>
); )).add('prompt with custom submit', () => (
}
Prompt.storyName = 'prompt';
export const PromptWithCustomSubmit = () => {
return (
<App> <App>
{(apis) => ( {(apis) => (
<button className='argo-button argo-button--base' onClick={() => { <button className='argo-button argo-button--base' onClick={() => {
apis.popup.prompt('Username: test Password: test', (api) => ( apis.popup.prompt('Username: test Password: test', (api) => (
<React.Fragment> <React.Fragment>
<div className='argo-form-row'> <div className='argo-form-row'>
<FormField label='Username' formApi={api} field='username' component={Text} /> <FormField label='Username' formApi={api} field='username' component={Text} />
</div> </div>
<div className='argo-form-row'> <div className='argo-form-row'>
<FormField label='Password' formApi={api} field='password' component={Text} componentProps={{type: 'password'}} /> <FormField label='Password' formApi={api} field='password' component={Text} componentProps={{type: 'password'}} />
</div> </div>
</React.Fragment> </React.Fragment>
), { ), {
validate: (vals) => ({ validate: (vals) => ({
@ -109,23 +87,18 @@ export const PromptWithCustomSubmit = () => {
}}>Click me</button> }}>Click me</button>
)} )}
</App> </App>
); )).add('prompt with red title and icon, with custom submit', () => (
}
PromptWithCustomSubmit.storyName = 'prompt with custom submit';
export const PromptWithRedTitleAndIconWithCustomSubmit = () => {
return (
<App> <App>
{(apis) => ( {(apis) => (
<button className='argo-button argo-button--base' onClick={() => { <button className='argo-button argo-button--base' onClick={() => {
apis.popup.prompt('Username: test Password: test', (api) => ( apis.popup.prompt('Username: test Password: test', (api) => (
<React.Fragment> <React.Fragment>
<div className='argo-form-row'> <div className='argo-form-row'>
<FormField label='Username' formApi={api} field='username' component={Text} /> <FormField label='Username' formApi={api} field='username' component={Text} />
</div> </div>
<div className='argo-form-row'> <div className='argo-form-row'>
<FormField label='Password' formApi={api} field='password' component={Text} componentProps={{type: 'password'}} /> <FormField label='Password' formApi={api} field='password' component={Text} componentProps={{type: 'password'}} />
</div> </div>
</React.Fragment> </React.Fragment>
), { ), {
validate: (vals) => ({ validate: (vals) => ({
@ -146,30 +119,25 @@ export const PromptWithRedTitleAndIconWithCustomSubmit = () => {
}}>Click me</button> }}>Click me</button>
)} )}
</App> </App>
); )).add('prompt with yellow title and icon, three fields and custom submit. Vertical center layout of icon', () => (
}
PromptWithRedTitleAndIconWithCustomSubmit.storyName = 'prompt with red title and icon, with custom submit';
export const PromptWithYellowTitleAndIconThreeFieldsAndCustomSubmitVerticalCenterLayoutOfIcon = () => {
return (
<App> <App>
{(apis) => ( {(apis) => (
<button className='argo-button argo-button--base' onClick={() => { <button className='argo-button argo-button--base' onClick={() => {
apis.popup.prompt('Username: test Password: test', (api) => ( apis.popup.prompt('Username: test Password: test', (api) => (
<React.Fragment> <React.Fragment>
<div className='argo-form-row'> <div className='argo-form-row'>
<FormField label='Username' formApi={api} field='username' component={Text} /> <FormField label='Username' formApi={api} field='username' component={Text} />
</div> </div>
<div className='argo-form-row'> <div className='argo-form-row'>
<FormField label='Password' formApi={api} field='password' component={Text} componentProps={{type: 'password'}} /> <FormField label='Password' formApi={api} field='password' component={Text} componentProps={{type: 'password'}} />
</div> </div>
<div className='argo-form-row'> <div className='argo-form-row'>
<FormField label='Re-enter password' formApi={api} field='password' component={Text} componentProps={{type: 'password'}} /> <FormField label='Re-enter password' formApi={api} field='password' component={Text} componentProps={{type: 'password'}} />
</div> </div>
<h4>This is an h4 header</h4> <h4>This is an h4 header</h4>
<p>This is a paragraph</p> <p>This is a paragraph</p>
<h4>This is another h4 header</h4> <h4>This is another h4 header</h4>
<p>This is a paragraph</p> <p>This is a paragraph</p>
</React.Fragment> </React.Fragment>
), { ), {
validate: (vals) => ({ validate: (vals) => ({
@ -190,23 +158,18 @@ export const PromptWithYellowTitleAndIconThreeFieldsAndCustomSubmitVerticalCente
}}>Click me</button> }}>Click me</button>
)} )}
</App> </App>
); )).add('prompt with green clock icon and custom submit', () => (
}
PromptWithYellowTitleAndIconThreeFieldsAndCustomSubmitVerticalCenterLayoutOfIcon.storyName = 'prompt with yellow title and icon, three fields and custom submit. Vertical center layout of icon';
export const PromptWithGreenClockIconAndCustomSubmit = () => {
return (
<App> <App>
{(apis) => ( {(apis) => (
<button className='argo-button argo-button--base' onClick={() => { <button className='argo-button argo-button--base' onClick={() => {
apis.popup.prompt('Username: test Password: test', (api) => ( apis.popup.prompt('Username: test Password: test', (api) => (
<React.Fragment> <React.Fragment>
<div className='argo-form-row'> <div className='argo-form-row'>
<FormField label='Username' formApi={api} field='username' component={Text} /> <FormField label='Username' formApi={api} field='username' component={Text} />
</div> </div>
<div className='argo-form-row'> <div className='argo-form-row'>
<FormField label='Password' formApi={api} field='password' component={Text} componentProps={{type: 'password'}} /> <FormField label='Password' formApi={api} field='password' component={Text} componentProps={{type: 'password'}} />
</div> </div>
</React.Fragment> </React.Fragment>
), { ), {
validate: (vals) => ({ validate: (vals) => ({
@ -222,16 +185,12 @@ export const PromptWithGreenClockIconAndCustomSubmit = () => {
} }
}, },
}, },
{ name: 'argo-icon-clock', color: 'success' }); { name: 'argo-icon-clock', color: 'success'}
);
}}>Click me</button> }}>Click me</button>
)} )}
</App> </App>
); )).add('prompt with just headers and paragraphs', () => (
}
PromptWithGreenClockIconAndCustomSubmit.storyName = 'prompt with green clock icon and custom submit';
export const PromptWithJustHeadersAndParagraphs = () => {
return (
<App> <App>
{(apis) => ( {(apis) => (
<button className='argo-button argo-button--base' onClick={async () => { <button className='argo-button argo-button--base' onClick={async () => {
@ -242,17 +201,13 @@ export const PromptWithJustHeadersAndParagraphs = () => {
<h4>This is another h4 header</h4> <h4>This is another h4 header</h4>
<p>This is a paragraph</p> <p>This is a paragraph</p>
</div> </div>
)); )
);
action('Prompt values')(values); action('Prompt values')(values);
}}>Click me</button> }}>Click me</button>
)} )}
</App> </App>
); )).add('prompt with only paragraphs. Additional top padding is optional for the first paragraph', () => (
}
PromptWithJustHeadersAndParagraphs.storyName = 'prompt with just headers and paragraphs';
export const PromptWithOnlyParagraphsAdditionalTopPaddingIsOptionalForTheFirstParagraph = () => {
return (
<App> <App>
{(apis) => ( {(apis) => (
<button className='argo-button argo-button--base' onClick={async () => { <button className='argo-button argo-button--base' onClick={async () => {
@ -261,17 +216,13 @@ export const PromptWithOnlyParagraphsAdditionalTopPaddingIsOptionalForTheFirstPa
<p style={{paddingTop: '20px'}}>This is a paragraph</p> <p style={{paddingTop: '20px'}}>This is a paragraph</p>
<p>This is another paragraph</p> <p>This is another paragraph</p>
</div> </div>
)); )
);
action('Prompt values')(values); action('Prompt values')(values);
}}>Click me</button> }}>Click me</button>
)} )}
</App> </App>
); )).add('prompt with React Checkbox that is checked by default; Username default set to admin', () => (
}
PromptWithOnlyParagraphsAdditionalTopPaddingIsOptionalForTheFirstParagraph.storyName = 'prompt with only paragraphs. Additional top padding is optional for the first paragraph';
export const PromptWithReactCheckboxThatIsCheckedByDefaultUsernameDefaultSetToAdmin = () => {
return (
<App> <App>
{(apis) => ( {(apis) => (
<button className='argo-button argo-button--base' onClick={async () => { <button className='argo-button argo-button--base' onClick={async () => {
@ -285,11 +236,11 @@ export const PromptWithReactCheckboxThatIsCheckedByDefaultUsernameDefaultSetToAd
<FormField label='Password' formApi={api} field='password' component={Text} componentProps={{type: 'password'}} /> <FormField label='Password' formApi={api} field='password' component={Text} componentProps={{type: 'password'}} />
</div> </div>
<div className='argo-form-row'> <div className='argo-form-row'>
<ReactCheckbox id='popup-react-checkbox' field='checkboxField' />{' '} <ReactCheckbox id='popup-react-checkbox' field='checkboxField'/> <label htmlFor='popup-react-checkbox'>This is a React Checkbox</label>
<label htmlFor='popup-react-checkbox'>This is a React Checkbox</label>
</div> </div>
</React.Fragment> </React.Fragment>
), { ),
{
validate: (vals) => ({ validate: (vals) => ({
username: !vals.username && 'Username is required', username: !vals.username && 'Username is required',
password: !vals.password && 'Password is required', password: !vals.password && 'Password is required',
@ -311,6 +262,4 @@ export const PromptWithReactCheckboxThatIsCheckedByDefaultUsernameDefaultSetToAd
}}>Click me</button> }}>Click me</button>
)} )}
</App> </App>
); ))
}
PromptWithReactCheckboxThatIsCheckedByDefaultUsernameDefaultSetToAdmin.storyName = 'prompt with React Checkbox that is checked by default; Username default set to admin';

View File

@ -1,42 +0,0 @@
import * as React from 'react';
import { Select } from '../src/components/select/select';
export default {
title: 'Select',
};
export const Default = () => {
const [selected, setSelected] = React.useState('option1');
return (
<div>
<h4>
Selected option value: {selected}
<button className='argo-button argo-button--base' onClick={() => setSelected('option2')} >Select option 2</button>
</h4>
<Select
value={selected}
placeholder='Select something'
options={['option1', { value: 'option2', title: 'Option 2' }]}
onChange={(option) => setSelected(option.value)}
/>
</div>
)
};
Default.storyName = 'default';
export const MultiSelect = () => {
const [selected, setSelected] = React.useState(['option1']);
return (
<div>
<Select
value={selected}
multiSelect={true}
placeholder='Select something'
options={['option1', { value: 'option2', title: 'Option 2' }]}
onMultiChange={(options) => setSelected(options.map((item) => item.value))}
/>
</div>
)
};
MultiSelect.storyName = 'multi-select';

35
stories/select.tsx Normal file
View File

@ -0,0 +1,35 @@
import { Store, withState } from '@dump247/storybook-state';
import { storiesOf } from '@storybook/react';
import * as React from 'react';
import { Select } from '../src/components';
storiesOf('Select', module)
.add('default', withState({ selected: 'option1' })(({store}: { store: Store<any> }) => (
() => (
<div>
<h4>
Selected option value: {store.state.selected}
<button className='argo-button argo-button--base' onClick={() => store.set({ selected: 'option2' })} >Select option 2</button>
</h4>
<Select
value={store.state.selected}
placeholder='Select something'
options={['option1', { value: 'option2', title: 'Option 2' }]}
onChange={(option) => store.set({ selected: option.value })}
/>
</div>
))),
).add('multi-select', withState({ selected: 'option1' })(({store}: { store: Store<any> }) => (
() => (
<div>
<Select
value={store.state.selected}
multiSelect={true}
placeholder='Select something'
options={['option1', { value: 'option2', title: 'Option 2' }]}
onMultiChange={(options) => store.set({ selected: options.map((item) => item.value) })}
/>
</div>
))),
);

View File

@ -1,11 +1,8 @@
import {default as classNames} from 'classnames'; import { storiesOf } from '@storybook/react';
import * as classNames from 'classnames';
import * as React from 'react'; import * as React from 'react';
export default { class TableExample extends React.Component<{}, {selectedIndex: number}> {
title: 'Table',
};
class TableExample extends React.Component<any, {selectedIndex: number}> {
constructor(props: any) { constructor(props: any) {
super(props); super(props);
@ -37,5 +34,5 @@ class TableExample extends React.Component<any, {selectedIndex: number}> {
} }
} }
export const Default = () => <TableExample />; storiesOf('Table', module)
Default.storyName = 'default'; .add('default', () => <TableExample/>);

View File

@ -1,14 +1,10 @@
import { storiesOf } from '@storybook/react';
import * as React from 'react'; import * as React from 'react';
import { Tabs } from '../src/components';
import { Tabs } from '../src/components/tabs/tabs'; storiesOf('Tabs', module)
.add('basic tabs', () => (
export default { <Tabs tabs={[{
title: 'Tabs',
};
export const BasicTabs = () => (
<Tabs
tabs={[{
title: 'Tab 1', title: 'Tab 1',
content: <p>Tab 1 content</p>, content: <p>Tab 1 content</p>,
key: 'tab1', key: 'tab1',
@ -18,5 +14,4 @@ export const BasicTabs = () => (
key: 'tab2', key: 'tab2',
badge: '5', badge: '5',
}]}/> }]}/>
); ));
BasicTabs.storyName = 'basic tabs';

20
stories/tsconfig.json Normal file
View File

@ -0,0 +1,20 @@
{
"compilerOptions": {
"sourceMap": true,
"noImplicitAny": true,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"experimentalDecorators": true,
"noUnusedLocals": true,
"declaration": false,
"lib": [
"es2017",
"dom"
]
},
"include": [
"./**/*",
"../src/**/*"
]
}

View File

@ -1,10 +1,7 @@
import * as PropTypes from 'prop-types'; import * as PropTypes from 'prop-types';
import * as React from 'react'; import * as React from 'react';
import { Notifications } from '../src/components/notifications/notifications'; import { Notifications, NotificationsApi, NotificationsManager, Popup, PopupApi, PopupManager, PopupProps} from '../src/components';
import { NotificationsApi, NotificationsManager } from '../src/components/notifications/notification-manager';
import { Popup, PopupProps } from '../src/components/popup/popup';
import { PopupApi, PopupManager } from '../src/components/popup/popup-manager';
export class App extends React.Component<{ children: (apis: { export class App extends React.Component<{ children: (apis: {
notifications: NotificationsApi, notifications: NotificationsApi,

19
tslint.json Normal file
View File

@ -0,0 +1,19 @@
{
"extends": [
"tslint:recommended", "tslint-react"
],
"jsRules": {},
"rules": {
"quotemark": [true, "single"],
"no-var-requires": false,
"interface-name": false,
"jsx-no-multiline-js": false,
"object-literal-sort-keys": false,
"jsx-alignment": false,
"max-line-length": [true, 180],
"jsx-no-lambda": false,
"array-type": false,
"max-classes-per-file": false
},
"rulesDirectory": []
}

1
v2/.gitignore vendored
View File

@ -1 +0,0 @@
node_modules

View File

@ -1,14 +1,13 @@
import {Key, useKeyListener} from 'react-keyhooks';
import * as React from 'react'; import * as React from 'react';
import {Key, useKeyListener} from '../../shared';
import {useClickOutside, useTimeout} from '../../utils/utils'; import {useClickOutside, useTimeout} from '../../utils/utils';
import {EffectDiv} from '../effect-div/effect-div'; import {EffectDiv} from '../effect-div/effect-div';
import {Tooltip} from '../tooltip/tooltip'; import {Tooltip} from '../tooltip/tooltip';
import {Theme} from '../theme-div/theme-div';
import './action-button.scss'; import './action-button.scss';
import {Theme} from '../theme-div/theme-div';
export interface ActionButtonProps { export interface ActionButtonProps {
// eslint-disable-next-line @typescript-eslint/ban-types
action?: Function; action?: Function;
label?: string; label?: string;
icon?: string; icon?: string;

View File

@ -8,7 +8,7 @@
&__items { &__items {
z-index: 3; z-index: 3;
position: fixed; position: absolute;
white-space: nowrap; white-space: nowrap;
max-height: 12em; max-height: 12em;
overflow-y: auto; overflow-y: auto;
@ -20,6 +20,10 @@
box-shadow: 1px 2px 2px rgba(0, 0, 0, 0.05); box-shadow: 1px 2px 2px rgba(0, 0, 0, 0.05);
line-height: 0.5em; line-height: 0.5em;
&--inverted {
bottom: 50px;
}
&__item { &__item {
z-index: 2; z-index: 2;
padding: 0.75em 0; padding: 0.75em 0;

View File

@ -14,7 +14,7 @@ export default {
export const Primary = (args: any) => ( export const Primary = (args: any) => (
<div style={{width: '50%', paddingBottom: '6em'}}> <div style={{width: '50%', paddingBottom: '6em'}}>
<Autocomplete {...args} items={['hello', 'world']} /> <Autocomplete items={['hello', 'world']} {...args} />
</div> </div>
); );

View File

@ -1,6 +1,5 @@
import {Key, KeybindingContext, KeybindingProvider, useNav} from 'react-keyhooks';
import * as React from 'react'; import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {Key, KeybindingContext, KeybindingProvider, useNav} from '../../shared';
import {Input, InputProps, SetInputFxn, useDebounce, useInput} from '../input/input'; import {Input, InputProps, SetInputFxn, useDebounce, useInput} from '../input/input';
import ThemeDiv from '../theme-div/theme-div'; import ThemeDiv from '../theme-div/theme-div';
@ -10,28 +9,23 @@ interface AutocompleteProps extends InputProps {
inputref?: React.MutableRefObject<HTMLInputElement>; inputref?: React.MutableRefObject<HTMLInputElement>;
} }
export const useAutocomplete = (init: string): [string, SetInputFxn, AutocompleteProps] => { export const useAutocomplete = (init: string, callback?: (val: string) => void): [string, SetInputFxn, AutocompleteProps] => {
const [state, setState, input] = useInput(init); const [state, setState, Input] = useInput(init);
const autocomplete = input as AutocompleteProps; const Autocomplete = Input as AutocompleteProps;
if (autocomplete.ref) { if (Autocomplete.ref) {
autocomplete.inputref = input.ref; Autocomplete.inputref = Input.ref;
delete autocomplete.ref; delete Autocomplete.ref;
} }
return [state, setState, autocomplete]; return [state, setState, Autocomplete];
}; };
export const Autocomplete = ( export const Autocomplete = (
props: React.InputHTMLAttributes<HTMLInputElement> & { props: React.InputHTMLAttributes<HTMLInputElement> & {
items: string[]; items: string[];
abbreviations?: Map<string, string>;
inputStyle?: React.CSSProperties; inputStyle?: React.CSSProperties;
onItemClick?: (item: string) => void; onItemClick?: (item: string) => void;
icon?: string; icon?: string;
inputref?: React.MutableRefObject<HTMLInputElement>; inputref?: React.MutableRefObject<HTMLInputElement>;
value: string;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
className?: string;
style?: React.CSSProperties;
} }
) => { ) => {
return ( return (
@ -44,15 +38,10 @@ export const Autocomplete = (
export const RenderAutocomplete = ( export const RenderAutocomplete = (
props: React.InputHTMLAttributes<HTMLInputElement> & { props: React.InputHTMLAttributes<HTMLInputElement> & {
items: string[]; items: string[];
abbreviations?: Map<string, string>;
inputStyle?: React.CSSProperties; inputStyle?: React.CSSProperties;
onItemClick?: (item: string) => void; onItemClick?: (item: string) => void;
icon?: string; icon?: string;
inputref?: React.MutableRefObject<HTMLInputElement>; inputref?: React.MutableRefObject<HTMLInputElement>;
value: string;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
className?: string;
style?: React.CSSProperties;
} }
) => { ) => {
const [curItems, setCurItems] = React.useState(props.items || []); const [curItems, setCurItems] = React.useState(props.items || []);
@ -60,12 +49,12 @@ export const RenderAutocomplete = (
const inputRef = props.inputref || nullInputRef; const inputRef = props.inputref || nullInputRef;
const autocompleteRef = React.useRef(null); const autocompleteRef = React.useRef(null);
const [showSuggestions, setShowSuggestions] = React.useState(false); const [showSuggestions, setShowSuggestions] = React.useState(false);
const [pos, nav, reset] = useNav(props.items?.length); const [pos, nav, reset] = useNav(props.items.length);
const menuRef = React.useRef(null); const menuRef = React.useRef(null);
React.useEffect(() => { React.useEffect(() => {
function unfocus(e: any) { function unfocus(e: any) {
if (autocompleteRef.current && !autocompleteRef.current.contains(e.target) && menuRef.current && !menuRef.current.contains(e.target)) { if (autocompleteRef.current && !autocompleteRef.current.contains(e.target)) {
setShowSuggestions(false); setShowSuggestions(false);
reset(); reset();
} }
@ -79,95 +68,58 @@ export const RenderAutocomplete = (
React.useEffect(() => { React.useEffect(() => {
const filtered = (props.items || []).filter((i) => { const filtered = (props.items || []).filter((i) => {
if (i) { return i.includes(debouncedVal);
return props.abbreviations !== undefined
? i.toLowerCase().includes(debouncedVal?.toLowerCase()) || props.abbreviations.get(i)?.includes(debouncedVal?.toLowerCase())
: i.toLowerCase().includes(debouncedVal?.toLowerCase());
}
return false;
}); });
setCurItems(filtered.length > 0 ? filtered : props.items); setCurItems(filtered.length > 0 ? filtered : props.items);
}, [debouncedVal, props.items]); }, [debouncedVal, props.items]);
React.useEffect(() => {
if (props.value !== null && props.value !== '') {
setShowSuggestions(true);
}
}, [props.value]);
const {useKeybinding} = React.useContext(KeybindingContext); const {useKeybinding} = React.useContext(KeybindingContext);
useKeybinding(Key.TAB, (e) => {
const target = { if (showSuggestions) {
combo: false, if (pos === curItems.length - 1) {
target: inputRef,
};
useKeybinding({
keys: Key.TAB,
action: () => {
if (showSuggestions) {
if (pos === curItems.length - 1) {
reset();
}
nav(1);
return true;
}
return false;
},
...target,
});
useKeybinding({
keys: Key.ESCAPE,
action: () => {
if (showSuggestions) {
reset(); reset();
setShowSuggestions(false);
if (inputRef && inputRef.current) {
inputRef.current.blur();
}
return true;
} }
return false; nav(1);
}, return true;
...target, }
return false;
}); });
useKeybinding({ useKeybinding(Key.ESCAPE, (e) => {
keys: Key.ENTER, if (showSuggestions) {
action: () => { reset();
if (showSuggestions && props.onItemClick) { setShowSuggestions(false);
props.onItemClick(curItems[pos]); if (inputRef && inputRef.current) {
setShowSuggestions(false); inputRef.current.blur();
return true;
}
return false;
},
...target,
});
useKeybinding({
keys: Key.UP,
action: () => {
if (showSuggestions) {
nav(-1);
return false;
} }
return true; return true;
}, }
...target, return false;
}); });
useKeybinding({ useKeybinding(Key.ENTER, () => {
keys: Key.DOWN, if (showSuggestions && props.onItemClick) {
action: () => { props.onItemClick(curItems[pos]);
if (showSuggestions) { setShowSuggestions(false);
nav(1);
return false;
}
return true; return true;
}, }
...target, return false;
});
useKeybinding(Key.UP, () => {
if (showSuggestions) {
nav(-1);
return false;
}
return true;
});
useKeybinding(Key.DOWN, () => {
if (showSuggestions) {
nav(1);
return false;
}
return true;
}); });
const style = props.style; const style = props.style;
@ -176,42 +128,37 @@ export const RenderAutocomplete = (
delete trimmedProps.inputStyle; delete trimmedProps.inputStyle;
delete trimmedProps.onItemClick; delete trimmedProps.onItemClick;
const [position, setPosition] = React.useState({top: 0, left: 0}); const [inverted, setInverted] = React.useState(false);
const checkDirection = () => { const checkDirection = () => {
if (autocompleteRef && autocompleteRef.current && menuRef.current) { if (autocompleteRef && autocompleteRef.current && menuRef.current && !(event.target === menuRef.current)) {
if (inputRef.current && menuRef.current) { const node = inputRef.current;
const rect = inputRef.current.getBoundingClientRect(); if (node && menuRef.current) {
const menuHeight = menuRef.current.clientHeight; const rect = node.getBoundingClientRect();
const offset = window.innerHeight - rect.bottom; const computedStyle = window.getComputedStyle(node);
const inverted = offset < menuHeight; const marginBottom = parseInt(computedStyle.marginBottom, 10) || 0;
let menuTop = rect.bottom + marginBottom;
const newPos = { if (window.innerHeight - (menuTop + menuRef.current.offsetHeight) < 30) {
top: inverted ? rect.top - menuRef.current.clientHeight : rect.top + rect.height, if (!inverted) {
left: rect.left, setInverted(true);
}; }
if (position.left !== newPos.left || position.top !== newPos.top) { } else {
setPosition(newPos); if (inverted) {
setInverted(false);
}
} }
} }
} }
}; };
React.useEffect(() => { React.useEffect(() => {
checkDirection();
document.addEventListener('scroll', checkDirection, true); document.addEventListener('scroll', checkDirection, true);
document.addEventListener('resize', checkDirection, true); document.addEventListener('resize', checkDirection, true);
return () => { return () => {
document.removeEventListener('scroll', checkDirection); document.removeEventListener('scroll', checkDirection);
document.removeEventListener('resize', checkDirection); document.removeEventListener('resize', checkDirection);
}; };
}, []); });
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (props.onChange) {
props.onChange(e);
}
};
return ( return (
<div className='autocomplete' ref={autocompleteRef} style={style as any}> <div className='autocomplete' ref={autocompleteRef} style={style as any}>
@ -220,28 +167,23 @@ export const RenderAutocomplete = (
style={props.inputStyle} style={props.inputStyle}
innerref={inputRef} innerref={inputRef}
className={(props.className || '') + ' autocomplete__input'} className={(props.className || '') + ' autocomplete__input'}
onChange={onChange} onChange={(e) => {
if (props.onChange) {
props.onChange(e);
}
}}
onFocus={() => { onFocus={() => {
setShowSuggestions(true);
checkDirection(); checkDirection();
setShowSuggestions(true);
}} }}
/> />
<div ref={menuRef}>
{ReactDOM.createPortal( <ThemeDiv className={`autocomplete__items ${inverted ? 'autocomplete__items--inverted' : ''}`} hidden={!showSuggestions || (props.items || []).length < 1}>
<ThemeDiv
className='autocomplete__items'
style={{
visibility: !showSuggestions || (props.items || []).length < 1 ? 'hidden' : 'visible',
overflow: !showSuggestions || (props.items || []).length < 1 ? 'hidden' : null,
top: position.top,
left: position.left,
}}
innerref={menuRef}>
{(curItems || []).map((i, n) => ( {(curItems || []).map((i, n) => (
<div <div
key={i} key={i}
onClick={() => { onClick={() => {
onChange({target: {value: i}} as React.ChangeEvent<HTMLInputElement>); props.onChange({target: {value: i}} as React.ChangeEvent<HTMLInputElement>);
setShowSuggestions(false); setShowSuggestions(false);
if (props.onItemClick) { if (props.onItemClick) {
props.onItemClick(i); props.onItemClick(i);
@ -251,9 +193,8 @@ export const RenderAutocomplete = (
{i} {i}
</div> </div>
))} ))}
</ThemeDiv>, </ThemeDiv>
document.body </div>
)}
</div> </div>
); );
}; };

View File

@ -17,4 +17,30 @@
background-color: $argo-color-teal-6; background-color: $argo-color-teal-6;
color: white; color: white;
} }
&__item {
height: 2em;
padding: 0 5px;
border-radius: 5px;
display: flex;
align-items: center;
background-color: white;
transition: background-color 200ms ease;
margin: 0.25em 0;
cursor: pointer;
flex-wrap: nowrap;
&__label {
text-overflow: ellipsis;
flex-grow: 0;
overflow: hidden;
white-space: nowrap;
}
&--selected {
background-color: $argo-color-teal-3;
color: $argo-color-teal-8;
font-weight: 500;
}
}
} }

View File

@ -28,3 +28,35 @@ export const Checkbox = (props: {value?: boolean; onChange?: (value: boolean) =>
</div> </div>
); );
}; };
export interface CheckboxOption {
label: string;
count?: number;
icon?: React.ReactNode;
}
export const CheckboxRow = (props: {value: boolean; onChange?: (value: boolean) => void; option: CheckboxOption}) => {
const [value, setValue] = React.useState(props.value);
React.useEffect(() => {
setValue(props.value);
}, [props.value]);
return (
<div className={`checkbox__item ${value ? 'checkbox__item--selected' : ''}`} onClick={() => setValue(!value)}>
<Checkbox
onChange={(val) => {
setValue(val);
props.onChange(val);
}}
value={value}
style={{
marginRight: '8px',
}}
/>
{props.option.icon && <div style={{marginRight: '5px'}}>{props.option.icon}</div>}
<div className='checkbox__item__label'>{props.option.label}</div>
<div style={{marginLeft: 'auto'}}>{props.option.count}</div>
</div>
);
};

View File

@ -0,0 +1,20 @@
@import '../../styles/colors.scss';
.code-editor {
height: 100%;
&--editor {
box-sizing: border-box;
padding: 10px 0;
font-family: monospace;
appearance: none;
resize: none;
width: 100%;
height: 100%;
border-radius: 5px;
border: 1px solid $argo-color-gray-4;
&:focus {
outline: none;
box-shadow: none;
}
}
}

View File

@ -0,0 +1,58 @@
import * as React from 'react';
import ThemeDiv from '../theme-div/theme-div';
import {Controlled as ReactCodeMirror} from 'react-codemirror2';
import {Editor as CMEditor, EditorConfiguration} from 'codemirror';
import 'codemirror/lib/codemirror.css';
import 'codemirror/mode/jsx/jsx';
import 'codemirror/theme/neo.css';
import './code-editor.scss';
export const Editor = (props: {setCode: (code: string) => void; init?: string}) => {
const [code, setCode] = React.useState(props.init || '');
const editorRef = React.useRef<CMEditor | null>(null);
React.useEffect(() => {
setCode(props.init);
}, [props.init]);
return (
<ThemeDiv className='code-editor'>
<ReactCodeMirror
className='code-editor--editor'
editorDidMount={(editorInstance) => {
editorRef.current = editorInstance;
}}
onBeforeChange={(editorInstance, data, newCode) => {
if (editorInstance.hasFocus()) {
setCode(newCode);
props.setCode(newCode);
}
}}
options={
{
mode: 'jsx',
autoCloseTags: true,
autoCloseBrackets: true,
theme: 'neo',
viewportMargin: 50,
lineNumbers: true,
extraKeys: {
Tab: (cm: any) => {
if (cm.somethingSelected()) {
cm.indentSelection('add');
} else {
const indent = cm.getOption('indentUnit') as number;
const spaces = Array(indent + 1).join(' ');
cm.replaceSelection(spaces);
}
},
},
} as EditorConfiguration
}
value={code}
/>
</ThemeDiv>
);
};
export default Editor;

View File

@ -11,13 +11,13 @@ interface EffectDivProps extends React.DetailedHTMLProps<React.HTMLAttributes<HT
/** /**
* EffectDiv is a component that attaches a background to a div, that can be animated with CSS transitions or otherwise. * EffectDiv is a component that attaches a background to a div, that can be animated with CSS transitions or otherwise.
* It was designed to avoid text artifacts when scaling a div; an EffectDiv allows you to easily scale JUST its background, and not its contents. It was designed to avoid text artifacts when scaling a div; an EffectDiv allows you to easily scale JUST its background, and not its contents.
*
* You can drop in replace a div with an EffectDiv, but to add a background effect, you need to: You can drop in replace a div with an EffectDiv, but to add a background effect, you need to:
*
* - Remove background styles from the main div (including border and border-radius) - Remove background styles from the main div (including border and border-radius)
* - Add the styles you removed to the `&__background` selector - Add the styles you removed to the `&__background` selector
* - Add transitions to the `&__background` selector - Add transitions to the `&__background` selector
*/ */
export const EffectDiv = (props: EffectDivProps) => { export const EffectDiv = (props: EffectDivProps) => {
const backgroundCl = appendSuffixToClasses(props.className, '__background'); const backgroundCl = appendSuffixToClasses(props.className, '__background');

View File

@ -10,6 +10,8 @@ export * from './header/header';
export * from './info-item/info-item'; export * from './info-item/info-item';
export * from './input/input'; export * from './input/input';
export * from './menu/menu'; export * from './menu/menu';
export * from './pod/pod';
export * from './replica-set/replica-set';
export * from './row/row'; export * from './row/row';
export * from './text/text'; export * from './text/text';
export {ThemeDiv} from './theme-div/theme-div'; export {ThemeDiv} from './theme-div/theme-div';

View File

@ -58,23 +58,17 @@
&--row { &--row {
display: flex; display: flex;
align-items: center; align-items: center;
margin: 0.5em 0;
flex-grow: 1; flex-grow: 1;
label { label {
margin-right: auto; margin-right: auto;
padding-right: 5px; padding-right: 5px;
} }
.info-item { .info-item {
margin: 0.25em 0;
margin-left: 5px;
}
&__container {
margin-left: auto; margin-left: auto;
display: flex; &:last-child {
min-width: 0; margin-right: 0;
padding-left: 25px; }
flex-wrap: wrap;
justify-content: flex-end;
} }
} }
} }

View File

@ -43,13 +43,12 @@ export const InfoItem = (props: InfoItemProps) => {
* Displays a right justified InfoItem (or multiple InfoItems) and a left justfied label * Displays a right justified InfoItem (or multiple InfoItems) and a left justfied label
*/ */
export const InfoItemRow = (props: {label: string | React.ReactNode; items?: InfoItemProps | InfoItemProps[]; lightweight?: boolean}) => { export const InfoItemRow = (props: {label: string | React.ReactNode; items?: InfoItemProps | InfoItemProps[]; lightweight?: boolean}) => {
let {items} = props; let {label, items} = props;
const {label} = props;
let itemComponents = null; let itemComponents = null;
if (!Array.isArray(items)) { if (!Array.isArray(items)) {
items = [items]; items = [items];
} }
itemComponents = items?.map((c, i) => <InfoItem key={`${c} ${i}`} {...c} lightweight={c?.lightweight === undefined ? props.lightweight : c?.lightweight} />); itemComponents = items.map((c, i) => <InfoItem key={`${c} ${i}`} {...c} lightweight={c.lightweight === undefined ? props.lightweight : c.lightweight} />);
return ( return (
<div className='info-item--row'> <div className='info-item--row'>
@ -58,7 +57,7 @@ export const InfoItemRow = (props: {label: string | React.ReactNode; items?: Inf
<label>{label}</label> <label>{label}</label>
</Text> </Text>
)} )}
{props.items && <div className='info-item--row__container'>{itemComponents}</div>} {props.items && <div style={{marginLeft: 'auto', display: 'flex', minWidth: 0, paddingLeft: '25px'}}>{itemComponents}</div>}
</div> </div>
); );
}; };

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