Move insomnia-components in the insomnia package (#5259)

* Move insomnia-components in the insomnia package

* remove extra tooltip components

* remove button from portal plugin

* remove insomniaComponent injection

* move spec editor sidebar

* move breadcrumb component

* move toggle-switch

* move table/notice-table

* remove unused switch component

* move header to app-header

* move card

* move card-container to wrapper-home

* Remove unused multi-switch and radio-group

* move svg icon and auto-run convert-svg command

* move list-group

* move button components into themed-button

* move dropdown

* remove auto-conversion of svg from npm scripts

* track svg icon components to git

* remove react-switch and framer-motion

* remove sidebar panel, fix lint

* remove animate blocks

* fix types, remove value from dropdown item

* remove button

* ignore svgr config

* update dropdown unit tests

* allow unreachable

* oauth e2e test: update locator to make the Create button specific

* fix type errors

Co-authored-by: jackkav <jackkav@gmail.com>
This commit is contained in:
James Gatz 2022-10-12 22:26:11 +02:00 committed by GitHub
parent b5f5ad8716
commit 09cf77c4ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
263 changed files with 1588 additions and 20060 deletions

View File

@ -14,5 +14,4 @@ screenshots/
**/__snapshots__/
**/dist/
**/.cache/
**/svgr/
*.md

View File

@ -1,3 +0,0 @@
dist
assets/svgr
src/assets/svgr

View File

@ -1,15 +0,0 @@
const { OFF, TYPESCRIPT_CONVERSION } = require('eslint-config-helpers');
/** @type { import('eslint').Linter.Config } */
module.exports = {
extends: '../../.eslintrc.js',
settings: {
react: {
version: 'detect',
},
},
rules: {
'@typescript-eslint/no-explicit-any': OFF(TYPESCRIPT_CONVERSION),
'@typescript-eslint/no-unnecessary-type-constraint': OFF('by an unfortunate quirk of how TypeScript differentiates between casts and JSX, this it is necessary for generic JSX to do extend unknown'),
},
};

View File

@ -1,4 +0,0 @@
.cache
src/assets/svgr
assets/svgr
dist

View File

@ -1,7 +0,0 @@
# Ignore everything by default
# NOTE: NPM publish will never ignore package.json, package-lock.json, README, LICENSE, CHANGELOG
# https://npm.github.io/publishing-pkgs-docs/publishing/the-npmignore-file.html
*
# Don't ignore dist folder
!dist/**

View File

@ -1 +0,0 @@
16.14.2

View File

@ -1,14 +0,0 @@
# insomnia-components
`insomnia-components` is a React component library of UI elements needed to build [Insomnia](https://insomnia.rest).
## SVGs
We use [SVGR](https://react-svgr.com) on the source SVGs to minify and convert them to React components. This conversion happens during `npm run bootstrap`.
The generated icons can automatically be bound to the theme library built into Insomnia, by following a few rules. In the source SVG:
1. All background colors should be black (#000)
1. All foreground colors should be white (#FFF)
SVGR converts these colors to `fill=''` and `fill='currentColor'` respectively. These icons are exposed via `svg-icon.js`, which sets the css `fill` and `color` of the SVG according to the required theme.

View File

@ -1,14 +0,0 @@
/** @type { import('@jest/types').Config.InitialOptions } */
module.exports = {
preset: '../../jest-preset.js',
globals: {
'ts-jest': {
isolatedModules: true,
},
},
fakeTimers: {
enableGlobally: true,
},
setupFilesAfterEnv: ['./src/jest/setup-after-env.ts'],
testEnvironment: 'jsdom',
};

File diff suppressed because it is too large Load Diff

View File

@ -1,78 +0,0 @@
{
"name": "insomnia-components",
"version": "3.6.1-beta.0",
"author": "Kong <office@konghq.com>",
"description": "Insomnia UI component library",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/Kong/insomnia.git",
"directory": "packages/insomnia-components"
},
"bugs": {
"url": "https://github.com/Kong/insomnia/issues"
},
"main": "./dist/commonjs/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"require": "./dist/commonjs/index.js",
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"scripts": {
"prebuild": "npm run convert-svg",
"build:source": "vite build",
"build:types": "tsc --build tsconfig.build.json",
"build": "concurrently --names source,types \"npm run build:source\" \"npm run build:types\"",
"clean": "rimraf dist && npm run clean:svg",
"clean:svg": "rimraf src/assets/svgr",
"clean:svg:index": "rimraf src/assets/svgr/index.tsx",
"convert-svg": "npm run clean:svg && svgr --config-file svgr.config.js --out-dir src/assets/svgr src/assets && npm run clean:svg:index",
"jest:watch": "jest --watch",
"prepare": "npm run convert-svg && npm run build",
"lint": "eslint . --ext .js,.ts,.tsx --cache",
"lint:fix": "npm run lint -- --fix",
"test": "jest",
"watch": "cross-env ESBUILD_WATCH=true esr esbuild.ts"
},
"devDependencies": {
"@jest/globals": "^28.1.0",
"@svgr/cli": "^5.5.0",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^12.1.5",
"@types/ramda": "^0.27.44",
"@types/react": "^17.0.39",
"@types/react-dom": "^17.0.11",
"concurrently": "^7.0.0",
"cross-env": "^7.0.3",
"esbuild": "^0.14.27",
"esbuild-runner": "^2.2.1",
"jest": "^28.1.0",
"jest-environment-jsdom": "^28.0.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-use": "^17.2.4",
"styled-components": "^5.3.3",
"type-fest": "^2.12.0",
"typescript": "^4.5.5",
"vite": "^2.9.1"
},
"dependencies": {
"@types/styled-components": "^5.1.23",
"class-autobind-decorator": "^3.0.1",
"classnames": "^2.3.1",
"framer-motion": "4.1.17",
"fuzzysort": "^1.2.1",
"ramda": "^0.27.1",
"react-switch": "^6.0.0"
},
"peerDependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-use": "^17.2.4",
"styled-components": "^5.3.3"
}
}

View File

@ -1,7 +0,0 @@
import styled from 'styled-components';
export const CardContainer = styled.div`
display: flex;
flex-wrap: wrap;
padding-top: var(--padding-md);
`;

View File

@ -1,34 +0,0 @@
import React, { FC, memo } from 'react';
import styled, { css } from 'styled-components';
const StyledDivider = styled.div<{ hasName: boolean }>`
border-bottom: 1px solid var(--hl-sm);
overflow: visible !important;
height: 0;
margin: var(--padding-md) var(--padding-md) var(--padding-md) var(--padding-md);
${({ hasName }) =>
!hasName &&
css`
margin: var(--padding-xs) 0;
`}
`;
const StyledLabel = styled.span`
position: relative;
left: calc(-1 * var(--padding-sm));
top: -0.7rem;
color: var(--hl);
padding-right: 1em;
background: var(--color-bg);
font-size: var(--font-size-xs);
text-transform: uppercase;
font-family: var(--font-default);
`;
export const DropdownDivider: FC = memo(({ children }) => (
<StyledDivider hasName={!!children}>
{children && <StyledLabel>{children}</StyledLabel>}
</StyledDivider>
));
DropdownDivider.displayName = 'DropdownDivider';

View File

