mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Lens app source code (#119)
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
1 parent
936cbd53d4
commit
1d0815abd2
797 files changed
+73714
-215
No files matched your search
@@ -0,0 +1 @@
|
||||
export * from './wizard'
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
|
||||
.Wizard {
|
||||
--wizard-width: #{65 * $unit};
|
||||
--wizard-content-height: auto;
|
||||
--wizard-content-max-height: #{40 * $unit};
|
||||
--wizard-spacing: #{$padding * 3};
|
||||
--wizard-step-bgc: #{$dialogBackground};
|
||||
|
||||
width: var(--wizard-width);
|
||||
color: $dialogTextColor;
|
||||
|
||||
&.small {
|
||||
--wizard-width: #{45 * $unit};
|
||||
--wizard-content-height: auto;
|
||||
}
|
||||
|
||||
@mixin scrollableContent() {
|
||||
@include custom-scrollbar($theme: dark);
|
||||
padding: var(--wizard-spacing);
|
||||
height: var(--wizard-content-height);
|
||||
max-height: var(--wizard-content-max-height);
|
||||
}
|
||||
|
||||
> .header {
|
||||
text-align: center;
|
||||
color: white;
|
||||
background: $dialogHeaderBackground;
|
||||
padding: var(--wizard-spacing);
|
||||
|
||||
b {
|
||||
font-weight: $font-weight-normal;
|
||||
}
|
||||
|
||||
> * {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.Stepper {
|
||||
color: silver;
|
||||
--stepper-color-point: black;
|
||||
--stepper-color-text: currentColor;
|
||||
margin-top: var(--wizard-spacing);
|
||||
}
|
||||
|
||||
.Checkbox {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.Select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.step-loading {
|
||||
padding: var(--wizard-spacing);
|
||||
}
|
||||
|
||||
.WizardStep {
|
||||
background: var(--wizard-step-bgc);
|
||||
|
||||
> .step-content.scrollable {
|
||||
@include scrollableContent();
|
||||
|
||||
> * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// buttons
|
||||
> :last-child {
|
||||
padding: var(--wizard-spacing);
|
||||
background: $dialogFooterBackground;
|
||||
|
||||
.back-btn {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.Button[type="submit"] {
|
||||
min-width: 10 * $unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
import "./wizard.scss";
|
||||
import * as React from "react";
|
||||
import { Trans } from "@lingui/macro";
|
||||
import { cssNames, prevDefault } from "../../utils";
|
||||
import { Button } from "../button";
|
||||
import { Stepper } from "../stepper";
|
||||
import { SubTitle } from "../layout/sub-title";
|
||||
import { Spinner } from "../spinner";
|
||||
|
||||
interface WizardCommonProps<D = any> {
|
||||
data?: Partial<D>;
|
||||
save?: (data: Partial<D>, callback?: () => void) => void;
|
||||
reset?: () => void;
|
||||
done?: () => void;
|
||||
hideSteps?: boolean;
|
||||
}
|
||||
|
||||
export interface WizardProps extends WizardCommonProps {
|
||||
className?: string;
|
||||
step?: number;
|
||||
title?: string;
|
||||
header?: React.ReactNode;
|
||||
onChange?: (step: number) => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
step?: number;
|
||||
}
|
||||
|
||||
export class Wizard extends React.Component<WizardProps, State> {
|
||||
public state: State = {
|
||||
step: this.getValidStep(this.props.step)
|
||||
};
|
||||
|
||||
get steps() {
|
||||
const { className, title, step, header, onChange, children, ...commonProps } = this.props;
|
||||
const steps = React.Children.toArray(children) as WizardStepElem[];
|
||||
return steps.filter(step => !step.props.skip).map((stepElem, i) => {
|
||||
const stepProps = stepElem.props;
|
||||
return React.cloneElement(stepElem, {
|
||||
step: i + 1,
|
||||
wizard: this,
|
||||
next: this.nextStep,
|
||||
prev: this.prevStep,
|
||||
first: this.firstStep,
|
||||
last: this.lastStep,
|
||||
isFirst: this.isFirstStep,
|
||||
isLast: this.isLastStep,
|
||||
...commonProps,
|
||||
...stepProps
|
||||
} as WizardStepProps<any>)
|
||||
});
|
||||
}
|
||||
|
||||
get step() {
|
||||
return this.state.step;
|
||||
}
|
||||
|
||||
set step(step: number) {
|
||||
step = this.getValidStep(step);
|
||||
if (step === this.step) return;
|
||||
|
||||
this.setState({ step }, () => {
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(step);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected getValidStep(step: number) {
|
||||
return Math.min(Math.max(1, step), this.steps.length) || 1;
|
||||
}
|
||||
|
||||
isFirstStep = () => this.step === 1;
|
||||
isLastStep = () => this.step === this.steps.length;
|
||||
firstStep = (): any => this.step = 1;
|
||||
nextStep = (): any => this.step++;
|
||||
prevStep = (): any => this.step--;
|
||||
lastStep = (): any => this.step = this.steps.length;
|
||||
|
||||
render() {
|
||||
const { className, title, header, hideSteps } = this.props;
|
||||
const steps = this.steps.map(stepElem => ({ title: stepElem.props.title }))
|
||||
const step = React.cloneElement(this.steps[this.step - 1]);
|
||||
return (
|
||||
<div className={cssNames("Wizard", className)}>
|
||||
<div className="header">
|
||||
{header}
|
||||
{title ? <SubTitle title={title}/> : null}
|
||||
{!hideSteps && steps.length > 1 ? <Stepper steps={steps} step={this.step}/> : null}
|
||||
</div>
|
||||
{step}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export interface WizardStepProps<D = any> extends WizardCommonProps<D> {
|
||||
wizard?: Wizard;
|
||||
title?: string;
|
||||
className?: string | object;
|
||||
contentClass?: string | object;
|
||||
customButtons?: React.ReactNode; // render custom buttons block in footer
|
||||
moreButtons?: React.ReactNode; // add more buttons to section in the footer
|
||||
loading?: boolean; // indicator of loading content for the step
|
||||
waiting?: boolean; // indicator of waiting response before going to next step
|
||||
disabledNext?: boolean; // disable next button flag, e.g when filling step is not finished
|
||||
hideNextBtn?: boolean;
|
||||
hideBackBtn?: boolean;
|
||||
step?: number;
|
||||
prevLabel?: React.ReactNode; // custom label for prev button
|
||||
nextLabel?: React.ReactNode; // custom label for next button
|
||||
next?: () => void | boolean | Promise<any>; // custom action for next button
|
||||
prev?: () => void; // custom action for prev button
|
||||
first?: () => void;
|
||||
last?: () => void;
|
||||
isFirst?: () => boolean;
|
||||
isLast?: () => boolean;
|
||||
beforeContent?: React.ReactNode;
|
||||
afterContent?: React.ReactNode;
|
||||
noValidate?: boolean; // no validate form attribute
|
||||
skip?: boolean; // don't render the step
|
||||
scrollable?: boolean;
|
||||
}
|
||||
|
||||
interface WizardStepState {
|
||||
waiting?: boolean;
|
||||
}
|
||||
|
||||
type WizardStepElem = React.ReactElement<WizardStepProps>;
|
||||
|
||||
export class WizardStep extends React.Component<WizardStepProps, WizardStepState> {
|
||||
private form: HTMLFormElement;
|
||||
public state: WizardStepState = {};
|
||||
private unmounting = false;
|
||||
|
||||
static defaultProps: WizardStepProps = {
|
||||
scrollable: true,
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unmounting = true;
|
||||
}
|
||||
|
||||
prev = () => {
|
||||
const { isFirst, prev, done } = this.props;
|
||||
if (isFirst() && done) done();
|
||||
else prev();
|
||||
}
|
||||
|
||||
next = () => {
|
||||
const next = this.props.next;
|
||||
const nextStep = this.props.wizard.nextStep;
|
||||
if (nextStep !== next) {
|
||||
const result = next();
|
||||
if (result instanceof Promise) {
|
||||
this.setState({ waiting: true });
|
||||
result.then(nextStep).finally(() => {
|
||||
if (this.unmounting) return;
|
||||
this.setState({ waiting: false });
|
||||
});
|
||||
}
|
||||
else if (typeof result === "boolean" && result) {
|
||||
nextStep();
|
||||
}
|
||||
}
|
||||
else {
|
||||
nextStep();
|
||||
}
|
||||
}
|
||||
|
||||
submit = () => {
|
||||
if (!this.form.noValidate) {
|
||||
const valid = this.form.checkValidity();
|
||||
if (!valid) return;
|
||||
}
|
||||
this.next();
|
||||
}
|
||||
|
||||
renderLoading() {
|
||||
return (
|
||||
<div className="step-loading flex center">
|
||||
<Spinner/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
step, isFirst, isLast, children,
|
||||
loading, customButtons, disabledNext, scrollable,
|
||||
hideNextBtn, hideBackBtn, beforeContent, afterContent, noValidate, skip, moreButtons,
|
||||
} = this.props;
|
||||
let { className, contentClass, nextLabel, prevLabel, waiting } = this.props;
|
||||
if (skip) {
|
||||
return;
|
||||
}
|
||||
waiting = (waiting !== undefined) ? waiting : this.state.waiting;
|
||||
className = cssNames(`WizardStep step${step}`, className);
|
||||
contentClass = cssNames("step-content", { scrollable }, contentClass);
|
||||
prevLabel = prevLabel || (isFirst() ? <Trans>Cancel</Trans> : <Trans>Back</Trans>);
|
||||
nextLabel = nextLabel || (isLast() ? <Trans>Submit</Trans> : <Trans>Next</Trans>);
|
||||
return (
|
||||
<form className={className}
|
||||
onSubmit={prevDefault(this.submit)} noValidate={noValidate}
|
||||
ref={e => this.form = e}>
|
||||
{beforeContent}
|
||||
<div className={contentClass}>
|
||||
{loading ? this.renderLoading() : children}
|
||||
</div>
|
||||
{customButtons !== undefined ? customButtons : (
|
||||
<div className="buttons flex gaps align-center">
|
||||
{moreButtons}
|
||||
<Button
|
||||
className="back-btn"
|
||||
plain label={prevLabel} hidden={hideBackBtn}
|
||||
onClick={this.prev}
|
||||
/>
|
||||
<Button
|
||||
primary type="submit"
|
||||
label={nextLabel} hidden={hideNextBtn}
|
||||
waiting={waiting} disabled={disabledNext}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{afterContent}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user