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:
push:
@ -7,9 +7,9 @@ jobs:
build-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version-file: ".nvmrc"
- run: yarn install
- run: yarn build-v2
node-version: 12
- run: yarn --cwd v2 install
- 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:
workflow_dispatch:
@ -11,12 +11,12 @@ jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
with:
ref: ${{ github.event.inputs.tag }}
- uses: actions/setup-node@v4
- uses: actions/setup-node@v1
with:
node-version-file: ".nvmrc"
node-version: 12
- run: cd v2 && npm publish
env:
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
bundle
.vscode
.idea
*.log
coverage/
.DS_STORE
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
<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
1. Install Toolset: [NodeJS](https://nodejs.org/en/download/) and [Yarn v1](https://classic.yarnpkg.com/en/docs)
1. Install Dependencies: run `yarn install`
1. Run: `yarn start` - starts the [Storybook v6](https://storybook.js.org/docs/6.5/get-started/install) 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`.
1. Install Toolset: [NodeJS](https://nodejs.org/en/download/) and [Yarn](https://yarnpkg.com)
2. Install Dependencies: From your command line, navigate to the argo-ui directory and run `yarn install` to install dependencies.
3. Run: `yarn start` - starts https://storybook.js.org/ dev server

View File

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

View File

@ -3,96 +3,98 @@
"version": "1.0.0",
"main": "./src/index.ts",
"types": "./src/index.ts",
"license": "Apache-2.0",
"license": "MIT",
"files": [
"src",
"v2"
"src"
],
"scripts": {
"lint": "eslint --ext .tsx .",
"deduplicate": "yarn-deduplicate -s fewer yarn.lock",
"build": "build-storybook -o ./dist/storybook",
"lint": "tslint -p ./src",
"test": "jest",
"utils:icons": "rm -f src/assets/fonts/* && node ./scripts/icons/generator.js",
"start": "NODE_OPTIONS='--openssl-legacy-provider' 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"
"start": "start-storybook -p 6006"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.2.1",
"@tippy.js/react": "^3.1.1",
"classnames": "^2.2.6",
"core-js": "^3.32.1",
"@fortawesome/fontawesome-free": "^5.8.1",
"@tippy.js/react": "^2.1.2",
"@types/react-autocomplete": "^1.8.5",
"@types/react-form": "^2.16.1",
"@types/react-helmet": "^6.1.0",
"classnames": "^2.2.5",
"foundation-sites": "^6.4.3",
"history": "^4.10.1",
"prop-types": "^15.8.1",
"react-autocomplete": "1.8.1",
"history": "^4.7.2",
"moment": "^2.20.1",
"prop-types": "^15.6.0",
"react-autocomplete": "^1.8.1",
"react-form": "^2.16.0",
"react-helmet": "^6.1.0",
"react-router-dom": "^4.2.2",
"react-toastify": "9.0.3",
"rxjs": "^7.8.1",
"typescript": "^4.9.5",
"uuid": "^9.0.0",
"xterm": "^4.19.0",
"xterm-addon-fit": "^0.5.0"
"react-toastify": "^5.0.1",
"rxjs": "^6.6.6",
"typescript": "^4.0.3",
"xterm": "2.4.0"
},
"peerDependencies": {
"@types/react": "^16.8.5",
"react": "^16.9.3",
"react-dom": "^16.9.3"
},
"devDependencies": {
"@babel/core": "^7.21.3",
"@storybook/addon-actions": "6.5.0-beta.1",
"@storybook/addon-controls": "6.5.0-beta.1",
"@storybook/addon-essentials": "6.5.0-beta.1",
"@storybook/addon-links": "6.5.0-beta.1",
"@storybook/addons": "6.5.0-beta.1",
"@storybook/react": "6.5.0-beta.1",
"@types/classnames": "^2.3.1",
"@babel/core": "^7.11.6",
"@dump247/storybook-state": "^1.5.0",
"@storybook/addon-actions": "^6.0.22",
"@storybook/addon-links": "^6.0.22",
"@storybook/addons": "^6.0.22",
"@storybook/react": "^6.0.22",
"@types/chai": "^4.1.2",
"@types/classnames": "^2.2.3",
"@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/history": "^4.7.8",
"@types/jest": "^26.0.15",
"@types/node": "^18.15.3",
"@types/prop-types": "^15.7.5",
"@types/react": "^16.8.5",
"@types/react-autocomplete": "^1.8.4",
"@types/prop-types": "^15.5.2",
"@types/react": "^16.9.3",
"@types/react-dom": "^16.9.3",
"@types/react-form": "^2.16.1",
"@types/react-helmet": "^6.1.6",
"@types/react-router-dom": "^4.2.2",
"@types/uuid": "^9.0.3",
"@typescript-eslint/eslint-plugin": "^5.36.1",
"@typescript-eslint/parser": "^5.36.1",
"babel-loader": "^8.2.5",
"@types/react-router-dom": "^4.2.3",
"@types/react-test-renderer": "^16.9.3",
"@types/storybook__addon-actions": "^3.0.2",
"@types/storybook__addon-links": "^3.3.0",
"@types/storybook__react": "^3.0.7",
"@types/yamljs": "^0.2.30",
"chai": "^4.1.2",
"copy-webpack-plugin": "^4.3.1",
"css-loader": "^3.6.0",
"copyfiles": "^1.2.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.7",
"enzyme-to-json": "^3.6.2",
"eslint": "^8.23.0",
"eslint-plugin-react": "^7.31.1",
"glob": "^8.0.3",
"enzyme-adapter-react-16": "^1.15.5",
"enzyme-to-json": "^3.6.1",
"foreman": "^3.0.1",
"glob": "^7.1.2",
"html-webpack-plugin": "^3.2.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^26.6.3",
"raw-loader": "^4.0.2",
"jest": "^26.6.2",
"jscs": "^3.0.7",
"node-sass": "^4.12.0",
"nodemon": "^1.14.11",
"raw-loader": "^0.5.1",
"react": "^16.9.3",
"react-dom": "^16.9.3",
"sass": "^1.55.0",
"sass-loader": "^v10.1.0",
"storybook": "6.5.0-beta.1",
"style-loader": "^2.0.0",
"ts-jest": "^26.5.6",
"ts-node": "^10.9.1",
"react-hot-loader": "^3.1.3",
"react-test-renderer": "^16.9.3",
"sass-loader": "^6.0.6",
"source-map-loader": "^0.2.3",
"style-loader": "^0.20.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",
"webpack": "^4.46.0",
"yarn-deduplicate": "^6.0.2"
"webpack": "^4.44.2",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"
},
"resolutions": {
"@types/react": "^16.8.5",
"@types/node": "14.11.2"
"@types/react": "16.9.3",
"@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 ReactForm from 'react-form';
@ -18,7 +18,7 @@ export const AutocompleteField = ReactForm.FormField((props: AutocompleteProps &
}}
inputProps={{
className: props.className,
style: {borderBottom: 'none', position: 'unset'},
style: {borderBottom: 'none'},
}}
value={value}
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 {CSSProperties, ReactNode} from 'react';
import * as classNames from 'classnames';
import * as React from 'react';
import ReactAutocomplete from 'react-autocomplete';
import * as ReactAutocomplete from 'react-autocomplete';
require('./autocomplete.scss');
export interface AutocompleteApi {
refresh(): any;
}
@ -25,8 +23,6 @@ export interface AutocompleteProps {
autoCompleteRef?: (api: AutocompleteApi) => any;
filterSuggestions?: boolean;
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) => {
@ -59,7 +55,7 @@ export const Autocomplete = (props: AutocompleteProps) => {
wrapperProps.className = classNames('select', wrapperProps.className);
return (
<ReactAutocomplete
autoHighlight={props.autoHighlight}
autoHighlight={true}
ref={(el: any) => {
if (el) {
if (el.refs.input) {
@ -109,16 +105,16 @@ export const Autocomplete = (props: AutocompleteProps) => {
shouldItemRender={(item: AutocompleteOption, val: string) => {
return !props.filterSuggestions || item.label.toLowerCase().includes(val.toLowerCase());
}}
renderMenu={function(menuItems: ReactNode[], _: string, style: CSSProperties) {
renderMenu={function(menuItems, _, style) {
if (menuItems.length === 0) {
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}
value={props.value}
renderItem={(item: any, isSelected: boolean) => (
renderItem={(item, isSelected) => (
<div className={classNames('select__option', {selected: isSelected})} key={item.label}>
{(props.renderItem && props.renderItem(item)) || item.label}
</div>

View File

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

View File

@ -1,12 +1,20 @@
import * as moment from 'moment';
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 = '';
/**
* Output a string duration from a number of seconds
*
* @param {number} props.durationS - The number of seconds.
*/
export function Duration(props: {durationS: number}) {
return <span>{formatDuration(props.durationS, 2)}</span>;
if (momentTime.diff(momentTimeStart, 'hours') === 0) {
formattedTime = ('0' + duration.minutes()).slice(-2) + ':' + ('0' + duration.seconds()).slice(-2) + ' min';
} else {
if (momentTime.diff(momentTimeStart, 'days') > 0) {
formattedTime += momentTime.diff(momentTimeStart, 'days') + ' days' + (props.allowNewLines ? '<br>' : ' ');
}
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 ReactForm from 'react-form';
import { Select as ArgoSelect, SelectOption, SelectProps } from '../select/select';
import { v1 as uuid } from 'uuid';
const uuid = require('uuid/v1');
require('./form-field.scss');

View File

@ -2,13 +2,13 @@ require('../styles/main.scss');
export { Utils } from './utils';
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 { 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 { type TopBarProps, type Toolbar, type TopBarFilter } from './top-bar/top-bar';
export { type Tab, Tabs } from './tabs/tabs';
export { TopBarProps, Toolbar, TopBarFilter } from './top-bar/top-bar';
export { Tab, Tabs } from './tabs/tabs';
export { Duration } from './duration';
export { SlidingPanel } from './sliding-panel/sliding-panel';
export { LogsViewer } from './logs-viewer/logs-viewer';
@ -16,7 +16,7 @@ export * from './notifications/notifications';
export * from './notifications/notification-manager';
export * from './popup/popup';
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 { Tooltip } from './tooltip/tooltip';
export * from './ticker';

View File

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

View File

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

View File

@ -1,5 +1,5 @@
@import '../../styles/config';
@import 'node_modules/xterm/css/xterm';
@import 'node_modules/xterm/dist/xterm';
.logs-viewer {
font: normal 13px/1.2 'Courier', sans-serif;
@ -11,3 +11,16 @@
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 { 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');
@ -16,43 +17,27 @@ export interface LogsViewerProps {
}
export class LogsViewer extends React.Component<LogsViewerProps> {
private terminal: Terminal;
private fitAddon: FitAddon;
private terminal: any;
private subscription: Subscription | null = null;
constructor(props: LogsViewerProps) {
super(props);
}
public UNSAFE_componentWillReceiveProps(nextProps: LogsViewerProps) {
public componentWillReceiveProps(nextProps: LogsViewerProps) {
if (this.props.source.key !== nextProps.source.key) {
this.refresh(nextProps.source);
}
}
public initTerminal(container: HTMLElement) {
this.fitAddon = new FitAddon();
this.terminal = new Terminal({
scrollback: 99999,
allowTransparency: true,
theme: {
background: 'transparent',
foreground: '#495763',
},
theme: 'ax',
});
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);
return false
}
});
this.terminal.open(container);
this.terminal.fit();
}
public componentDidMount() {
@ -71,14 +56,12 @@ export class LogsViewer extends React.Component<LogsViewerProps> {
);
}
public shouldComponentUpdate() {
public shouldComponentUpdate(prevProps: LogsViewerProps) {
return false;
}
private refresh(source: LogsSource) {
if (this.terminal) {
this.terminal.reset();
}
this.ensureUnsubscribed();
const onLoadComplete = () => {
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 React from 'react';
@ -10,11 +10,6 @@ require('./nav-bar.scss');
export interface NavBarProps {
items: Array<{ path: string; iconClassName: string; title: string; }>;
version?: () => React.ReactElement;
style?: NavBarStyle;
}
export interface NavBarStyle {
backgroundColor?: 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) => {
const locationPath = context.router.route.location.pathname;
const navBarStyle = {
...(props.style?.backgroundColor && {background: `linear-gradient(to bottom, ${props.style?.backgroundColor}, #999`}),
};
return (
<div className={classNames('nav-bar', {
'nav-bar--compact': (props.items || []).length >= 10,
})} style={navBarStyle}>
})}>
<div className='nav-bar__logo'>
<img src='assets/images/logo.png' alt='Argo'/>
<div className='nav-bar__version'>{props.version && props.version()}</div>

View File

@ -37,7 +37,9 @@ export class NavigationManager implements NavigationApi {
path = `${path}?${urlQuery}`;
}
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');
} else {
if (options.replace) {

View File

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

View File

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

View File

@ -14,7 +14,7 @@ export interface PopupApi {
},
customIcon?: {name: string, color: string},
titleColor?: string,
defaultValues?: FormValues,
defaultValues?: {},
): Promise<FormValues | null>;
}
@ -41,8 +41,8 @@ export class PopupManager implements PopupApi {
content,
footer: (
<div>
<button qe-id='argo-popup-ok-button' className='argo-button argo-button--base' onClick={() => closeAndResolve(true)}>OK</button>
<button qe-id='argo-popup-cancel-button' className='argo-button argo-button--base-o' onClick={() => closeAndResolve(false)}>Cancel</button>
<button qe-id='argo-popup-ok-button' className='argo-button argo-button--base' onClick={() => closeAndResolve(true)}>OK</button> <button
qe-id='argo-popup-cancel-button' className='argo-button argo-button--base-o' onClick={() => closeAndResolve(false)}>Cancel</button>
</div>
),
});
@ -58,7 +58,7 @@ export class PopupManager implements PopupApi {
},
customIcon?: { name: string, color: string },
titleColor?: string,
defaultValues?: FormValues,
defaultValues?: {},
): Promise<FormValues | null> {
return new Promise((resolve) => {
const closeAndResolve = (result: FormValues | null) => {
@ -98,8 +98,8 @@ export class PopupManager implements PopupApi {
),
footer: (
<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-cancel-button' className='argo-button argo-button--base-o' onClick={() => closeAndResolve(null)}>Cancel</button>
<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-cancel-button' className='argo-button argo-button--base-o' onClick={() => closeAndResolve(null)}>Cancel</button>
</div>
),
});

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,6 @@
@import 'node_modules/foundation-sites/scss/util/util';
@import '../../styles/config';
@import '../../styles/theme';
$sliding-panel-header-height: 50px;
$sliding-panel-footer-height: 64px;
@ -51,10 +50,7 @@ $sliding-panel-middle-width: 600px;
right: 0;
bottom: 0;
width: 80%;
@include themify($themes) {
background-color: themed('background-2');
color: themed('text-2');
}
background-color: #fff;
transition: right .5s;
@include breakpoint(medium down) {
@ -101,17 +97,13 @@ $sliding-panel-middle-width: 600px;
padding: 0 30px;
line-height: $sliding-panel-header-height;
color: $argo-color-gray-5;
@include themify($themes) {
background-color: themed('light-argo-gray-2');
border-bottom: 1px solid themed('border');
}
background-color: $argo-color-gray-2;
border-bottom: 1px solid #c6cfd1;
font-weight: 500;
font-size: .925em;
.sliding-panel--off-canvas & {
@include themify($themes) {
background-color: themed('light-argo-gray-2');
}
background-color: $argo-color-gray-2;
}
strong {
@ -128,9 +120,8 @@ $sliding-panel-middle-width: 600px;
position: relative;
height: 100%;
overflow: auto;
@include themify($themes) {
background-color: themed('light-argo-gray-2');
}
background-color: $argo-color-gray-2;
.sliding-panel:not(.sliding-panel--no-padding) & {
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 { Key, KeybindingContext, KeybindingProvider } from '../../../v2';
export interface SlidingPanelProps extends React.Props<any> {
isShown?: boolean;
@ -16,43 +15,7 @@ export interface SlidingPanelProps extends React.Props<any> {
require('./sliding-panel.scss');
export const SlidingPanel = (props: SlidingPanelProps) => {
return (
<KeybindingProvider>
<RenderSlidingPanel {...props} />
</KeybindingProvider>
);
};
const RenderSlidingPanel = (props: SlidingPanelProps) => {
const {useKeybinding} = React.useContext(KeybindingContext);
const closeButtonRef = React.useRef(null);
const bodyDivRef = React.useRef(null);
const panelHeaderDivRef = React.useRef(null);
const panelFooterDivRef = React.useRef(null);
React.useEffect(() => {
if (closeButtonRef && closeButtonRef.current) {
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 (
export const SlidingPanel = (props: SlidingPanelProps) => (
<div className={classNames('sliding-panel', {
'sliding-panel--has-header': !!props.header,
'sliding-panel--has-footer': !!props.footer,
@ -63,24 +26,21 @@ const RenderSlidingPanel = (props: SlidingPanelProps) => {
'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}>
<button className='sliding-panel__close' aria-hidden='true' onClick={() => props.onClose && props.onClose()}>
<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}>
<div className={classNames('sliding-panel__header', {'sliding-panel__header--close-btn-right-padding': props.hasCloseButton})}>
{props.header}
</div>
)}
<div className='sliding-panel__body' ref={bodyDivRef} tabIndex={-1}>
<div className='sliding-panel__body'>
{props.children}
</div>
{props.footer && (
<div className='sliding-panel__footer' ref={panelFooterDivRef} tabIndex={-1}>
<div className='sliding-panel__footer'>
{props.footer}
</div>
)}
@ -88,4 +48,3 @@ const RenderSlidingPanel = (props: SlidingPanelProps) => {
<div className='sliding-panel__outside' onClick={() => props.onClose && props.onClose()}/>
</div>
);
};

View File

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

View File

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

View File

@ -1,13 +1,14 @@
import * as moment from 'moment';
import * as React from 'react';
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;
constructor(props: {intervalMs?: number, children?: ((time: Date) => React.ReactNode)}) {
constructor(props: {intervalMs?: number, children?: ((time: moment.Moment) => React.ReactNode)}) {
super(props);
this.state = { time: new Date() };
this.state = { time: moment() };
this.ensureSubscribed();
}
@ -27,7 +28,7 @@ export class Ticker extends React.Component<{intervalMs?: number, disabled?: boo
if (this.props.disabled) {
this.ensureUnsubscribed();
} 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 'tippy.js/dist/tippy.css';
import 'tippy.js/themes/light.css';
export const Tooltip = ( props: TippyProps ) => (
<Tippy animation='fade' appendTo={document.body} theme='light' interactive={true} {...props} />
);
export const Tooltip = ( props: any ) => <Tippy animation='fade' arrow='true' {...props} />;

View File

@ -1,19 +1,12 @@
@import '../../styles/config';
@import 'node_modules/foundation-sites/scss/util/util';
@import '../../styles/theme';
.top-bar {
line-height: $top-bar-height;
@include themify($themes) {
background: themed('background-2');
}
background: $white-color;
transition: right .5s;
border-bottom: 1px solid $argo-color-gray-2;
.theme-dark & {
border-color: $argo-color-gray-7;
}
&__left-side {
padding-left: 20px;
white-space: nowrap;
@ -28,9 +21,7 @@
float: left;
font-weight: 500;
font-size: .925em;
@include themify($themes) {
color: themed('light-argo-teal-7');
}
color: $argo-color-teal-7;
text-transform: uppercase;
}
@ -61,9 +52,7 @@
height: $top-bar-height;
font-weight: 500;
font-size: .8em;
@include themify($themes) {
color: themed('text-2');
}
color: $argo-color-gray-8;
a {
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 { Link } from 'react-router-dom';
@ -27,7 +27,7 @@ export interface ActionMenu {
export interface Toolbar {
filter?: TopBarFilter<any>;
breadcrumbs?: { title: string | React.ReactNode, path?: string; }[];
breadcrumbs?: { title: string, path?: string; }[];
tools?: React.ReactNode;
actionMenu?: ActionMenu;
}
@ -68,17 +68,17 @@ const renderFilter = (filter: TopBarFilter<any>) => (
</DropDown>
);
const renderBreadcrumbs = (breadcrumbs: { title: string | React.ReactNode, path?: string; }[]) => (
const renderBreadcrumbs = (breadcrumbs: { title: string, path?: string; }[]) => (
<div className='top-bar__breadcrumbs'>
{(breadcrumbs || []).map((breadcrumb, i) => {
const nodes = [];
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 {
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) {
nodes.push(<span key={`${i}_sep`} className='top-bar__sep'/>);
nodes.push(<span key={`${breadcrumb.title}_sep`} className='top-bar__sep'/>);
}
return nodes;
})}

View File

@ -5,6 +5,45 @@ export function isPromise<T>(obj: any): obj is PromiseLike<T> {
}
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> {
const observable = val as Observable<T>;
if (observable && observable.subscribe && observable.forEach) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,43 @@
<?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">
<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"/>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.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 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>

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/solid';
@import 'node_modules/@fortawesome/fontawesome-free/scss/brands';
@import 'node_modules/@fortawesome/fontawesome-free/scss/regular';
@include foundation-global-styles;
@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": {
"outDir": "./../bundle",
"outDir": "./../../bundle",
"sourceMap": true,
"noImplicitAny": true,
"module": "commonjs",
"target": "es6",
"target": "es5",
"jsx": "react",
"esModuleInterop": true,
"experimentalDecorators": true,
"noUnusedLocals": true,
"declaration": true,
"skipLibCheck": true,
"lib": [
"es2017",
"dom"
],
"typeRoots": [
"./node_modules/@types"
"../node_modules/@types"
]
},
"include": ["./**/*"],
"exclude": ["node_modules", "./**/*.test.ts", "./**/*.test.tsx", "./**/*.stories.tsx"]
"include": [
"./**/*"
]
}

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 { App } from './utils';
@ -8,18 +10,13 @@ function loadData(input: string): Promise<string> {
return new Promise((resolve) => window.setTimeout(() => resolve(`hello ${input}`), 50));
}
export default {
title: 'Data Loader',
};
export const LoadingDataAsynchronously = () => {
const [input, setInput] = React.useState('world');
return (
storiesOf('Data Loader', module)
.add('loading data asynchronously', withState({ input: 'world' })(({store}: { store: Store<any> }) => (
<App>
{() => (
<React.Fragment>
<input value={input} onChange={(e) => setInput(e.target.value)}/>
<DataLoader input={input} load={loadData}>
<input value={store.state.input} onChange={(e) => store.set({ input: e.target.value })}/>
<DataLoader input={store.state.input} load={(input) => loadData(input)}>
{(data) => (
<div>
{data}
@ -29,6 +26,4 @@ export const LoadingDataAsynchronously = () => {
</React.Fragment>
)}
</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 { Form, Text } from 'react-form';
import { FormField, FormSelect } from '../src/components';
import { FormField, FormSelect } from '../src/components/form-field/form-field';
export default {
title: 'Forms',
};
export const Default = () => {
return (
storiesOf('Forms', module)
.add('default', () => (
<Form>
{(api) => (
<form style={{padding: '1em'}}>
@ -24,6 +20,4 @@ export const Default = () => {
</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 { storiesOf } from '@storybook/react';
import * as React from 'react';
import { Checkbox as ReactCheckbox} from 'react-form';
import { Text } from 'react-form';
import { Checkbox } from '../src/components/checkbox';
import { FormField } from '../src/components/form-field/form-field';
import { Checkbox, FormField } from '../src/components';
import { App } from './utils';
export default {
title: 'Popup',
};
export const Confirmation = () => {
return (
storiesOf('Popup', module)
.add('confirmation', () => (
<App>
{(apis) => (
<button className='argo-button argo-button--base' onClick={async () => {
@ -21,36 +18,24 @@ export const Confirmation = () => {
}}>Click me</button>
)}
</App>
);
}
Confirmation.storyName = 'confirmation';
export const ConfirmationWithCustomFormInside = () => {
const [checked, setChecked] = React.useState(false);
return (
(
)).add('confirmation with custom form inside', withState({ checked: false })(({store}: { store: Store<any> }) => (
<App>
{(apis) => (
<div>
<button className='argo-button argo-button--base' onClick={async () => {
const confirmed = await apis.popup.confirm('Do it!', () => (
<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>
));
action('Confirmed')(confirmed);
}}>Click me</button>
<p>Checked?: {JSON.stringify(checked)}</p>
<p>Checked?: {JSON.stringify(store.state.checked)}</p>
</div>
)}
</App>
)
);
}
ConfirmationWithCustomFormInside.storyName = 'confirmation with custom form inside';
export const Prompt = () => {
return (
),
)).add('prompt', () => (
<App>
{(apis) => (
<button className='argo-button argo-button--base' onClick={async () => {
@ -63,23 +48,16 @@ export const Prompt = () => {
<FormField label='Last Name' formApi={api} field='lastName' component={Text} />
</div>
</React.Fragment>
), {
validate: (vals) => ({
), { validate: (vals) => ({
firstName: !vals.firstName && 'First Name is required',
lastName: !vals.lastName && 'Last Name is required',
})
});
})});
action('Prompt values')(values);
}}>Click me</button>
)}
</App>
);
}
Prompt.storyName = 'prompt';
export const PromptWithCustomSubmit = () => {
return (
)).add('prompt with custom submit', () => (
<App>
{(apis) => (
<button className='argo-button argo-button--base' onClick={() => {
@ -109,12 +87,7 @@ export const PromptWithCustomSubmit = () => {
}}>Click me</button>
)}
</App>
);
}
PromptWithCustomSubmit.storyName = 'prompt with custom submit';
export const PromptWithRedTitleAndIconWithCustomSubmit = () => {
return (
)).add('prompt with red title and icon, with custom submit', () => (
<App>
{(apis) => (
<button className='argo-button argo-button--base' onClick={() => {
@ -146,12 +119,7 @@ export const PromptWithRedTitleAndIconWithCustomSubmit = () => {
}}>Click me</button>
)}
</App>
);
}
PromptWithRedTitleAndIconWithCustomSubmit.storyName = 'prompt with red title and icon, with custom submit';
export const PromptWithYellowTitleAndIconThreeFieldsAndCustomSubmitVerticalCenterLayoutOfIcon = () => {
return (
)).add('prompt with yellow title and icon, three fields and custom submit. Vertical center layout of icon', () => (
<App>
{(apis) => (
<button className='argo-button argo-button--base' onClick={() => {
@ -190,12 +158,7 @@ export const PromptWithYellowTitleAndIconThreeFieldsAndCustomSubmitVerticalCente
}}>Click me</button>
)}
</App>
);
}
PromptWithYellowTitleAndIconThreeFieldsAndCustomSubmitVerticalCenterLayoutOfIcon.storyName = 'prompt with yellow title and icon, three fields and custom submit. Vertical center layout of icon';
export const PromptWithGreenClockIconAndCustomSubmit = () => {
return (
)).add('prompt with green clock icon and custom submit', () => (
<App>
{(apis) => (
<button className='argo-button argo-button--base' onClick={() => {
@ -222,16 +185,12 @@ export const PromptWithGreenClockIconAndCustomSubmit = () => {
}
},
},
{ name: 'argo-icon-clock', color: 'success' });
{ name: 'argo-icon-clock', color: 'success'}
);
}}>Click me</button>
)}
</App>
);
}
PromptWithGreenClockIconAndCustomSubmit.storyName = 'prompt with green clock icon and custom submit';
export const PromptWithJustHeadersAndParagraphs = () => {
return (
)).add('prompt with just headers and paragraphs', () => (
<App>
{(apis) => (
<button className='argo-button argo-button--base' onClick={async () => {
@ -242,17 +201,13 @@ export const PromptWithJustHeadersAndParagraphs = () => {
<h4>This is another h4 header</h4>
<p>This is a paragraph</p>
</div>
));
)
);
action('Prompt values')(values);
}}>Click me</button>
)}
</App>
);
}
PromptWithJustHeadersAndParagraphs.storyName = 'prompt with just headers and paragraphs';
export const PromptWithOnlyParagraphsAdditionalTopPaddingIsOptionalForTheFirstParagraph = () => {
return (
)).add('prompt with only paragraphs. Additional top padding is optional for the first paragraph', () => (
<App>
{(apis) => (
<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>This is another paragraph</p>
</div>
));
)
);
action('Prompt values')(values);
}}>Click me</button>
)}
</App>
);
}
PromptWithOnlyParagraphsAdditionalTopPaddingIsOptionalForTheFirstParagraph.storyName = 'prompt with only paragraphs. Additional top padding is optional for the first paragraph';
export const PromptWithReactCheckboxThatIsCheckedByDefaultUsernameDefaultSetToAdmin = () => {
return (
)).add('prompt with React Checkbox that is checked by default; Username default set to admin', () => (
<App>
{(apis) => (
<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'}} />
</div>
<div className='argo-form-row'>
<ReactCheckbox id='popup-react-checkbox' field='checkboxField' />{' '}
<label htmlFor='popup-react-checkbox'>This is a React Checkbox</label>
<ReactCheckbox id='popup-react-checkbox' field='checkboxField'/> <label htmlFor='popup-react-checkbox'>This is a React Checkbox</label>
</div>
</React.Fragment>
), {
),
{
validate: (vals) => ({
username: !vals.username && 'Username is required',
password: !vals.password && 'Password is required',
@ -311,6 +262,4 @@ export const PromptWithReactCheckboxThatIsCheckedByDefaultUsernameDefaultSetToAd
}}>Click me</button>
)}
</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';
export default {
title: 'Table',
};
class TableExample extends React.Component<any, {selectedIndex: number}> {
class TableExample extends React.Component<{}, {selectedIndex: number}> {
constructor(props: any) {
super(props);
@ -37,5 +34,5 @@ class TableExample extends React.Component<any, {selectedIndex: number}> {
}
}
export const Default = () => <TableExample />;
Default.storyName = 'default';
storiesOf('Table', module)
.add('default', () => <TableExample/>);

View File

@ -1,14 +1,10 @@
import { storiesOf } from '@storybook/react';
import * as React from 'react';
import { Tabs } from '../src/components';
import { Tabs } from '../src/components/tabs/tabs';
export default {
title: 'Tabs',
};
export const BasicTabs = () => (
<Tabs
tabs={[{
storiesOf('Tabs', module)
.add('basic tabs', () => (
<Tabs tabs={[{
title: 'Tab 1',
content: <p>Tab 1 content</p>,
key: 'tab1',
@ -18,5 +14,4 @@ export const BasicTabs = () => (
key: 'tab2',
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 React from 'react';
import { Notifications } from '../src/components/notifications/notifications';
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';
import { Notifications, NotificationsApi, NotificationsManager, Popup, PopupApi, PopupManager, PopupProps} from '../src/components';
export class App extends React.Component<{ children: (apis: {
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 {Key, useKeyListener} from '../../shared';
import {useClickOutside, useTimeout} from '../../utils/utils';
import {EffectDiv} from '../effect-div/effect-div';
import {Tooltip} from '../tooltip/tooltip';
import {Theme} from '../theme-div/theme-div';
import './action-button.scss';
import {Theme} from '../theme-div/theme-div';
export interface ActionButtonProps {
// eslint-disable-next-line @typescript-eslint/ban-types
action?: Function;
label?: string;
icon?: string;

View File

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

View File

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

View File

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

View File

@ -17,4 +17,30 @@
background-color: $argo-color-teal-6;
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>
);
};
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.
* 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:
*
* - Remove background styles from the main div (including border and border-radius)
* - Add the styles you removed to the `&__background` selector
* - Add transitions to the `&__background` selector
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:
- Remove background styles from the main div (including border and border-radius)
- Add the styles you removed to the `&__background` selector
- Add transitions to the `&__background` selector
*/
export const EffectDiv = (props: EffectDivProps) => {
const backgroundCl = appendSuffixToClasses(props.className, '__background');

View File

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

View File

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

View File

@ -43,13 +43,12 @@ export const InfoItem = (props: InfoItemProps) => {
* 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}) => {
let {items} = props;
const {label} = props;
let {label, items} = props;
let itemComponents = null;
if (!Array.isArray(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 (
<div className='info-item--row'>
@ -58,7 +57,7 @@ export const InfoItemRow = (props: {label: string | React.ReactNode; items?: Inf
<label>{label}</label>
</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>
);
};

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