@ -1,157 +0,0 @@
import { autoBindMethodsForReact } from 'class-autobind-decorator';
import classnames from 'classnames';
import React, { PureComponent, ReactNode } from 'react';
import styled, { css } from 'styled-components';
interface ControlledDropdown<T> {
value: T;
onClick?: (value: T, event: React.MouseEvent<HTMLButtonElement>) => void | Promise<void>;
}
interface UncontrolledDropdown {
onClick?: React.MouseEventHandler<HTMLButtonElement>;
}
const isControlledInput = <T extends unknown>(dropdown: DropdownItemProps<T>): dropdown is ControlledDropdown<T> => (
Object.prototype.hasOwnProperty.call(dropdown, 'value')
);
const isUncontrolledInput = <T extends unknown>(dropdown: DropdownItemProps<T>): dropdown is UncontrolledDropdown => (
!isControlledInput(dropdown)
);
export type DropdownItemProps<T = string> = {
buttonClass?: string;
stayOpenAfterClick?: boolean;
disabled?: boolean;
children?: ReactNode;
icon?: ReactNode;
right?: ReactNode;
className?: string;
color?: string;
selected?: boolean;
} & (ControlledDropdown<T> | UncontrolledDropdown);
const StyledButton = styled.button<{ selected?: boolean }>`
display: flex;
align-items: center;
min-width: 15rem;
font-size: var(--font-size-md);
text-align: left;
padding-right: var(--padding-md);
padding-left: var(--padding-sm);
height: var(--line-height-xs);
width: 100%;
color: var(--color-font) !important;
background-color: var(--color-bg);
white-space: nowrap;
margin: 0;
border: 0;
&:hover:not(:disabled),
&:active:not(:disabled) {
background: var(--hl-xs);
}
&:active:not(:disabled) {
background: var(--hl-md);
}
${({ selected }) => selected && css`
background: var(--hl-xs) !important;
font-weight: bold;
`};
&:disabled {
opacity: 0.5;
}
`;
const StyledRightNode = styled.span`
display: flex;
align-items: center;
color: var(--hl-xl);
margin-left: auto;
padding-left: var(--padding-lg);
`;
const StyledInner = styled.div`
width: 100%;
display: flex;
align-items: center;
flex-direction: row;
`;
const StyledText = styled.div`
white-space: nowrap;
display: flex;
align-items: center;
& > *:not(:first-child) {
margin-left: 0.3em;
}
input + label {
padding-top: 0px !important;
}
`;
const StyledIconContainer = styled.div`
display: flex;
align-items: center;
padding-left: var(--padding-xs);
padding-right: var(--padding-md);
`;
@autoBindMethodsForReact
export class DropdownItem<T> extends PureComponent<DropdownItemProps<T>> {
_handleClick(event: React.MouseEvent<HTMLButtonElement>) {
const { stayOpenAfterClick, onClick, disabled } = this.props;
if (stayOpenAfterClick) {
event.stopPropagation();
}
if (!onClick || disabled) {
return;
}
if (isControlledInput<T>(this.props)) {
this.props.onClick?.(this.props.value, event);
} else if (isUncontrolledInput(this.props)) {
this.props.onClick?.(event);
}
}
render() {
const {
buttonClass,
children,
className,
color,
disabled,
right,
icon,
selected,
} = this.props;
const styles = color ? { color } : {};
const inner = (
<StyledInner className={className}>
<StyledText style={styles}>{children}</StyledText>
</StyledInner>
);
return (
<StyledButton
className={classnames(className, buttonClass)}
type="button"
onClick={this._handleClick}
disabled={disabled}
selected={selected}
>
{icon && <StyledIconContainer>{icon}</StyledIconContainer>}
{inner}
{right && <StyledRightNode>{right}</StyledRightNode>}
</StyledButton>
);
}
}

View File

@ -1,594 +0,0 @@
import { autoBindMethodsForReact } from 'class-autobind-decorator';
import classnames from 'classnames';
import fuzzySort from 'fuzzysort';
import { any, equals } from 'ramda';
import React, {
CSSProperties,
Fragment,
PureComponent,
ReactChild,
ReactFragment,
ReactNode,
ReactPortal,
} from 'react';
import ReactDOM from 'react-dom';
import styled, { css } from 'styled-components';
import { SvgIcon } from '../svg-icon';
import { DropdownDivider } from './dropdown-divider';
import { DropdownItem } from './dropdown-item';
export interface DropdownProps {
children: ReactNode;
renderButton: ReactNode | ((props: { open: boolean }) => void);
right?: boolean;
outline?: boolean;
wide?: boolean;
onOpen?: () => void;
onHide?: () => void;
className?: string;
style?: CSSProperties;
beside?: boolean;
}
const StyledDropdown = styled.div`
position: relative;
display: inline-block;
box-sizing: border-box;
text-align: left;
&:focus {
outline: none;
}
`;
const StyledNoResults = styled.div`
text-align: center;
padding: var(--padding-md);
color: var(--color-warning);
`;
const StyledBackdrop = styled.div`
position: fixed;
z-index: 9999;
left: 0;
right: 0;
top: 0;
bottom: 0;
content: ' ';
`;
const StyledFilter = styled.div<{ filtering: boolean }>`
border: 1px solid var(--hl-md);
margin: var(--padding-xs);
width: auto;
border-radius: var(--radius-sm);
display: flex;
flex-direction: row;
align-items: center;
padding-left: var(--padding-sm);
color: var(--hl);
// Can't "display: none" this because we need to be able to type
// in it. So, we'll just store it off screen
${({ filtering }) =>
filtering
? css`
position: static;
left: auto;
`
: css`
position: absolute;
left: -9999999px;
`}
input {
margin: 0;
padding: var(--padding-xs) var(--padding-sm);
color: var(--color-font);
outline: 0;
border: 0;
}
`;
const StyledMenu = styled.div`
z-index: 99999;
position: fixed;
top: 0;
left: 0;
border: 1px solid var(--hl-sm);
box-shadow: 0 0 1rem 0 rgba(0, 0, 0, 0.1);
box-sizing: border-box;
background: var(--color-bg);
max-height: 100px;
// Separate it from it's surroundings a bit
margin: var(--padding-xxs) 3px;
padding-top: var(--radius-md);
padding-bottom: var(--radius-md);
border-radius: var(--radius-md);
overflow: auto;
&:focus {
outline: 0;
}
li {
button:disabled {
opacity: 0.5;
}
}
li button:hover:not(:disabled),
li.active button:not(:disabled) {
background: var(--hl-xs);
}
li.active button:not(:disabled) {
background: var(--hl-sm);
}
`;
interface State {
open: boolean;
dropUp: boolean;
filter: string;
filterVisible: boolean;
filterItems: number[] | null;
filterActiveIndex: number;
forcedPosition: null | {
x: number;
y: number;
};
uniquenessKey: number;
}
const isComponent = (match: string) => (child: ReactNode) => any(equals(match), [
// @ts-expect-error this is required by our API for Dropdown
child.type.name,
// @ts-expect-error this is required by our API for Dropdown
child.type.displayName,
]);
const isDropdownItem = isComponent(DropdownItem.name);
const isDropdownDivider = isComponent(DropdownDivider.name);
@autoBindMethodsForReact
export class Dropdown extends PureComponent<DropdownProps, State> {
// Save body overflow so we can revert it when needed
defaultBodyOverflow = document.body.style.overflow;
dropdownsContainer: HTMLElement | null = null;
_dropdownList: HTMLDivElement | null = null;
_cachedMinWidth: string | null = null;
_node: HTMLDivElement | null = null;
_filter: HTMLInputElement | null = null;
state: State = {
open: false,
dropUp: false,
// Filter Stuff
filter: '',
filterVisible: false,
filterItems: null,
filterActiveIndex: 0,
// Position
forcedPosition: null,
// Use this to force new menu every time dropdown opens
uniquenessKey: 0,
};
constructor(props: DropdownProps) {
super(props);
this.dropdownsContainer = document.querySelector('#dropdowns-container');
if (!this.dropdownsContainer) {
this.dropdownsContainer = document.createElement('div');
this.dropdownsContainer.id = 'dropdowns-container';
// @ts-expect-error -- TSCONVERSION
this.dropdownsContainer.style = [
// Position off-screen so we don't see it
'position: fixed',
'right: -90000px',
'left: -90000px', // Make same size as viewport so we can use it to calculate the
// position of the dropdown accurately.
'width: 100vw',
'height: 100vh',
].join('; ');
document.body.appendChild(this.dropdownsContainer);
}
}
_handleCheckFilterSubmit(event: React.KeyboardEvent<HTMLInputElement>) {
if (event.key === 'Enter') {
// Listen for the Enter key and "click" on the active list item
const selector = `li[data-filter-index="${this.state.filterActiveIndex}"] button`;
this._dropdownList?.querySelector<HTMLButtonElement>(selector)?.click();
}
}
_handleChangeFilter(event: React.ChangeEvent<HTMLInputElement>) {
const newFilter = event.target.value;
// Nothing to do if the filter didn't change
if (newFilter === this.state.filter) {
return;
}
// Filter the list items that are filterable (have data-filter-index property)
const filterItems = [];
// @ts-expect-error -- TSCONVERSION
for (const listItem of (this._dropdownList?.querySelectorAll('li') || [])) {
if (!listItem.hasAttribute('data-filter-index')) {
continue;
}
const hasMatch = !!fuzzySort.single(newFilter, listItem.textContent);
if (!newFilter || hasMatch) {
const filterIndex = listItem.getAttribute('data-filter-index');
filterItems.push(parseInt(filterIndex, 10));
}
}
this.setState({
filter: newFilter,
filterItems: newFilter ? filterItems : null,
filterActiveIndex: filterItems[0] || -1,
filterVisible: this.state.filterVisible ? true : newFilter.length > 0,
});
}
_handleDropdownNavigation(event: React.KeyboardEvent<HTMLDivElement>) {
const { key, shiftKey } = event;
// Handle tab and arrows to move up and down dropdown entries
const { filterItems, filterActiveIndex } = this.state;
if (['Tab', 'ArrowDown', 'ArrowUp'].includes(key)) {
event.preventDefault();
const items = filterItems || [];
if (!filterItems) {
for (const li of (this._dropdownList?.querySelectorAll('li') || [])) {
if (li.hasAttribute('data-filter-index')) {
const filterIndex = li.getAttribute('data-filter-index');
items.push(parseInt(filterIndex, 10));
}
}
}
const i = items.indexOf(filterActiveIndex);
if (key === 'ArrowUp' || (key === 'Tab' && shiftKey)) {
const nextI = i > 0 ? items[i - 1] : items[items.length - 1];
this.setState({
filterActiveIndex: nextI,
});
} else {
this.setState({
filterActiveIndex: items[i + 1] || items[0],
});
}
}
this._filter?.focus();
}
_handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
if (!this.state.open) {
return;
}
// Catch all key presses (like global app hotkeys) if we're open
event.stopPropagation();
this._handleDropdownNavigation(event);
if (event.key === 'Escape') {
this.hide();
}
}
_checkSizeAndPosition() {
if (!this.state.open || !this._dropdownList) {
return;
}
// Get dropdown menu
const dropdownList = this._dropdownList;
// Compute the size of all the menus
let dropdownBtnRect = this._node?.getBoundingClientRect();
const bodyRect = this.dropdownsContainer?.getBoundingClientRect();
const dropdownListRect = dropdownList.getBoundingClientRect();
const { forcedPosition } = this.state;
if (forcedPosition && bodyRect !== undefined) {
dropdownBtnRect = {
left: forcedPosition.x,
right: bodyRect.width - forcedPosition.x,
top: forcedPosition.y,
bottom: bodyRect.height - forcedPosition.y,
width: 100,
height: 10,
x: 0,
y: 0,
toJSON: () => {},
};
}
// Should it drop up?
const bodyHeight = bodyRect?.height || 0;
const dropdownTop = dropdownBtnRect?.top || 0;
const dropUp = dropdownTop > bodyHeight - 200;
// Reset all the things so we can start fresh
this._dropdownList.style.left = 'initial';
this._dropdownList.style.right = 'initial';
this._dropdownList.style.top = 'initial';
this._dropdownList.style.bottom = 'initial';
this._dropdownList.style.minWidth = this._cachedMinWidth || 'initial';
this._dropdownList.style.maxWidth = 'initial';
// Cache the width so we can use it later. This is because the dropdown width
// could shrink once we start filtering items out.
if (!this._cachedMinWidth) {
this._cachedMinWidth = dropdownListRect.width + 'px';
}
const screenMargin = 6;
const { right, wide } = this.props;
if (dropdownBtnRect === undefined) {
return;
}
if (!bodyRect) {
return;
}
if (right || wide) {
const { right: originalRight } = dropdownBtnRect;
// Prevent dropdown from squishing against left side of screen
const right = Math.max(dropdownListRect.width + screenMargin, originalRight);
const { beside } = this.props;
const offset = beside ? dropdownBtnRect.width - 40 : 0;
this._dropdownList.style.right = `${bodyRect.width - right + offset}px`;
this._dropdownList.style.maxWidth = `${Math.min(dropdownListRect.width, right + offset)}px`;
}
if (!right || wide) {
const { left: originalLeft } = dropdownBtnRect;
const { beside } = this.props;
const offset = beside ? dropdownBtnRect.width - 40 : 0;
// Prevent dropdown from squishing against right side of screen
const left = Math.min(bodyRect.width - dropdownListRect.width - screenMargin, originalLeft);
this._dropdownList.style.left = `${left + offset}px`;
this._dropdownList.style.maxWidth = `${Math.min(
dropdownListRect.width,
bodyRect.width - left - offset,
)}px`;
}
if (dropUp) {
const { top } = dropdownBtnRect;
this._dropdownList.style.bottom = `${bodyRect.height - top}px`;
this._dropdownList.style.maxHeight = `${top - screenMargin}px`;
} else {
const { bottom } = dropdownBtnRect;
this._dropdownList.style.top = `${bottom}px`;
this._dropdownList.style.maxHeight = `${bodyRect.height - bottom - screenMargin}px`;
}
}
_handleClick(event: React.MouseEvent<HTMLDivElement>) {
event.preventDefault();
event.stopPropagation();
this.toggle();
}
static _handleMouseDown(event: React.MouseEvent<HTMLDivElement>) {
// Intercept mouse down so that clicks don't trigger things like drag and drop.
event.preventDefault();
}
_getFlattenedChildren(children: ReactNode) {
let newChildren: (ReactChild | ReactFragment | ReactPortal)[] = [];
for (const child of React.Children.toArray(children)) {
if (!child) {
// Ignore null components
continue;
}
// @ts-expect-error -- TSCONVERSION
if (child.type === Fragment) {
newChildren = [
...newChildren,
// @ts-expect-error -- TSCONVERSION
...this._getFlattenedChildren(child.props.children),
];
} else if (Array.isArray(child)) {
newChildren = [
...newChildren,
...this._getFlattenedChildren(child),
];
} else {
newChildren.push(child);
}
}
return newChildren;
}
componentDidUpdate() {
this._checkSizeAndPosition();
}
hide() {
// Focus the dropdown button after hiding
if (this._node) {
const button = this._node.querySelector('button');
button?.focus();
}
this.setState({
open: false,
});
this.props.onHide?.();
}
show(filterVisible = false, forcedPosition = null) {
const bodyHeight = document.body.getBoundingClientRect().height;
const dropdownTop = this._node?.getBoundingClientRect().top || 0;
const dropUp = dropdownTop > bodyHeight - 200;
this.setState({
open: true,
dropUp,
forcedPosition,
filterVisible,
filter: '',
filterItems: null,
filterActiveIndex: -1,
uniquenessKey: this.state.uniquenessKey + 1,
});
this.props.onOpen?.();
}
toggle(filterVisible = false) {
if (this.state.open) {
this.hide();
document.body.style.overflow = this.defaultBodyOverflow;
} else {
this.show(filterVisible);
// Prevent body from scrolling when dropdown is open
document.body.style.overflow = 'hidden';
}
}
render() {
const { className, style, children, renderButton } = this.props;
const {
open,
uniquenessKey,
filterVisible,
filterActiveIndex,
filterItems,
filter,
} = this.state;
const menuClasses = classnames({
'theme--dropdown__menu': true,
});
const allChildren = this._getFlattenedChildren(children);
const visibleChildren = allChildren.filter((child, i) => {
if (!isDropdownItem(child)) {
return true;
}
// It's visible if its index is in the filterItems
return !filterItems || filterItems.includes(i);
});
const dropdownItems: ReactNode[] = [];
for (let i = 0; i < allChildren.length; i++) {
const child = allChildren[i];
if (isDropdownItem(child)) {
const active = i === filterActiveIndex;
const hide = !visibleChildren.includes(child);
dropdownItems.push(
<li
key={i}
data-filter-index={i}
className={classnames({ active, hide })}
>
{child}
</li>,
);
} else if (isDropdownDivider(child)) {
const currentIndex = visibleChildren.indexOf(child);
const nextChild = visibleChildren[currentIndex + 1];
// Only show the divider if the next child is a DropdownItem
if (nextChild && isDropdownItem(nextChild)) {
dropdownItems.push(<li key={i}>{child}</li>);
}
}
}
const noResults = filter && filterItems && filterItems.length === 0;
const button =
typeof renderButton === 'function'
? renderButton({
open,
})
: renderButton;
return (
<StyledDropdown
style={style}
className={className}
ref={ref => {
this._node = ref;
}}
onClick={this._handleClick}
onKeyDown={this._handleKeyDown}
tabIndex={-1}
onMouseDown={Dropdown._handleMouseDown}
>
<Fragment key="button">{button}</Fragment>
{this.dropdownsContainer && ReactDOM.createPortal(
<div
key="item"
className={menuClasses}
aria-hidden={!open}
>
{open && <StyledBackdrop className="theme--transparent-overlay" />}
{open && (
<StyledMenu
key={uniquenessKey}
ref={ref => {
this._dropdownList = ref;
}}
tabIndex={-1}
>
<StyledFilter filtering={filterVisible}>
<SvgIcon icon="search" />
<input
type="text"
onChange={this._handleChangeFilter}
ref={ref => {
this._filter = ref;
// Automatically focus the filter element when mounted so we can start typing
if (this._filter) {
this._filter.focus();
}
}}
onKeyPress={this._handleCheckFilterSubmit}
/>
</StyledFilter>
{noResults && <StyledNoResults>No match</StyledNoResults>}
<ul
className={classnames({
hide: noResults,
})}
>
{dropdownItems}
</ul>
</StyledMenu>
)}
</div>,
this.dropdownsContainer,
)}
</StyledDropdown>
);
}
}

View File

@ -1,3 +0,0 @@
export { Dropdown, type DropdownProps } from './dropdown';
export { DropdownItem, type DropdownItemProps } from './dropdown-item';
export { DropdownDivider } from './dropdown-divider';

View File

@ -1,48 +0,0 @@
import classNames from 'classnames';
import React, { FC, memo, ReactNode } from 'react';
import styled from 'styled-components';
export interface HeaderProps {
className?: string;
gridLeft?: ReactNode;
gridCenter?: ReactNode;
gridRight?: ReactNode;
}
const StyledHeader = styled.div`
border-bottom: 1px solid var(--hl-md);
padding: var(--padding-xxs) var(--padding-sm);
display: grid;
grid-template-columns: 2fr 1.5fr 2fr;
grid-template-rows: 1fr;
grid-template-areas: 'header_left header_center header_right';
.header_left {
grid-area: header_left;
text-align: left;
display: flex;
align-items: center;
}
.header_center {
grid-area: header_center;
text-align: center;
display: flex;
align-items: center;
}
.header_right {
grid-area: header_right;
text-align: right;
display: flex;
align-items: center;
justify-content: flex-end;
}
`;
export const Header: FC<HeaderProps> = memo(({ className, gridLeft, gridCenter, gridRight }) => (
<StyledHeader className={classNames('app-header theme--app-header', className)}>
<div className="header_left">{gridLeft}</div>
<div className="header_center">{gridCenter}</div>
<div className="header_right">{gridRight}</div>
</StyledHeader>
));
Header.displayName = 'Header';

View File

@ -1,32 +0,0 @@
import React, { CSSProperties, FC, ReactNode } from 'react';
import { SvgIcon } from './svg-icon';
import { Tooltip } from './tooltip';
interface Props {
children: ReactNode;
position?: 'bottom' | 'top' | 'right' | 'left';
delay?: number;
className?: string;
style?: CSSProperties;
info?: boolean;
}
export const HelpTooltip: FC<Props> = ({
children,
className,
style,
info,
position,
delay,
}) => (
<Tooltip
position={position}
delay={delay}
className={className}
message={children}
style={style}
>
{info ? <SvgIcon icon="info" /> : <SvgIcon icon="question" />}
</Tooltip>
);

View File

@ -1,27 +0,0 @@
export * from './button';
export * from './dropdown';
export * from './sidebar';
export * from './list-group';
export { Breadcrumb, type BreadcrumbProps, type CrumbProps } from './breadcrumb';
export { CardContainer } from './card-container';
export { Card, type CardProps } from './card';
export { Header, type HeaderProps } from './header';
export { MultiSwitch } from './multi-switch';
export { NoticeTable, type NoticeTableProps, type Notice } from './notice-table';
export { RadioButtonGroup, type RadioButtonGroupProps } from './radio-button-group';
export { SvgIcon, type SvgIconProps, type IconEnum, type ThemeEnum, type IconId } from './svg-icon';
export { Switch, type SwitchProps, type SwitchItem } from './switch';
export {
Table,
type TableProps,
TableBody,
TableData,
type TableDataProps,
TableHead,
TableHeader,
type TableHeaderProps,
TableRow,
} from './table';
export { ToggleSwitch, type ToggleSwitchProps } from './toggle-switch';
export { Tooltip, type TooltipProps } from './tooltip';

View File

@ -1,2 +0,0 @@
import '@testing-library/jest-dom';
import '@testing-library/jest-dom/extend-expect';

View File

@ -1,40 +0,0 @@
import React, { FunctionComponent } from 'react';
import styled from 'styled-components';
import type { RadioButtonGroupProps } from './radio-button-group';
import { RadioButtonGroup } from './radio-button-group';
const ThemedButtonGroup = styled(RadioButtonGroup)<RadioButtonGroupProps>`
&& {
font-weight: 500;
background: var(--hl-xs);
color: var(--color-font);
border: 0;
border-radius: 100px;
align-content: space-evenly;
padding: var(--padding-xxs);
transform: scale(0.9);
transformOrigin: 'center';
label {
padding: 0;
}
span {
text-transform: uppercase;
padding: var(--padding-xs) var(--padding-xxs);
color: var(--hl);
background: transparent;
font-size: var(--font-size-xs);
margin: 0 auto;
min-width: 4rem;
}
input:checked + span {
color: var(--color-font);
background: var(--color-bg);
}
}
`;
export const MultiSwitch: FunctionComponent<RadioButtonGroupProps> = props => <ThemedButtonGroup {...props} />;

View File

@ -1,80 +0,0 @@
import React, { FunctionComponent, useCallback } from 'react';
import styled from 'styled-components';
export interface RadioButtonGroupProps {
name: string;
onChange: (value: string) => any;
choices: {
label: string;
value: string;
}[];
className?: string;
selectedValue: string;
}
const StyledRadioButtonGroup = styled.div`
display: flex;
justify-content: space-between;
padding: var(--padding-xs);
border: 1px solid var(--hl-xs);
border-radius: var(--radius--sm);
& > * :not(:last-child) {
margin-right: var(--padding-xs);
}
`;
const StyledRadioButtonBtn = styled.label`
cursor: pointer;
color: var(--color-font);
flex-grow: 1;
justify-content: center;
input {
display: none;
}
span {
text-align: center;
padding: var(--padding-sm) var(--padding-xs);
display: block;
border-radius: var(--line-height-sm);
}
input:checked + span {
background-color: var(--hl-xs);
}
`;
export const RadioButtonGroup: FunctionComponent<RadioButtonGroupProps> = ({
name,
choices,
onChange,
className,
selectedValue,
}) => {
const handleChange = useCallback(event => {
if (typeof onChange !== 'function') {
return;
}
onChange(event.currentTarget.value);
}, [onChange]);
return (
<StyledRadioButtonGroup className={className}>
{choices.map(({ label, value }) => (
<StyledRadioButtonBtn key={value}>
<input
type="radio"
name={name}
value={value}
checked={selectedValue === value}
onChange={handleChange}
/>
<span>{label}</span>
</StyledRadioButtonBtn>
))}
</StyledRadioButtonGroup>
);
};

View File

@ -1,30 +0,0 @@
import { motion } from 'framer-motion';
import React, { FunctionComponent, ReactNode } from 'react';
import styled from 'styled-components';
export interface SidebarPanelProps {
children: ReactNode;
childrenVisible: boolean;
}
const StyledPanel = styled(motion.div)`
height: 0;
`;
export const SidebarPanel: FunctionComponent<SidebarPanelProps> = ({ childrenVisible, children }) => (
<StyledPanel
initial={{
height: childrenVisible ? '100%' : '0px',
}}
animate={{
height: childrenVisible ? '100%' : '0px',
}}
transition={{
duration: 0.2,
ease: 'easeInOut',
delay: 0,
}}
>
{children}
</StyledPanel>
);

View File

@ -1,94 +0,0 @@
import React, { Fragment, FunctionComponent } from 'react';
import styled from 'styled-components';
export interface SwitchItem {
label: string;
selected: boolean;
}
export interface SwitchProps {
className?: string;
onClick?: (event: React.SyntheticEvent<HTMLInputElement>) => any;
optionItems?: SwitchItem[];
}
const StyledSwitch = styled.div`
margin: auto;
.switch {
position: relative;
height: 34px;
width: 140px;
background: var(--hl-xs);
border-radius: 17px;
}
.switch-label {
position: relative;
font-weight: bold;
z-index: 2;
float: left;
width: 67px;
line-height: 34px;
font-size: 11px;
color: var(--hl);
text-align: center;
cursor: pointer;
padding-top: 0px;
}
.switch-label-off {
padding-left: 2px;
}
.switch-label-on {
padding-right: 2px;
}
.switch-input {
display: none;
}
.switch-input:checked + .switch-label {
color: var(--color-font);
text-shadow: 0 1px rgba(255, 255, 255, 0.25);
}
.switch-input:checked + .switch-label-on ~ .switch-selection {
left: 71px;
}
.switch-selection {
position: absolute;
z-index: 1;
top: 4px;
left: 4px;
display: block;
width: 65px;
height: 26px;
border-radius: 13px;
background-color: var(--color-bg);
transition: left 0.2s ease-out;
}
`;
export const Switch: FunctionComponent<SwitchProps> = ({ className, onClick, optionItems }) => (
<StyledSwitch className={className}>
<div className="switch">
{optionItems?.map((item, i) => {
return (
<Fragment key={item.label}>
<input
type="radio"
className="switch-input"
name="switch"
onClick={onClick}
value={item.label}
id={item.label}
defaultChecked={item.selected}
/>
<label
htmlFor={item.label}
className={`switch-label ${i === 0 ? 'switch-label-off' : 'switch-label-on'}`}
>
{item.label}
</label>
</Fragment>
);
})}
<span className="switch-selection" />
</div>
</StyledSwitch>
);

View File

@ -1,112 +0,0 @@
import { ReactNode } from 'react';
import styled from 'styled-components';
/***********/
/* <table> *
/***********/
export interface TableProps {
children: ReactNode;
striped?: boolean;
outlined?: boolean;
compact?: boolean;
headings?: ReactNode[];
}
export const Table = styled.table<TableProps>`
width: 100%;
border-spacing: 0;
border-collapse: collapse;
td,
th {
padding: ${({ compact }) => (compact ? 'var(--padding-xs)' : 'var(--padding-sm)')}
${({ compact }) => (compact ? 'var(--padding-sm)' : 'var(--padding-md)')};
}
${({ striped }) =>
striped &&
`
tbody tr:nth-child(odd) {
background: var(--hl-xs);
}`}
${({ outlined }) =>
outlined &&
`
& {
th {
background: var(--hl-xxs);
}
&,
td {
border: 1px solid var(--hl-sm);
}
tr.table--no-outline-row td {
border-left: 0;
border-right: 0;
}
& {
border-radius: 3px;
border-collapse: unset;
}
td {
border-left: 0;
border-bottom: 0;
&:last-child {
border-right: 0;
}
}
}`}
`;
/********/
/* <tr> */
/********/
export const TableRow = styled.tr``;
/********/
/* <td> */
/********/
export interface TableDataProps {
compact?: boolean;
align?: 'center' | 'left';
}
export const TableData = styled.td<TableDataProps>`
vertical-align: top;
padding: 0 var(--padding-md);
text-align: ${({ align }) => align || 'left'};
`;
/********/
/* <th> */
/********/
export interface TableHeaderProps {
compact?: boolean;
align?: 'center' | 'left';
}
export const TableHeader = styled.th<TableHeaderProps>`
vertical-align: top;
padding: 0 var(--padding-md);
text-align: ${({ align }) => align || 'left'};
`;
/***********/
/* <thead> */
/***********/
export const TableHead = styled.thead``;
/***********/
/* <tbody> */
/***********/
export const TableBody = styled.tbody``;

View File

@ -1,85 +0,0 @@
import React, { FunctionComponent, ReactNode, useCallback, useEffect, useState } from 'react';
import ReactSwitch from 'react-switch';
import styled, { css } from 'styled-components';
// @ts-expect-error Issue with commonJS export in vite
const Switch = ReactSwitch.default;
export interface ToggleSwitchProps {
labelClassName?: string;
switchClassName?: string;
checked?: boolean;
disabled?: boolean;
onChange(checked: boolean, arg1: Event, arg2: string): void | Promise<void>;
label?: ReactNode;
}
const ThemedSwitch = styled(Switch)<{ checked: boolean }>`
&& {
.react-switch-bg {
background: ${({ checked }) => (checked ? 'var(--color-surprise)' : 'var(--hl-xl)')} !important;
${({ checked }) => checked && css`
path {
fill: var(--color-font-surprise) !important
}
`}
}
vertical-align: middle;
}
`;
const StyledLabel = styled.label`
display: inline-flex;
flex-direction: row;
align-items: center;
span {
margin-left: var(--padding-md);
}
`;
export const ToggleSwitch: FunctionComponent<ToggleSwitchProps> = ({
labelClassName,
switchClassName = '',
checked: checkedProp,
onChange,
disabled,
label,
...props
}) => {
const [checked, setChecked] = useState(Boolean(checkedProp));
// If prop changes and differs from state, update state
useEffect(() => {
setChecked(Boolean(checkedProp));
}, [checkedProp]);
const callback = useCallback((c, a, b) => {
setChecked(c);
onChange(c, a, b);
}, [onChange]);
const toggle = (
<ThemedSwitch
className={switchClassName}
checked={checked}
disabled={disabled}
onChange={callback}
height={20}
width={40}
{...(props as Record<string, any>)}
/>
);
if (label) {
return (
<StyledLabel className={labelClassName}>
{toggle}
<span>{label}</span>
</StyledLabel>
);
} else {
return toggle;
}
};

View File

@ -1,81 +0,0 @@
import { jest } from '@jest/globals';
import { ByRoleMatcher, ByRoleOptions, fireEvent, render } from '@testing-library/react';
import React from 'react';
import { Tooltip } from './tooltip';
type QueryByRole = (
text: ByRoleMatcher,
options?: ByRoleOptions | undefined,
) => HTMLElement | null;
const expectNoTooltip = (queryByRole: QueryByRole) => {
expect(queryByRole('tooltip', { hidden: true })).toBeNull();
expect(queryByRole('tooltip', {})).toBeNull();
};
describe('<Tooltip />', () => {
it('should show and hide the tooltip correctly', async () => {
const childText = 'some child';
const delay = 200;
const message = 'message';
const { getByRole, getByText, queryByRole, unmount } = render(
<Tooltip message={message} delay={delay}>
{childText}
</Tooltip>,
);
expect(getByRole('tooltip', { hidden: true })).toBeTruthy();
fireEvent.mouseEnter(getByText(childText));
// Should open after the configured delay, wait a sufficient amount
jest.advanceTimersByTime(delay * 2);
expect(getByRole('tooltip')).toBeTruthy();
fireEvent.mouseLeave(getByText(childText));
// Should close after 100ms default, wait a sufficient amount
jest.advanceTimersByTime(delay);
expect(getByRole('tooltip', { hidden: true })).toBeTruthy();
unmount();
expectNoTooltip(queryByRole);
});
it('should not render tooltip if no message exists', () => {
const childText = 'some child';
const message = '';
const { queryByRole, getByText } = render(<Tooltip message={message}>{childText}</Tooltip>);
expectNoTooltip(queryByRole);
expect(getByText(childText)).toBeTruthy();
});
it('should unmount successfully if message is empty in the first render then and non-empty after update', async () => {
const childText = 'some child';
const initialMessage = '';
const newMessage = 'message';
const { queryByRole, getByRole, getByText, queryByText, rerender, unmount } = render(
<Tooltip message={initialMessage}>{childText}</Tooltip>,
);
expectNoTooltip(queryByRole);
expect(getByText(childText)).toBeTruthy();
rerender(<Tooltip message={newMessage}>{childText}</Tooltip>);
expect(getByRole('tooltip', { hidden: true })).toBeTruthy();
expect(getByText(childText)).toBeTruthy();
unmount();
expectNoTooltip(queryByRole);
expect(queryByText(childText)).toBeFalsy();
});
});

View File

@ -1,254 +0,0 @@
import { autoBindMethodsForReact } from 'class-autobind-decorator';
import classnames from 'classnames';
import React, { CSSProperties, MouseEvent, PureComponent, ReactNode } from 'react';
import ReactDOM from 'react-dom';
import styled from 'styled-components';
export interface TooltipProps {
children: ReactNode;
message: ReactNode;
position?: 'bottom' | 'top' | 'right' | 'left';
className?: string;
delay?: number;
selectable?: boolean;
wide?: boolean;
style?: CSSProperties;
}
interface State {
visible: boolean;
movedToBody: boolean;
}
const StyledTooltip = styled.div`
position: relative;
display: inline-block;
`;
const StyledTooltipBubble = styled.div`
position: fixed;
left: -999999px;
opacity: 0;
background: var(--color-bg);
border: 1px solid var(--hl-sm);
box-shadow: 0 0 1em rgba(0, 0, 0, 0.1);
color: var(--color-font);
padding: var(--padding-sm) var(--padding-md);
border-radius: var(--radius-sm);
font-size: var(--font-size-sm);
max-width: 20rem;
text-align: center;
z-index: 10;
white-space: normal !important;
word-wrap: break-word;
&.tooltip__bubble--visible {
opacity: 1;
z-index: 99999;
transition: opacity 200ms;
// Back to normal
height: auto;
line-height: normal;
}
&.tooltip__bubble--wide {
max-width: 30rem;
}
`;
@autoBindMethodsForReact
export class Tooltip extends PureComponent<TooltipProps, State> {
_showTimeout: NodeJS.Timeout | null = null;
_hideTimeout: NodeJS.Timeout | null = null;
_tooltip: HTMLDivElement | null = null;
_bubble: HTMLDivElement | null = null;
_id = Math.random() + '';
state: State = {
visible: false,
movedToBody: false,
};
_handleStopClick(event: MouseEvent) {
event.stopPropagation();
}
_handleMouseEnter() {
if (this._showTimeout !== null) {
clearTimeout(this._showTimeout);
}
if (this._hideTimeout !== null) {
clearTimeout(this._hideTimeout);
}
this._showTimeout = setTimeout(() => {
const tooltip = this._tooltip;
const bubble = this._bubble;
if (!tooltip) {
return;
}
if (!bubble) {
return;
}
const tooltipRect = tooltip.getBoundingClientRect();
const bubbleRect = bubble.getBoundingClientRect();
const margin = 3;
let left = 0;
let top = 0;
switch (this.props.position) {
case 'right':
top = tooltipRect.top - bubbleRect.height / 2 + tooltipRect.height / 2;
left = tooltipRect.left + tooltipRect.width + margin;
break;
case 'left':
top = tooltipRect.top - bubbleRect.height / 2 + tooltipRect.height / 2;
left = tooltipRect.left - bubbleRect.width - margin;
break;
case 'bottom':
top = tooltipRect.top + tooltipRect.height + margin;
left = tooltipRect.left - bubbleRect.width / 2 + tooltipRect.width / 2;
break;
case 'top':
default:
top = tooltipRect.top - bubbleRect.height - margin;
left = tooltipRect.left - bubbleRect.width / 2 + tooltipRect.width / 2;
break;
}
bubble.style.left = `${Math.max(0, left)}px`;
bubble.style.top = `${Math.max(0, top)}px`;
this.setState({
visible: true,
});
}, this.props.delay || 400);
}
_handleMouseLeave(): void {
if (this._showTimeout !== null) {
clearTimeout(this._showTimeout);
}
if (this._hideTimeout !== null) {
clearTimeout(this._hideTimeout);
}
this._hideTimeout = setTimeout(() => {
this.setState({
visible: false,
});
const bubble = this._bubble;
if (!bubble) {
return;
}
// Reset positioning stuff
bubble.style.left = '';
bubble.style.top = '';
bubble.style.bottom = '';
bubble.style.right = '';
}, 100);
}
_getContainer() {
let container = document.querySelector<HTMLElement>('#tooltips-container');
if (!container) {
container = document.createElement('div');
container.id = 'tooltips-container';
container.style.zIndex = '1000000';
container.style.position = 'relative';
document.body && document.body.appendChild(container);
}
return container;
}
_moveBubbleToBody() {
if (this._bubble) {
const el = ReactDOM.findDOMNode(this._bubble);
el && this._getContainer().appendChild(el);
this.setState({
movedToBody: true,
});
}
}
_removeBubbleFromBody() {
if (this._bubble) {
const el = ReactDOM.findDOMNode(this._bubble);
if (this._getContainer().contains(el)) {
el && this._getContainer().removeChild(el);
}
this.setState({
movedToBody: false,
});
}
}
componentDidMount() {
// Move the element to the body so we can position absolutely
this._moveBubbleToBody();
}
componentDidUpdate() {
// If the bubble has not been moved to body, move it.
// This can happen if there is no message during the first mount but a message is provided during on a subsequent render.
if (!this.state.movedToBody) {
this._moveBubbleToBody();
}
}
componentWillUnmount() {
// Remove the element from the body
this._removeBubbleFromBody();
}
render() {
const { children, message, className, selectable, wide, style } = this.props;
const { visible } = this.state;
if (!message) {
return children;
}
const tooltipClasses = classnames(className, 'tooltip');
const bubbleClasses = classnames('tooltip__bubble theme--tooltip', {
'tooltip__bubble--visible': visible,
'tooltip__bubble--wide': wide,
selectable: selectable,
});
return (
<StyledTooltip
className={tooltipClasses}
ref={ref => {
this._tooltip = ref;
}}
id={this._id}
onMouseEnter={this._handleMouseEnter}
onMouseLeave={this._handleMouseLeave}
style={style}
>
<StyledTooltipBubble
className={bubbleClasses}
onClick={this._handleStopClick}
role="tooltip"
aria-hidden={!visible}
aria-describedby={this._id}
ref={ref => {
this._bubble = ref;
}}
>
{message}
</StyledTooltipBubble>
{children}
</StyledTooltip>
);
}
}

View File

@ -1,21 +0,0 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"emitDeclarationOnly": true,
"experimentalDecorators": true,
"isolatedModules": true,
"jsx": "react",
"lib": ["DOM"],
"outDir": "dist",
"rootDir": "src",
},
"include": [
"src"
],
"exclude": [
"**/*.test.ts",
"**/*.test.tsx",
"src/jest",
],
}

View File

@ -1,16 +0,0 @@
{
"extends": "./tsconfig.build.json",
"compilerOptions": {
"rootDir": "."
},
"include": [
".eslintrc.js",
"jest.config.js",
"src",
"svgr.config.js",
"vite.config.ts",
],
"exclude": [
"dist",
],
}

View File

@ -1,30 +0,0 @@
import { defineConfig } from 'vite';
export default defineConfig(({ mode }) => {
const __DEV__ = mode !== 'production';
return {
build: {
sourcemap: __DEV__,
lib: {
entry: './src/index.ts',
fileName: format => {
if (format === 'cjs') {
return 'commonjs/index.js';
}
return 'index.js';
},
// We use CommonJS output for running tests in jest.
formats: ['cjs', 'es'],
name: 'insomnia-components',
},
emptyOutDir: false,
rollupOptions: {
external: ['react', 'react-dom', 'styled-components', 'react-use'],
},
},
define: {
'process.env.NODE_ENV': JSON.stringify(mode),
},
};
});

View File

@ -17,7 +17,8 @@ test('can make oauth2 requests', async ({ app, page }) => {
});
await page.locator('[data-testid="project"]').click();
await page.locator('text=Create').click();
const projectView = page.locator('#wrapper');
await projectView.locator('text=Create').click();
const text = await loadFixture('oauth.yaml');
await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text);

View File

@ -4,3 +4,5 @@ send-request
coverage
src/main.min.js
src/preload.js
**/svgr
svgr.config.js

View File

@ -37,7 +37,8 @@
"test": "jest",
"test:watch": "jest --watch",
"type-check": "tsc --noEmit --project tsconfig.build.json",
"type-check:watch": "npm run type-check -- --watch"
"type-check:watch": "npm run type-check -- --watch",
"convert-svg": "npm_config_yes=true npx @svgr/cli@6.4.0 --no-index --config-file svgr.config.js --out-dir src/ui/components/assets/svgr src/ui/components/assets/"
},
"dependencies": {
"@getinsomnia/node-libcurl": "2.3.5-2",
@ -177,7 +178,6 @@
"graphql": "^16.3.0",
"graphql-language-service": "^4.1.5",
"highlight.js": "^11.5.1",
"insomnia-components": "3.6.1-beta.0",
"jest": "^28.1.0",
"jest-environment-jsdom": "^28.0.2",
"jest-mock": "^28.0.2",

View File

@ -21,7 +21,6 @@ const toSchema = <T>(obj: T): Schema<T> => {
return output as Schema<T>;
};
export const baseModelSchema: Schema<BaseModel> = {
_id: () => 'id',
created: () => 1234,

View File

@ -66,7 +66,6 @@ export interface PrivateProperties {
axios: typeof axios;
analytics: typeof analytics;
loadRendererModules: () => Promise<{
insomniaComponents: any;
ReactDOM: typeof ReactDOM;
React: typeof React;
} | {}>;
@ -226,12 +225,10 @@ export function init(renderPurpose: RenderPurpose = RENDER_PURPOSE_GENERAL): {
const ReactDOM = await import('react-dom');
const React = await import('react');
const insomniaComponents = await import('insomnia-components');
return {
ReactDOM,
React,
insomniaComponents,
};
},
},

View File

@ -537,12 +537,12 @@ export function describeChanges<T extends BaseModel>(a: T, b: T): string[] {
const bStr = deterministicStringify(bValue);
if (aValue === undefined && bValue !== undefined) {
changes.push(`+${key}`);
changes.push(`+${String(key)}`);
continue;
}
if (aValue !== undefined && bValue === undefined) {
changes.push(`-${key}`);
changes.push(`-${String(key)}`);
continue;
}

View File

@ -1,10 +1,11 @@
import { Breadcrumb, BreadcrumbProps, Header as _Header } from 'insomnia-components';
import classNames from 'classnames';
import React, { FC, Fragment, ReactNode } from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import coreLogo from '../images/insomnia-logo.svg';
import { selectIsLoggedIn } from '../redux/selectors';
import { Breadcrumb, BreadcrumbProps } from './breadcrumb';
import { SettingsButton } from './buttons/settings-button';
import { AccountDropdownButton } from './dropdowns/account-dropdown/account-dropdown';
import { GitHubStarsButton } from './github-stars-button';
@ -13,12 +14,6 @@ const LogoWraper = styled.div({
display: 'flex',
});
const Header = styled(_Header)({
'&&': {
whiteSpace: 'nowrap',
},
});
const RightWrapper = styled.div({
transformOrigin: 'right',
transform: 'scale(0.85)',
@ -37,6 +32,55 @@ export interface AppHeaderProps {
gridRight?: ReactNode;
}
export interface HeaderProps {
className?: string;
gridLeft?: ReactNode;
gridCenter?: ReactNode;
gridRight?: ReactNode;
}
const StyledHeader = styled.div({
borderBottom: '1px solid var(--hl-md)',
padding: 'var(--padding-xxs) var(--padding-sm)',
display: 'grid',
gridTemplateColumns: '2fr 1.5fr 2fr',
gridTemplateRows: '1fr',
gridTemplateAreas: "'header_left header_center header_right'",
'.header_left': {
gridArea: 'header_left',
textAlign: 'left',
display: 'flex',
alignItems: 'center',
},
'.header_center': {
gridArea: 'header_center',
textAlign: 'center',
display: 'flex',
alignItems: 'center',
},
'.header_right': {
gridArea: 'header_right',
textAlign: 'right',
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
},
'&&': {
whiteSpace: 'nowrap',
},
});
const Header: FC<HeaderProps> = ({ className, gridLeft, gridCenter, gridRight }) => (
<StyledHeader className={classNames('app-header theme--app-header', className)}>
<div className="header_left">{gridLeft}</div>
<div className="header_center">{gridCenter}</div>
<div className="header_right">{gridRight}</div>
</StyledHeader>
);
Header.displayName = 'Header';
export const AppHeader: FC<AppHeaderProps> = ({
breadcrumbProps,
gridCenter,

View File

Before

Width:  |  Height:  |  Size: 226 B

After

Width:  |  Height:  |  Size: 226 B

View File

Before

Width:  |  Height:  |  Size: 652 B

After

Width:  |  Height:  |  Size: 652 B

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 640 B

After

Width:  |  Height:  |  Size: 640 B

View File

Before

Width:  |  Height:  |  Size: 520 B

After

Width:  |  Height:  |  Size: 520 B

View File

Before

Width:  |  Height:  |  Size: 200 B

After

Width:  |  Height:  |  Size: 200 B

View File

Before

Width:  |  Height:  |  Size: 177 B

After

Width:  |  Height:  |  Size: 177 B

View File

Before

Width:  |  Height:  |  Size: 177 B

After

Width:  |  Height:  |  Size: 177 B

View File

Before

Width:  |  Height:  |  Size: 483 B

After

Width:  |  Height:  |  Size: 483 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 586 B

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 941 B

After

Width:  |  Height:  |  Size: 941 B

View File

Before

Width:  |  Height:  |  Size: 330 B

After

Width:  |  Height:  |  Size: 330 B

View File

Before

Width:  |  Height:  |  Size: 853 B

After

Width:  |  Height:  |  Size: 853 B

View File

Before

Width:  |  Height:  |  Size: 667 B

After

Width:  |  Height:  |  Size: 667 B

View File

Before

Width:  |  Height:  |  Size: 190 B

After

Width:  |  Height:  |  Size: 190 B

View File

Before

Width:  |  Height:  |  Size: 398 B

After

Width:  |  Height:  |  Size: 398 B

View File

Before

Width:  |  Height:  |  Size: 359 B

After

Width:  |  Height:  |  Size: 359 B

View File

Before

Width:  |  Height:  |  Size: 809 B

After

Width:  |  Height:  |  Size: 809 B

View File

Before

Width:  |  Height:  |  Size: 430 B

After

Width:  |  Height:  |  Size: 430 B

View File

Before

Width:  |  Height:  |  Size: 858 B

After

Width:  |  Height:  |  Size: 858 B

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 778 B

After

Width:  |  Height:  |  Size: 778 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 638 B

After

Width:  |  Height:  |  Size: 638 B

View File

Before

Width:  |  Height:  |  Size: 222 B

After

Width:  |  Height:  |  Size: 222 B

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 244 B

After

Width:  |  Height:  |  Size: 244 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 268 B

After

Width:  |  Height:  |  Size: 268 B

View File

Before

Width:  |  Height:  |  Size: 679 B

After

Width:  |  Height:  |  Size: 679 B

View File

Before

Width:  |  Height:  |  Size: 318 B

After

Width:  |  Height:  |  Size: 318 B

View File

Before

Width:  |  Height:  |  Size: 465 B

After

Width:  |  Height:  |  Size: 465 B

View File

Before

Width:  |  Height:  |  Size: 597 B

After

Width:  |  Height:  |  Size: 597 B

View File

Before

Width:  |  Height:  |  Size: 879 B

After

Width:  |  Height:  |  Size: 879 B

View File

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 406 B

View File

Before

Width:  |  Height:  |  Size: 428 B

After

Width:  |  Height:  |  Size: 428 B

View File

Before

Width:  |  Height:  |  Size: 611 B

After

Width:  |  Height:  |  Size: 611 B

View File

Before

Width:  |  Height:  |  Size: 701 B

After

Width:  |  Height:  |  Size: 701 B

View File

Before

Width:  |  Height:  |  Size: 897 B

After

Width:  |  Height:  |  Size: 897 B

View File

Before

Width:  |  Height:  |  Size: 274 B

After

Width:  |  Height:  |  Size: 274 B

View File

Before

Width:  |  Height:  |  Size: 769 B

After

Width:  |  Height:  |  Size: 769 B

View File

Before

Width:  |  Height:  |  Size: 744 B

After

Width:  |  Height:  |  Size: 744 B

View File

Before

Width:  |  Height:  |  Size: 292 B

After

Width:  |  Height:  |  Size: 292 B

View File

Before

Width:  |  Height:  |  Size: 328 B

After

Width:  |  Height:  |  Size: 328 B

View File

Before

Width:  |  Height:  |  Size: 696 B

After

Width:  |  Height:  |  Size: 696 B

View File

Before

Width:  |  Height:  |  Size: 543 B

After

Width:  |  Height:  |  Size: 543 B

View File

Before

Width:  |  Height:  |  Size: 521 B

After

Width:  |  Height:  |  Size: 521 B

View File

Before

Width:  |  Height:  |  Size: 250 B

After

Width:  |  Height:  |  Size: 250 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 348 B

After

Width:  |  Height:  |  Size: 348 B

View File

Before

Width:  |  Height:  |  Size: 522 B

After

Width:  |  Height:  |  Size: 522 B

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 334 B

After

Width:  |  Height:  |  Size: 334 B

View File

@ -0,0 +1,14 @@
import React, { SVGProps, memo } from 'react';
export const SvgIcnArrowRight = memo<SVGProps<SVGSVGElement>>(props => (
<svg
width="1em"
height="1em"
viewBox="0 0 12 12"
xmlns="http://www.w3.org/2000/svg"
role="img"
{...props}
>
<circle cx={6} cy={6} r={6} />
<path fill="currentColor" d="M3 5v2h3v2l4-3-4-3v2z" />
</svg>
));

View File

@ -0,0 +1,20 @@
import React, { SVGProps, memo } from 'react';
export const SvgIcnBitbucketLogo = memo<SVGProps<SVGSVGElement>>(props => (
<svg
width="1em"
height="1em"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
role="img"
{...props}
>
<path fill="none" d="M0 0h14v14H0z" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2.078 3.111A.32.32 0 0 1 2.325 3l9.35.002a.32.32 0 0 1 .32.372l-1.36 8.357a.32.32 0 0 1-.32.269H3.79a.436.436 0 0 1-.426-.364l-1.36-8.264a.32.32 0 0 1 .074-.26Zm3.565 6.433h2.754l.667-4.091H4.897l.746 4.09Z"
fill=""
/>
</svg>
));

View File

@ -0,0 +1,17 @@
import React, { SVGProps, memo } from 'react';
export const SvgIcnBrackets = memo<SVGProps<SVGSVGElement>>(props => (
<svg
width="1em"
height="1em"
viewBox="0 0 12 12"
xmlns="http://www.w3.org/2000/svg"
role="img"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.504 0C3.22.07 2.312.432 1.873 1.237c-.231.424-.273.877-.26 1.268.014.376.085.765.145 1.092l.004.024c.065.356.116.645.125.903.01.254-.026.402-.077.495-.068.125-.356.467-1.805.468H0v1.5h.005c1.473 0 1.765.324 1.82.417.037.061.07.17.062.385-.009.223-.057.475-.122.801l-.006.026a6.578 6.578 0 0 0-.145 1.01c-.015.37.03.808.274 1.216.507.844 1.59 1.145 3.112 1.145l-.432-1.51c-1.105-.056-1.343-.324-1.394-.407-.036-.061-.07-.17-.061-.385.009-.223.057-.475.122-.8l.006-.027c.059-.294.131-.656.145-1.009.015-.37-.03-.809-.274-1.217-.096-.159-.212-.299-.347-.42a1.93 1.93 0 0 0 .362-.475c.231-.423.273-.877.26-1.267a7.801 7.801 0 0 0-.145-1.093l-.004-.024c-.065-.356-.116-.645-.125-.903-.01-.254.026-.402.077-.494.068-.126.357-.469 1.81-.469L4.504 0Zm2.992 0L7 1.487c1.453 0 1.742.343 1.81.469.05.092.086.24.077.494-.01.258-.06.547-.125.903l-.004.024a7.8 7.8 0 0 0-.144 1.093c-.014.39.028.844.259 1.267.098.18.219.337.362.474a1.834 1.834 0 0 0-.347.421c-.245.408-.289.847-.274 1.217.014.353.086.715.145 1.01l.006.025c.065.326.113.578.122.801.009.216-.025.324-.061.385-.05.083-.29.351-1.394.407L7 11.987c1.523 0 2.605-.301 3.112-1.145.245-.408.289-.847.274-1.217-.014-.353-.086-.715-.145-1.01l-.006-.025c-.065-.326-.113-.578-.122-.8-.009-.216.025-.325.061-.386.056-.093.349-.417 1.826-.417v-1.5c-1.453 0-1.742-.343-1.81-.468-.05-.093-.086-.24-.077-.495.01-.258.06-.547.125-.903l.004-.024c.06-.327.13-.716.144-1.092.014-.39-.028-.844-.259-1.268C9.687.432 8.78.07 7.496 0Z"
/>
</svg>
));

View File

@ -0,0 +1,6 @@
import React, { SVGProps, memo } from 'react';
export const SvgIcnBug = memo<SVGProps<SVGSVGElement>>(props => (
<svg viewBox="0 0 111 111" width="1em" height="1em" role="img" {...props}>
<path d="M110.081 62.515c-.102 3.716-3.245 6.63-6.962 6.63H91.32v3.412c0 4.662-1.04 9.08-2.9 13.037l12.842 12.842a6.823 6.823 0 1 1-9.649 9.649l-11.67-11.67a30.575 30.575 0 0 1-19.326 6.845V51.236a2.559 2.559 0 0 0-2.558-2.559h-5.117a2.559 2.559 0 0 0-2.559 2.559v52.024a30.575 30.575 0 0 1-19.325-6.846l-11.67 11.671a6.823 6.823 0 0 1-9.65-9.649L22.58 85.594a30.583 30.583 0 0 1-2.9-13.037v-3.411H7.882c-3.718 0-6.86-2.915-6.962-6.631A6.823 6.823 0 0 1 7.74 55.5h11.94V42.975l-9.942-9.942a6.823 6.823 0 0 1 9.65-9.65l11.647 11.648h48.93l11.648-11.647a6.823 6.823 0 1 1 9.649 9.65l-9.942 9.94V55.5h11.941a6.823 6.823 0 0 1 6.82 7.015ZM55.713.917c-13.188 0-23.88 10.691-23.88 23.88h47.76c0-13.189-10.69-23.88-23.88-23.88Z" />
</svg>
));

View File

@ -0,0 +1,20 @@
import React, { SVGProps, memo } from 'react';
export const SvgIcnBurgerMenu = memo<SVGProps<SVGSVGElement>>(props => (
<svg
width="1em"
height="1em"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
role="img"
{...props}
>
<path fill="none" d="M0 0h14v14H0z" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M11.5 10a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h9Zm0-4a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h9Zm0-4a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h9Z"
fill=""
/>
</svg>
));

View File

@ -0,0 +1,13 @@
import React, { SVGProps, memo } from 'react';
export const SvgIcnCheckmark = memo<SVGProps<SVGSVGElement>>(props => (
<svg
width="1em"
height="1em"
viewBox="0 0 14 14"
xmlns="http://www.w3.org/2000/svg"
role="img"
{...props}
>
<path d="m11.753 2 1.244 1.23-8.375 8.473L1 8.081l1.237-1.238L4.614 9.22 11.753 2Z" />
</svg>
));

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