React工程化实践之UI组件库
PC端UI库
移动端UI库
import * as React from 'react'; import { Alert } from '@athena-ui/components/Alert' constructor(props) { super(props); this.state = { canEscapeKeyCancel: false, canOutsideClickCancel: false, isOpen: false, isOpenError: false, }; this.handleEscapeKeyChange = handleBooleanChange(canEscapeKeyCancel => this.setState({ canEscapeKeyCancel })); this.handleOutsideClickChange = handleBooleanChange(click => this.setState({ canOutsideClickCancel: click })); this.handleErrorOpen = () => this.setState({ isOpenError: true }); this.handleErrorClose = () => this.setState({ isOpenError: false }); this.handleMoveOpen = () => this.setState({ isOpen: true }); this.handleMoveConfirm = () => { this.setState({ isOpen: false }); }; this.handleMoveCancel = () => this.setState({ isOpen: false }); } render() { const { isOpen, isOpenError, ...alertProps } = this.state; const options = ( <React.Fragment> <H5>Props</H5> <Switch checked={this.state.canEscapeKeyCancel} label="Can escape key cancel" onChange={this.handleEscapeKeyChange} /> <Switch checked={this.state.canOutsideClickCancel} label="Can outside click cancel" onChange={this.handleOutsideClickChange} /> </React.Fragment> ); return ( <Example options={options} {...this.props} className="docs-example-frame-row"> <Button onClick={this.handleErrorOpen} text="Open file error alert" /> <Alert {...alertProps} confirmButtonText="Okay" isOpen={isOpenError} onClose={this.handleErrorClose} > <p> Couldn't create the file because the containing folder doesn't exist anymore. You will be redirected to your user folder. </p> </Alert> <Button onClick={this.handleMoveOpen} text="Open file deletion alert" /> <Alert {...alertProps} cancelButtonText="Cancel" confirmButtonText="Move to Trash" icon="trash" intent={Intent.DANGER} isOpen={isOpen} onCancel={this.handleMoveCancel} onConfirm={this.handleMoveConfirm} > <p> Are you sure you want to move <b>filename</b> to Trash? You will be able to restore it later, but it will become private to you. </p> </Alert> </Example> ); }
render() { return ( <React.Fragment> <div className="at-row"> <Button intent="default">默认按钮</Button> <Button intent="primary">主要按钮</Button> <Button intent="success">成功按钮</Button> <Button intent="info">信息按钮</Button> <Button intent="warning">警告按钮</Button> <Button intent="danger">危险按钮</Button> </div> <div className="at-row"> <Button intent="default" minimal>朴素按钮</Button> <Button intent="primary" minimal>主要按钮</Button> <Button intent="success" minimal>成功按钮</Button> <Button intent="info" minimal>信息按钮</Button> <Button intent="warning" minimal>警告按钮</Button> <Button intent="danger" minimal>危险按钮</Button> </div> <div className="at-row"> <Button intent="default" outline>Outline按钮</Button> <Button intent="primary" outline>主要按钮</Button> <Button intent="success" outline>成功按钮</Button> <Button intent="info" outline>信息按钮</Button> <Button intent="warning" outline>警告按钮</Button> <Button intent="danger" outline>危险按钮</Button> </div> <div className="at-row"> <Button intent="default" round>圆角按钮</Button> <Button intent="primary" round>主要按钮</Button> <Button intent="success" round>成功按钮</Button> <Button intent="info" round>信息按钮</Button> <Button intent="warning" round>警告按钮</Button> <Button intent="danger" round>危险按钮</Button> </div> <div className="at-row"> <Button intent="default" icon="search" circle></Button> <Button intent="primary" icon="edit" circle></Button> <Button intent="success" icon="help" circle></Button> <Button intent="info" icon="repeat" circle></Button> <Button intent="warning" icon="star" circle></Button> <Button intent="danger" icon="delete" circle></Button> </div> </React.Fragment> ) }
render() { return ( <React.Fragment> <div className="at-row"> <Button intent="default" disabled>默认按钮</Button> <Button intent="primary" disabled>主要按钮</Button> <Button intent="success" disabled>成功按钮</Button> <Button intent="info" disabled>信息按钮</Button> <Button intent="warning" disabled>警告按钮</Button> <Button intent="danger" disabled>危险按钮</Button> </div> <div className="at-row"> <Button intent="default" minimal disabled>朴素按钮</Button> <Button intent="primary" minimal disabled>主要按钮</Button> <Button intent="success" minimal disabled>成功按钮</Button> <Button intent="info" minimal disabled>信息按钮</Button> <Button intent="warning" minimal disabled>警告按钮</Button> <Button intent="danger" minimal disabled>危险按钮</Button> </div> </React.Fragment> ) }
render() { return ( <div> <Button intent="primary" link>文字按钮</Button> <Button intent="success" link disabled>文字按钮</Button> </div> ) }
render() { return ( <div style={{display: "flex"}}> <Button intent="primary" icon="edit"></Button> <Button intent="primary" icon="share"></Button> <Button intent="primary" icon="delete"></Button> <Button intent="primary" icon="search">搜索</Button> <Button intent="primary" rightIcon="upload">上传</Button> </div> ) }
render() { return ( <div> <Button.Group> <Button intent="primary" icon="at-icon-arrow-left">上一页</Button> <Button intent="primary">下一页<i className="el-icon-arrow-right el-icon-right"></i></Button> </Button.Group> <Button.Group> <Button intent="primary" icon="at-icon-edit"></Button> <Button intent="primary" icon="at-icon-share"></Button> <Button intent="primary" icon="at-icon-delete"></Button> </Button.Group> </div> ) }
render() { return <Button intent="primary" loading={true}>加载中</Button> }
render() { return ( <div> <div className="at-row"> <Button>默认按钮</Button> <Button large>中等按钮</Button> <Button small>小型按钮</Button> </div> <div className="at-row"> <Button round>默认按钮</Button> <Button large round>中等按钮</Button> <Button small round>小型按钮</Button> </div> </div> ) }
render() { return ( <div> <div className="at-row"> <CompoundButton intent="info" secondaryText="You can create a new account here." text="Hi!" loading></CompoundButton> </div> </div> ) }
render() { return ( <div> <div className="at-row"> <Button intent="primary" text="新建" menuProps={{ items: [ { key: 'emailMessage', text: 'Email message', icon: 'envelope', }, { key: 'calendarEvent', text: 'Calendar event', icon: 'timeline-events', } ], directionalHintFixed: true }} ></Button> </div> </div> ) }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(props) { super(props); this.state = { alignText: Alignment.CENTER, fill: false, iconOnly: false, large: false, minimal: false, vertical: false, }; this.handleFillChange = handleBooleanChange(fill => this.setState({ fill })); this.handleIconOnlyChange = handleBooleanChange(iconOnly => this.setState({ iconOnly })); this.handleLargeChange = handleBooleanChange(large => this.setState({ large })); this.handleMinimalChange = handleBooleanChange(minimal => this.setState({ minimal })); this.handleVerticalChange = handleBooleanChange(vertical => this.setState({ vertical })); this.handleAlignChange = (alignText) => this.setState({ alignText }); } render() { const { iconOnly, ...bgProps } = this.state; const options = ( <React.Fragment> <H5>Props</H5> <Switch checked={this.state.fill} label="Fill" onChange={this.handleFillChange} /> <Switch checked={this.state.large} label="Large" onChange={this.handleLargeChange} /> <Switch checked={this.state.minimal} label="Minimal" onChange={this.handleMinimalChange} /> <Switch checked={this.state.vertical} label="Vertical" onChange={this.handleVerticalChange} /> <AlignmentSelect align={this.state.alignText} onChange={this.handleAlignChange} /> <H5>Example</H5> <Switch checked={this.state.iconOnly} label="Icons only" onChange={this.handleIconOnlyChange} /> </React.Fragment> ); return ( <Example options={options} {...this.props}> <ButtonGroup style={{ minWidth: 200 }} {...bgProps}> <Button icon="database">{!iconOnly && "Queries"}</Button> <Button icon="function">{!iconOnly && "Functions"}</Button> <Button icon="cog" rightIcon="settings"> {!iconOnly && "Options"} </Button> <Button intent="primary" text={!iconOnly && "Create account"} split menuProps={{ items: [ { key: 'emailMessage', text: 'Email message', }, { key: 'calendarEvent', text: 'Calendar event', } ], directionalHintFixed: true }} /> </ButtonGroup> </Example> ); }
Buttons
constructor(props) { super(props); this.state = { alignText: Alignment.CENTER, large: false, minimal: false, vertical: false, }; this.handleLargeChange = handleBooleanChange(large => this.setState({ large })); this.handleMinimalChange = handleBooleanChange(minimal => this.setState({ minimal })); this.handleVerticalChange = handleBooleanChange(vertical => this.setState({ vertical })); this.handleAlignChange = (alignText) => this.setState({ alignText }); } render() { const options = ( <React.Fragment> <H5>Props</H5> <Switch label="Large" checked={this.state.large} onChange={this.handleLargeChange} /> <Switch label="Minimal" checked={this.state.minimal} onChange={this.handleMinimalChange} /> <Switch label="Vertical" checked={this.state.vertical} onChange={this.handleVerticalChange} /> <AlignmentSelect align={this.state.alignText} onChange={this.handleAlignChange} /> </React.Fragment> ); return ( <Example options={options} {...this.props}> <ButtonGroup {...this.state} style={{ minWidth: 120 }}> {this.renderButton("File", "document")} {this.renderButton("Edit", "edit")} {this.renderButton("View", "eye-open")} </ButtonGroup> </Example> ); } renderButton(text, iconName) { const { vertical } = this.state; const rightIconName: IconName = vertical ? "caret-right" : "caret-down"; const position = vertical ? Position.RIGHT_TOP : Position.BOTTOM_LEFT; return ( <Popover content={<FileMenu />} position={position}> <Button rightIcon={rightIconName} icon={iconName} text={text} /> </Popover> ); }
constructor(props) { super(props); this.state = { selectedDate: null, }; this._onSelectDate = this._onSelectDate.bind(this); } render() { const divStyle = { height: '340px' }; const buttonStyle = { margin: '17px 10px 0 0' }; let dateRangeString = null; if (this.state.selectedDateRange) { const rangeStart = this.state.selectedDateRange[0]; const rangeEnd = this.state.selectedDateRange[this.state.selectedDateRange.length - 1]; dateRangeString = rangeStart.toLocaleDateString() + '-' + rangeEnd.toLocaleDateString(); } return ( <Example {...this.props}> <div style={divStyle}> <div> 选择的日期:{' '} <span>{!this.state.selectedDate ? '无' : this.state.selectedDate.toLocaleString()}</span> </div> <Calendar isMonthPickerVisible={false} value={this.state.selectedDate} onSelectDate={this._onSelectDate} /> </div> </Example> ); } _onSelectDate(date: Date, dateRangeArray: Date[]) { this.setState({ selectedDate: date, }); }
constructor(props) { super(props); this.state = { selectedDate: null, }; this._onSelectDate = this._onSelectDate.bind(this); } render() { const divStyle = { height: '340px' }; const buttonStyle = { margin: '17px 10px 0 0' }; let dateRangeString = null; if (this.state.selectedDateRange) { const rangeStart = this.state.selectedDateRange[0]; const rangeEnd = this.state.selectedDateRange[this.state.selectedDateRange.length - 1]; dateRangeString = rangeStart.toLocaleDateString() + '-' + rangeEnd.toLocaleDateString(); } return ( <Example {...this.props}> <div style={divStyle}> <div> 选择的日期:{' '} <span>{!this.state.selectedDate ? '无' : this.state.selectedDate.toLocaleString()}</span> </div> <Calendar isMonthPickerVisible={false} showMonthPickerAsOverlay value={this.state.selectedDate} onSelectDate={this._onSelectDate} /> </div> </Example> ); } _onSelectDate(date: Date, dateRangeArray: Date[]) { this.setState({ selectedDate: date, }); }
constructor(props) { super(props); this.state = { selectedDate: null, }; this._onSelectDate = this._onSelectDate.bind(this); } render() { const divStyle = { height: '340px' }; const buttonStyle = { margin: '17px 10px 0 0' }; let dateRangeString = null; if (this.state.selectedDateRange) { const rangeStart = this.state.selectedDateRange[0]; const rangeEnd = this.state.selectedDateRange[this.state.selectedDateRange.length - 1]; dateRangeString = rangeStart.toLocaleDateString() + '-' + rangeEnd.toLocaleDateString(); } return ( <Example {...this.props}> <div style={divStyle}> <div> 选择的日期:{' '} <span>{!this.state.selectedDate ? '无' : this.state.selectedDate.toLocaleString()}</span> </div> <Calendar isMonthPickerVisible value={this.state.selectedDate} onSelectDate={this._onSelectDate} /> </div> </Example> ); } _onSelectDate(date: Date, dateRangeArray: Date[]) { this.setState({ selectedDate: date, }); }
constructor(props) { super(props); this.state = { selectedDate: null, selectedDateRange: null, dateRangeType: DateRangeType.Week, }; this.DATE_RANGE_TYPES = [ {label: 'Day', value: DateRangeType.Day}, {label: 'Week', value: DateRangeType.Week}, {label: 'Month', value: DateRangeType.Month}, {label: 'WorkWeek', value: DateRangeType.WorkWeek}, ] this._onSelectDate = this._onSelectDate.bind(this); this._goNext = this._goNext.bind(this); this._goPrevious = this._goPrevious.bind(this); this._handleDateRangeTypeChange = this._handleDateRangeTypeChange.bind(this); } render() { const divStyle = { height: '340px' }; const buttonStyle = { margin: '17px 10px 0 0' }; let dateRangeString = null; if (this.state.selectedDateRange) { const rangeStart = this.state.selectedDateRange[0]; const rangeEnd = this.state.selectedDateRange[this.state.selectedDateRange.length - 1]; dateRangeString = rangeStart.toLocaleDateString() + '-' + rangeEnd.toLocaleDateString(); } return ( <Example options={this._renderOptions()} {...this.props}> <div style={divStyle}> <div> 选择的日期:{' '} <span>{!this.state.selectedDate ? '无' : this.state.selectedDate.toLocaleString()}</span> </div> <div> 选择的日期范围: <span> {!dateRangeString ? '无' : dateRangeString}</span> </div> <Calendar isMonthPickerVisible dateRangeType={this.state.dateRangeType} value={this.state.selectedDate} onSelectDate={this._onSelectDate} /> <div> <Button style={buttonStyle} onClick={this._goPrevious} text="上一区间" /> <Button style={buttonStyle} onClick={this._goNext} text="下一区间" /> </div> </div> </Example> ); } _onSelectDate(date: Date, dateRangeArray: Date[]) { this.setState({ selectedDate: date, selectedDateRange: dateRangeArray }); } _goPrevious() { this.setState((prevState) => { const selectedDate = prevState.selectedDate || new Date(); const dateRangeArray = this.props.getDateRangeArray(selectedDate, this.state.dateRangeType, DayOfWeek.Sunday); let subtractFrom = dateRangeArray[0]; let daysToSubtract = dateRangeArray.length; if (this.state.dateRangeType === DateRangeType.Month) { subtractFrom = new Date(subtractFrom.getFullYear(), subtractFrom.getMonth(), 1); daysToSubtract = 1; } const newSelectedDate = this.props.addDays(subtractFrom, -daysToSubtract); return { selectedDate: newSelectedDate }; }); } _goNext() { this.setState((prevState) => { const selectedDate = prevState.selectedDate || new Date(); const dateRangeArray = this.props.getDateRangeArray(selectedDate, this.state.dateRangeType, DayOfWeek.Sunday); const newSelectedDate = this.props.addDays(dateRangeArray.pop(), 1); return { selectedDate: newSelectedDate }; }); } _handleDateRangeTypeChange(evt) { this.setState({ dateRangeType: parseInt(evt.target.value) }) } _renderOptions() { const { dateRangeType } = this.state; return ( <React.Fragment> <H5>Props</H5> <Label> Position <HTMLSelect value={dateRangeType} onChange={this._handleDateRangeTypeChange} options={this.DATE_RANGE_TYPES} /> </Label> </React.Fragment> ); }
render() { return ( <Example alignLeft compact {...this.props}> <div> <Checkbox label="Gilad Gray" defaultIndeterminate={true} /> <Checkbox label="Jason Killian" /> <Checkbox label="Antoine Llorca" /> <Checkbox> <SvgIcon use="#user" color="success" size={16} /> <span style={{marginRight: 8}}></span> Gilad <strong>Gray</strong> </Checkbox> </div> </Example> ) }
render() { return ( <Example alignLeft {...this.props}> <div> <Checkbox disabled>备选项</Checkbox> <Checkbox checked={true} disabled>选中且禁用</Checkbox> </div> </Example> ) }
constructor(props) { super(props); this.state = { mealTypes: ["one", "three"] } } handleMealChange(values) { this.setState({ mealTypes: values }); }; render() { return ( <Example alignLeft {...this.props}> <div> <CheckboxGroup label="Meal Choice" onValuesChange={this.handleMealChange.bind(this)} selectedValues={this.state.mealTypes} > <Checkbox label="Soup" value="one" /> <Checkbox label="Salad" value="two" /> <Checkbox label="Sandwich" value="three" /> <Checkbox label="Franchy" value="four" disabled /> </CheckboxGroup> </div> </Example> ) }
render() { return ( <Example alignLeft {...this.props}> <Checkbox checkedIcon="heart" unCheckedIcon="heart-broken">备选项</Checkbox> </Example> ) }
constructor(props) { super(props); this.state = { isOpen: false, keepChildrenMounted: false, }; this.handleChildrenMountedChange = handleBooleanChange(keepChildrenMounted => { this.setState({ keepChildrenMounted }); }); this.handleClick = () => this.setState({ isOpen: !this.state.isOpen }); } render() { const options = ( <React.Fragment> <H5>Props</H5> <Switch checked={this.state.keepChildrenMounted} label="Keep children mounted" onChange={this.handleChildrenMountedChange} /> </React.Fragment> ); return ( <Example options={options} {...this.props}> <div style={{ width: "100%" }}> <Button onClick={this.handleClick}>{this.state.isOpen ? "Hide" : "Show"} build logs</Button> <Collapse isOpen={this.state.isOpen} keepChildrenMounted={this.state.keepChildrenMounted}> <Pre> [11:53:30] Finished 'typescript-bundle-blueprint' after 769 ms<br /> [11:53:30] Starting 'typescript-typings-blueprint'...<br /> [11:53:30] Finished 'typescript-typings-blueprint' after 198 ms<br /> [11:53:30] write ./blueprint.css<br /> [11:53:30] Finished 'sass-compile-blueprint' after 2.84 s </Pre> </Collapse> </div> </Example> ); }
constructor(props) { super(props); this.state = { firstDayOfWeek: DayOfWeek.Sunday }; } render() { const { firstDayOfWeek } = this.state; return ( <Example {...this.props}> <DatePicker firstDayOfWeek={firstDayOfWeek} placeholder="Select a date..." onAfterMenuDismiss={() => console.log('onAfterMenuDismiss called')} /> </Example> ); }
constructor(props) { super(props); this.state = { }; this._onSelectDate = this._onSelectDate.bind(this); this._onClick = this._onClick.bind(this); } render() { const { value } = this.state; return ( <Example {...this.props}> <DatePicker allowTextInput placeholder="Select a date..." value={value} onSelectDate={this._onSelectDate} /> <Button onClick={this._onClick} text="清除" /> </Example> ); } _onSelectDate(date) { this.setState({ value: date }); } _onClick() { this.setState({ value: null }); }
constructor(props) { super(props); this.state = { }; this._onSelectDate = this._onSelectDate.bind(this); this._onClick = this._onClick.bind(this); } render() { const { value } = this.state; return ( <Example {...this.props}> <DatePicker allowTextInput placeholder="Select a date..." formatDate='DD/MM/YY' value={value} onSelectDate={this._onSelectDate} /> <Button onClick={this._onClick} text="清除" /> </Example> ); } _onSelectDate(date) { this.setState({ value: date }); } _onClick() { this.setState({ value: null }); }
constructor(props) { super(props); } render() { const today: Date = new Date(Date.now()); const minDate: Date = this.props.addMonths(today, -1); const maxDate: Date = this.props.addYears(today, 1); return ( <Example {...this.props}> <DatePicker allowTextInput placeholder="Select a date..." minDate={minDate} maxDate={maxDate} /> </Example> ); }
constructor(props) { super(props); this.handleOpen = this.handleOpen.bind(this); this.handleClose = this.handleClose.bind(this); this.state = { autoFocus: true, canEscapeKeyClose: true, canOutsideClickClose: true, enforceFocus: true, isOpen: false, usePortal: true, } } handleOpen() { this.setState({ isOpen: true }); } handleClose() { this.setState({ isOpen: false }); } render() { return ( <Example options={this.renderOptions()}> <Button onClick={this.handleOpen}>Show dialog</Button> <Dialog onClose={this.handleClose} title="Palantir Foundry" {...this.state} > <div className="bp3-dialog-body"> <p> <strong> Data integration is the seminal problem of the digital age. For over ten years, we’ve helped the world’s premier organizations rise to the challenge. </strong> </p> <p> Palantir Foundry radically reimagines the way enterprises interact with data by amplifying and extending the power of data integration. With Foundry, anyone can source, fuse, and transform data into any shape they desire. Business analysts become data engineers — and leaders in their organization’s data revolution. </p> <p> Foundry’s back end includes a suite of best-in-class data integration capabilities: data provenance, git-style versioning semantics, granular access controls, branching, transformation authoring, and more. But these powers are not limited to the back-end IT shop. </p> <p> In Foundry, tables, applications, reports, presentations, and spreadsheets operate as data integrations in their own right. Access controls, transformation logic, and data quality flow from original data source to intermediate analysis to presentation in real time. Every end product created in Foundry becomes a new data source that other users can build upon. And the enterprise data foundation goes where the business drives it. </p> <p>Start the revolution. Unleash the power of data integration with Palantir Foundry.</p> </div> <div className='bp3-dialog-footer'> <div className='bp3-dialog-footer-actions'> <Button onClick={this.handleClose}>Close</Button> <Button color='primary' > Visit the Foundry website </Button> </div> </div> </Dialog> </Example> ) } renderOptions() { const { autoFocus, enforceFocus, canEscapeKeyClose, canOutsideClickClose, usePortal } = this.state; return ( <div> <h5>Props</h5> <Switch checked={autoFocus} label="Auto focus" onChange={(evt) => this.setState({autoFocus: evt.target.checked})} /> <Switch checked={enforceFocus} label="Enforce focus" onChange={(evt) => this.setState({enforceFocus: evt.target.checked})} /> <Switch checked={usePortal} onChange={(evt) => this.setState({usePortal: evt.target.checked})}> Use <strong>Portal</strong> </Switch> <Switch checked={canOutsideClickClose} label="Click outside to close" onChange={(evt) => this.setState({canOutsideClickClose: evt.target.checked})} /> <Switch checked={canEscapeKeyClose} label="Escape key to close" onChange={(evt) => this.setState({canEscapeKeyClose: evt.target.checked})} /> </div> ) }
constructor(props) { super(props); this.PHOTOS = createArray(25, () => { const randomWidth = 50 + Math.floor(Math.random() * 150); return { url: `http://placehold.it/${randomWidth}x100`, width: randomWidth, height: 100 }; }); } render() { const log = (): void => { console.log('clicked'); }; return ( <FocusZone elementType="ul" className="at-FocusZoneExamples-photoList"> {this.PHOTOS.map((photo, index) => ( <li key={index} className="at-FocusZoneExamples-photoCell" aria-posinset={index + 1} aria-setsize={this.PHOTOS.length} aria-label="Photo" data-is-focusable={true} onClick={log} > <Image src={photo.url} width={photo.width} height={photo.height} /> </li> ))} </FocusZone> ) }
constructor(props) { super(props); this.state = { disabled: false, helperText: false, inline: false, intent: Intent.NONE, requiredLabel: true, }; this.handleDisabledChange = handleBooleanChange(disabled => this.setState({ disabled })); this.handleHelperTextChange = handleBooleanChange(helperText => this.setState({ helperText })); this.handleInlineChange = handleBooleanChange(inline => this.setState({ inline })); this.handleRequiredLabelChange = handleBooleanChange(requiredLabel => this.setState({ requiredLabel })); this.handleIntentChange = handleStringChange((intent: Intent) => this.setState({ intent })); } render() { const { disabled, helperText, inline, intent, requiredLabel } = this.state; const options = ( <React.Fragment> <H5>Props</H5> <Switch label="Disabled" checked={disabled} onChange={this.handleDisabledChange} /> <Switch label="Inline" checked={inline} onChange={this.handleInlineChange} /> <Switch label="Show helper text" checked={helperText} onChange={this.handleHelperTextChange} /> <Switch label="Show label info" checked={requiredLabel} onChange={this.handleRequiredLabelChange} /> <IntentSelect intent={intent} onChange={this.handleIntentChange} /> </React.Fragment> ); return ( <Example options={options} {...this.props}> <FormGroup disabled={disabled} helperText={helperText && "Helper text with details..."} inline={inline} intent={intent} label="Label" labelFor="text-input" labelInfo={requiredLabel && "(required)"} > <Input id="text-input" placeholder="Placeholder text" disabled={disabled} intent={intent} /> </FormGroup> <FormGroup disabled={disabled} helperText={helperText && "Helper text with details..."} inline={inline} intent={intent} label="Label" labelFor="text-input" labelInfo={requiredLabel && "(required)"} > <Switch id="text-input" label="Engage the hyperdrive" disabled={disabled} /> <Switch id="text-input" label="Initiate thrusters" disabled={disabled} /> </FormGroup> </Example> ); }
render() { return ( <div> <p> With no imageFit property set, the width and height props control the size of the frame. Depending on which of those props is used, the image may scale to fit the frame. </p> <p> Without a width or height specified, the frame remains at its natural size and the image will not be scaled. </p> <Image src="http://placehold.it/350x150" alt="Example implementation with no image fit property and no height or width is specified." /> <br /> <p> If only a width is provided, the frame will be set to that width. The image will scale proportionally to fill the available width. </p> <Image src="http://placehold.it/350x150" alt="Example implementation with no image fit property and only width is specified." width={600} /> <br /> <p> If only a height is provided, the frame will be set to that height. The image will scale proportionally to fill the available height. </p> <Image src="http://placehold.it/350x150" alt="Example implementation with no image fit property and only height is specified." height={100} /> <br /> <p> If both width and height are provided, the frame will be set to that width and height. The image will scale to fill both the available width and height. <strong>This may result in a distorted image.</strong> </p> <Image src="http://placehold.it/350x150" alt="Example implementation with no image fit property and height or width is specified." width={100} height={100} /> </div> ); }
render() { const imageProps: IImageProps = { src: 'http://placehold.it/500x250', imageFit: ImageFit.none, width: 350, height: 150 }; return ( <div> <p> By setting the imageFit property to "none", the image will remain at its natural size, even if the frame is made larger or smaller by setting the width and height props. </p> <p> The image is larger than the frame, so it is cropped to fit. The image is positioned at the upper left of the frame. </p> <Image {...imageProps} alt="Example implementation of the property image fit using the none value on an image larger than the frame." /> <br /> <p> The image is smaller than the frame, so there is empty space within the frame. The image is positioned at the upper left of the frame. </p> <Image {...imageProps} src="http://placehold.it/100x100" alt="Example implementation of the property image fit using the none value on an image smaller than the frame." /> </div> ); }
render() { const imageProps: IImageProps = { src: 'http://placehold.it/500x250', imageFit: ImageFit.center, width: 350, height: 150 }; return ( <div> <p> Setting the imageFit property to "center" behaves the same as "none", while centering the image within the frame. </p> <p>The image is larger than the frame, so all sides are cropped to center the image.</p> <Image {...imageProps} src="http://placehold.it/800x300" alt="Example implementation of the property image fit using the center value on an image larger than the frame." /> <br /> <p> The image is smaller than the frame, so there is empty space within the frame. The image is centered in the available space. </p> <Image {...imageProps} src="http://placehold.it/100x100" alt="Example implementation of the property image fit using the center value on an image smaller than the frame." /> </div> ); }
render() { const imageProps: IImageProps = { src: 'http://placehold.it/700x300', imageFit: ImageFit.contain, }; return ( <div> <p> Setting the imageFit property to "contain" will scale the image up or down to fit the frame, while maintaining its natural aspect ratio and without cropping the image. </p> <p> The image has a wider aspect ratio (more landscape) than the frame, so the image is scaled to fit the width and centered in the available vertical space. </p> <Image {...imageProps} alt="Example implementation of the property image fit using the contain value on an image wider than the frame." width={200} height={200} /> <br /> <p> The image has a taller aspect ratio (more portrait) than the frame, so the image is scaled to fit the height and centered in the available horizontal space. </p> <Image {...imageProps} alt="Example implementation of the property image fit using the contain value on an image taller than the frame." width={300} height={50} /> </div> ); }
render() { const imageProps: IImageProps = { src: 'http://placehold.it/500X500', imageFit: ImageFit.cover, }; return ( <div> <p> Setting the imageFit property to "cover" will cause the image to scale up or down proportionally, while cropping from either the top and bottom or sides to completely fill the frame. </p> <p> The image has a wider aspect ratio (more landscape) than the frame, so the image is scaled to fit the height and the sides are cropped evenly. </p> <Image {...imageProps} alt="Example implementation of the property image fit using the cover value on an image wider than the frame." width={150} height={250} /> <br /> <p> The image has a taller aspect ratio (more portrait) than the frame, so the image is scaled to fit the width and the top and bottom are cropped evenly. </p> <Image {...imageProps} alt="Example implementation of the property image fit using the cover value on an image taller than the frame." width={250} height={150} /> </div> ); }
render() { const imageProps: IImageProps = { src: 'http://placehold.it/500x500', imageFit: ImageFit.cover, maximizeFrame: true }; return ( <div> <p> Where the exact width and height of the image's frame is not known, such as when sizing an image as a percentage of its parent, you can use the "maximizeFrame" prop to expand the frame to fill the parent element. </p> <p>The image is placed within a landscape container.</p> <div style={{ width: '200px', height: '100px' }}> <Image {...imageProps} alt="Example implementation of the property maximize frame with a landscape container." /> </div> <br /> <p>The image is placed within a portrait container.</p> <div style={{ width: '100px', height: '200px' }}> <Image {...imageProps} alt="Example implementation of the property maximize frame with a portrait container" /> </div> </div> ); }
render() { return [ <div className="at-row"> <Input placeholder="Text input"/> <Input placeholder="Text input" disabled/> <Input placeholder="Text input" readonly/> </div> , <div className="at-row"> <Input placeholder="Text input" round/> <Input placeholder="Text input" intent="primary"/> <Input placeholder="Text input" intent="warning"/> </div> , <div className="at-row" data-modifier=".pt-fill"> <Input placeholder="large and fill" large fill/> </div> ] }
render() { return [ <div className="at-row"> <Input placeholder="Filter histogram..." leftElement="filter" /> <Input placeholder="Filter histogram..." leftElement="filter" disabled/> <Input placeholder="Filter histogram..." leftElement="filter" round/> </div> , <div className="at-row"> <Input placeholder="Enter your password..." rightElement="lock" intent="warning"/> <Input placeholder="Enter your password..." rightElement="lock" disabled/> <Input placeholder="Enter your password..." rightElement="lock" intent="warning" round/> </div> , <div className="at-row" data-modifier=".pt-fill"> <Input placeholder="Filter histogram..." fill leftElement="filter" /> </div> , <div className="at-row" data-modifier=".pt-fill"> <Input placeholder="Enter your password..." fill rightElement="lock" intent="warning"/> </div> , <div className="at-row" data-modifier=".pt-fill"> <Input placeholder="Search" fill leftElement="search" rightElement="arrow-right" intent="success"/> </div> ] }
render() { return [ <div className="at-row"> <div> <TextArea placeholder="Writing..." fill/> </div> <div> <TextArea placeholder="Writing..." disabled fill/> </div> <div> <TextArea placeholder="Writing..." intent="success" fill/> </div> </div> , <div className="at-row"> <div data-modifier=".pt-fill"> <TextArea placeholder="Writing..." rows={3} fill/> </div> </div> , <div className="at-row"> <div data-modifier=".pt-fill"> <TextArea placeholder="Writing..." fill autosize={{minRows: 2, maxRows: 8}}/> </div> </div> ] }
constructor(props) { super(props); this.items = createArray(25, () => { const randomWidth = 50 + Math.floor(Math.random() * 150); return { url: `http://placehold.it/${randomWidth}x100`, content: lorem(10), width: randomWidth, height: 100 }; }); } render() { return ( <Example {...this.props}> <div className="example-block is-scrollable"> <List items={this.items} onRenderCell={this._renderRow.bind(this)} /> </div> </Example> ) } _renderRow(item, index) { return ( <div> <p className="list-textContent">{item.content}</p> <Image src={item.url} width={item.width} height={item.height} /> </div> ) }
ListView
ListView
constructor(props) { super(props); this.items = createArray(25, () => { const randomWidth = 50 + Math.floor(Math.random() * 150); return { url: `http://placehold.it/${randomWidth}x100`, content: lorem(20), width: randomWidth, height: 100 }; }); } render() { return ( <Example {...this.props}> <div className="example-block is-scrollable"> <ListView items={this.items} onRenderItem={this._renderRow.bind(this)} /> </div> </Example> ) } _renderRow(item, index) { return ( <div className="listItem"> <p className="list-textContent">{item.content}</p> <Image src={item.url} width={item.width} height={item.height} /> </div> ) }
render() { return ( <div> <Navbar> <NavbarGroup align={Alignment.LEFT}> <NavbarHeading>Athena</NavbarHeading> <NavbarDivider /> <Button minimal icon="home" text="Home" /> <Button minimal icon="document" text="Files" /> </NavbarGroup> </Navbar> </div> ) }
render() { return [ <div className="at-row"> <NumericInput placeholder="Text input"/> <NumericInput placeholder="Text input" disabled/> <NumericInput placeholder="Text input" readonly/> </div> , <div className="at-row"> <NumericInput placeholder="Text input" round/> <NumericInput placeholder="Text input" intent="primary"/> <NumericInput placeholder="Text input" intent="warning"/> </div> , <div className="at-row" data-modifier=".pt-fill"> <NumericInput placeholder="large and fill" large fill/> </div> ] }
constructor(props) { super(props); this.state = { value: 0, }; this._onValueChanged = this._onValueChanged.bind(this); } render() { const { value } = this.state; return [ <div className="at-row"> <NumericInput placeholder="Text input" value={value} onValueChanged={this._onValueChanged}/> </div> ] } _onValueChanged(evt, value /* value is number type */) { console.log(value); this.setState({ value: value }) }
constructor(props) { super(props); this.refHandlers = { button: (ref) => (this.button = ref), }; this.handleAutoFocusChange = handleBooleanChange(autoFocus => this.setState({ autoFocus })); this.handleBackdropChange = handleBooleanChange(hasBackdrop => this.setState({ hasBackdrop })); this.handleEnforceFocusChange = handleBooleanChange(enforceFocus => this.setState({ enforceFocus })); this.handleEscapeKeyChange = handleBooleanChange(canEscapeKeyClose => this.setState({ canEscapeKeyClose })); this.handleUsePortalChange = handleBooleanChange(usePortal => this.setState({ usePortal })); this.handleOutsideClickChange = handleBooleanChange(val => this.setState({ canOutsideClickClose: val })); this.handleOpen = () => this.setState({ isOpen: true }); this.handleClose = () => this.setState({ isOpen: false }); this.focusButton = () => this.button.focus(); this.state = { autoFocus: true, canEscapeKeyClose: true, canOutsideClickClose: true, enforceFocus: true, hasBackdrop: true, isOpen: false, usePortal: true, }; } render() { const classes = classNames(Classes.CARD, Classes.ELEVATION_4, "docs-overlay-example-transition"); return ( <Example options={this.renderOptions()} {...this.props}> <Button elementRef={this.refHandlers.button} onClick={this.handleOpen} text="Show overlay" /> <Overlay onClose={this.handleClose} className={Classes.OVERLAY_SCROLL_CONTAINER} {...this.state}> <div className={classes}> <H3>I'm an Overlay!</H3> <p> This is a simple container with some inline styles to position it on the screen. Its CSS transitions are customized for this example only to demonstrate how easily custom transitions can be implemented. </p> <p> Click the right button below to transfer focus to the "Show overlay" trigger button outside of this overlay. If persistent focus is enabled, focus will be constrained to the overlay. Use the <Code>tab</Code> key to move to the next focusable element to illustrate this effect. </p> <br /> <Button intent={Intent.DANGER} onClick={this.handleClose}> Close </Button> <Button onClick={this.focusButton} style={{ float: "right" }}> Focus button </Button> </div> </Overlay> </Example> ); } renderOptions() { const { autoFocus, enforceFocus, canEscapeKeyClose, canOutsideClickClose, hasBackdrop, usePortal } = this.state; return ( <React.Fragment> <H5>Props</H5> <Switch checked={autoFocus} label="Auto focus" onChange={this.handleAutoFocusChange} /> <Switch checked={enforceFocus} label="Enforce focus" onChange={this.handleEnforceFocusChange} /> <Switch checked={usePortal} onChange={this.handleUsePortalChange}> Use <Code>Portal</Code> </Switch> <Switch checked={canOutsideClickClose} label="Click outside to close" onChange={this.handleOutsideClickChange} /> <Switch checked={canEscapeKeyClose} label="Escape key to close" onChange={this.handleEscapeKeyChange} /> <Switch checked={hasBackdrop} label="Has backdrop" onChange={this.handleBackdropChange} /> </React.Fragment> ); }
constructor(props) { super(props); this.INTERACTION_KINDS = [ { label: "Click", value: PopoverInteractionKind.CLICK.toString() }, { label: "Click (target only)", value: PopoverInteractionKind.CLICK_TARGET_ONLY.toString() }, { label: "Hover", value: PopoverInteractionKind.HOVER.toString() }, { label: "Hover (target only)", value: PopoverInteractionKind.HOVER_TARGET_ONLY.toString() }, ]; this.VALID_POSITIONS = [ "auto", Position.TOP_LEFT, Position.TOP, Position.TOP_RIGHT, Position.RIGHT_TOP, Position.RIGHT, Position.RIGHT_BOTTOM, Position.BOTTOM_LEFT, Position.BOTTOM, Position.BOTTOM_RIGHT, Position.LEFT_TOP, Position.LEFT, Position.LEFT_BOTTOM, ]; this.state = { canEscapeKeyClose: true, exampleIndex: 0, hasBackdrop: false, inheritDarkTheme: true, interactionKind: PopoverInteractionKind.CLICK, isOpen: false, minimal: false, modifiers: { arrow: { enabled: true }, flip: { enabled: true }, keepTogether: { enabled: true }, preventOverflow: { enabled: true, boundariesElement: "scrollParent" }, }, position: "auto", sliderValue: 5, usePortal: true, } this.getModifierChangeHandler = (name) => { return (evt => { const enabled = evt.target.checked; this.setState({ modifiers: { ...this.state.modifiers, [name]: { ...this.state.modifiers[name], enabled }, }, }); }); } this.handleSliderChange = (value) => this.setState({ sliderValue: value }); this.handleExampleIndexChange = handleNumberChange(exampleIndex => this.setState({ exampleIndex })); this.handleInteractionChange = handleStringChange((interactionKind) => { const hasBackdrop = this.state.hasBackdrop && interactionKind === PopoverInteractionKind.CLICK; this.setState({ interactionKind, hasBackdrop }); }); this.handlePositionChange = handleStringChange((position) => this.setState({ position })); this.handleBoundaryChange = handleStringChange((boundary) => this.setState({ modifiers: { ...this.state.modifiers, preventOverflow: { boundariesElement: boundary, enabled: boundary.length > 0, }, }, }), ); this.toggleEscapeKey = handleBooleanChange(canEscapeKeyClose => this.setState({ canEscapeKeyClose })); this.toggleIsOpen = handleBooleanChange(isOpen => this.setState({ isOpen })); this.toggleMinimal = handleBooleanChange(minimal => this.setState({ minimal })); this.toggleUsePortal = handleBooleanChange(usePortal => { if (usePortal) { this.setState({ hasBackdrop: false, inheritDarkTheme: false }); } this.setState({ usePortal }); }); } render() { const { exampleIndex, sliderValue, ...popoverProps } = this.state; return ( <Example options={this.renderOptions()} {...this.props}> <div className="docs-popover-example-scroll" ref={this.centerScroll}> <Popover popoverClassName={exampleIndex <= 2 ? Classes.POPOVER_CONTENT_SIZING : ""} portalClassName="foo" {...popoverProps} enforceFocus={false} isOpen={this.state.isOpen === true ? /* Controlled */ true : /* Uncontrolled */ undefined} > <Button intent={Intent.PRIMARY} text="Popover target" /> {this.getContents(exampleIndex)} </Popover> <p> Scroll around this container to experiment<br /> with <Code>flip</Code> and <Code>preventOverflow</Code> modifiers. </p> </div> </Example> ); } renderOptions() { const { arrow, flip, preventOverflow } = this.state.modifiers; return ( <React.Fragment> <H5>Appearance</H5> <FormGroup helperText="May be overridden to prevent overflow" label="Position when opened" labelFor="position" > <HTMLSelect value={this.state.position} onChange={this.handlePositionChange} options={this.VALID_POSITIONS} /> </FormGroup> <FormGroup label="Example content"> <HTMLSelect value={this.state.exampleIndex} onChange={this.handleExampleIndexChange}> <option value="0">Text</option> <option value="1">Input</option> <option value="2">Slider</option> <option value="3">Menu</option> <option value="4">Empty</option> </HTMLSelect> </FormGroup> <Switch checked={this.state.usePortal} onChange={this.toggleUsePortal}> Use <Code>Portal</Code> </Switch> <Switch checked={this.state.minimal} label="Minimal appearance" onChange={this.toggleMinimal} /> <Switch checked={this.state.isOpen} label="Open (controlled mode)" onChange={this.toggleIsOpen} /> <H5>Interactions</H5> <RadioGroup label="Interaction kind" selectedValue={this.state.interactionKind.toString()} options={this.INTERACTION_KINDS} onChange={this.handleInteractionChange} /> <Switch checked={this.state.canEscapeKeyClose} label="Can escape key close" onChange={this.toggleEscapeKey} /> <H5>Modifiers</H5> <Switch checked={arrow.enabled} label="Arrow" onChange={this.getModifierChangeHandler("arrow")} /> <Switch checked={flip.enabled} label="Flip" onChange={this.getModifierChangeHandler("flip")} /> <Switch checked={preventOverflow.enabled} label="Prevent overflow" onChange={this.getModifierChangeHandler("preventOverflow")} > <br /> <div style={{ marginTop: 5 }} /> <HTMLSelect disabled={!preventOverflow.enabled} value={preventOverflow.boundariesElement.toString()} onChange={this.handleBoundaryChange} > <option value="scrollParent">scrollParent</option> <option value="viewport">viewport</option> <option value="window">window</option> </HTMLSelect> </Switch> </React.Fragment> ); } getContents(index) { return [ <div key="text"> <H5>Confirm deletion</H5> <p>Are you sure you want to delete these items? You won't be able to recover them.</p> <div style={{ display: "flex", justifyContent: "flex-end", marginTop: 15 }}> <Button className={Classes.POPOVER_DISMISS} style={{ marginRight: 10 }}> Cancel </Button> <Button intent={Intent.DANGER} className={Classes.POPOVER_DISMISS}> Delete </Button> </div> </div>, <div key="input"> <label className={Classes.LABEL}> Enter some text <input autoFocus={true} className={Classes.INPUT} type="text" /> </label> </div>, <Slider key="slider" min={0} max={10} onChange={this.handleSliderChange} value={this.state.sliderValue} />, <Menu key="menu"> <MenuDivider title="Edit" /> <MenuItem icon="cut" text="Cut" label="⌘X" /> <MenuItem icon="duplicate" text="Copy" label="⌘C" /> <MenuItem icon="clipboard" text="Paste" label="⌘V" disabled={true} /> <MenuDivider title="Text" /> <MenuItem icon="align-left" text="Alignment"> <MenuItem icon="align-left" text="Left" /> <MenuItem icon="align-center" text="Center" /> <MenuItem icon="align-right" text="Right" /> <MenuItem icon="align-justify" text="Justify" /> </MenuItem> <MenuItem icon="style" text="Style"> <MenuItem icon="bold" text="Bold" /> <MenuItem icon="italic" text="Italic" /> <MenuItem icon="underline" text="Underline" /> </MenuItem> </Menu>, ][index]; } centerScroll(div) { if (div != null) { // if we don't requestAnimationFrame, this function apparently executes // before styles are applied to the page, so the centering is way off. requestAnimationFrame(() => { const container = div.parentElement; container.scrollTop = div.clientHeight / 4; container.scrollLeft = div.clientWidth / 4; }); } }
constructor(props) { super(props); this.state = { hasValue: false, value: 0.7, } } render() { const { hasValue, intent, value } = this.state; return ( <Example options={this.renderOptions()}> <ProgressBar intent={intent} value={hasValue ? value : null} /> </Example> ) } renderOptions() { const { hasValue, intent, value } = this.state; return ( <div> <h5>Props</h5> <IntentSelect intent={intent} onChange={(evt) => this.setState({ intent: evt.target.value})} /> <Switch checked={hasValue} label="Known value" onChange={(evt) => this.setState({ hasValue: evt.target.checked})} /> <Slider disabled={!hasValue} labelStepSize={1} min={0} max={1} onChange={(value) => this.setState({ value: value})} labelRenderer={this.renderLabel} stepSize={0.1} showTrackFill={false} value={value} /> </div> ) } renderLabel(value) { return value.toFixed(1); }
constructor(props) { super(props); this.state = { value: 1 } } onChange(e) { this.setState({ value: parseInt(e.target.value) }); } render() { return ( <Example {...this.props}> <div> <Radio value="1" checked={this.state.value === 1} onChange={this.onChange.bind(this)}>备选项</Radio> <Radio value="2" checked={this.state.value === 2} onChange={this.onChange.bind(this)}>备选项</Radio> </div> </Example> ) }
render() { return ( <Example {...this.props}> <div> <Radio value="1" disabled>备选项</Radio> <Radio value="2" checked={true} disabled>选中且禁用</Radio> </div> </Example> ) }
constructor(props) { super(props); this.state = { mealType: "one" } } handleMealChange(e) { this.setState({ mealType: e.target.value }); }; render() { return ( <Example {...this.props}> <div> <RadioGroup label="Meal Choice" onChange={this.handleMealChange.bind(this)} selectedValue={this.state.mealType} > <Radio label="Soup" value="one" /> <Radio label="Salad" value="two" /> <Radio label="Sandwich" value="three" /> </RadioGroup> </div> </Example> ) }
constructor(props) { super(props); this._onSelectionChanged = this._onSelectionChanged.bind(this); this._onToggleSelectAll = this._onToggleSelectAll.bind(this); this.state = { items: this._createListItmes(), selection: new Selection({onSelectionChanged: this._onSelectionChanged}), selectionMode: SelectionMode.multiple }; this.state.selection.setItems(this.state.items, false); } render() { const {selection, selectionMode, items} = this.state; return ( <div className="selection"> <div className="selection-item-check"> <Checkbox onChange={this._onToggleSelectAll} checked={selection.isAllSelected()} indeterminate={selection.getSelectedCount() > 0 && !selection.isAllSelected()} >全选</Checkbox> </div> <SelectionZone selection={selection} > {items.map((item, index) => ( this._renderItem(item, index) ))} </SelectionZone> </div> ) } _createListItmes() { const colors = '赤橙黄绿青蓝紫'; return colors.split('').map((c, index) => ({ key: 'k' + index, name: c, })) } _renderItem(item, index) { const {selection} = this.state; let isSelected = false; if (selection && index !== undefined) { isSelected = selection.isIndexSelected(index); } return ( <div key={item.key} className="selection-item" data-is-focusable={true} data-selection-toggle={true} data-selection-index={index} > {selection && selection.canSelectItem(item) && selection.mode !== SelectionMode.none && ( <div className="selection-item-check" data-is-focusable={true}> <Check checked={isSelected} /> </div> )} <span className="selection-item-name">{item.name}</span> </div> ) } _onSelectionChanged() { console.log('onSelectionChanged') this.forceUpdate(); } _onToggleSelectAll(evt) { const { selection } = this.state; selection.toggleAllSelected(); }
constructor(props) { super(props); this.state = { hasValue: false, size: Spinner.SIZE_STANDARD, value: 0.7, }; this.handleIndeterminateChange = handleBooleanChange(hasValue => this.setState({ hasValue })); this.handleModifierChange = handleStringChange((intent) => this.setState({ intent })); this.renderLabel = (value) => value.toFixed(1); this.handleValueChange = (value) => this.setState({ value }); this.handleSizeChange = (size) => this.setState({ size }); } render() { const { size, hasValue, intent, value } = this.state; return ( <Example options={this.renderOptions()} {...this.props}> <Spinner intent={intent} size={size} value={hasValue ? value : null} /> </Example> ); } renderOptions() { const { size, hasValue, intent, value } = this.state; return ( <React.Fragment> <H5>Props</H5> <IntentSelect intent={intent} onChange={this.handleModifierChange} /> <Label>Size</Label> <Slider labelStepSize={50} min={0} max={Spinner.SIZE_LARGE * 2} showTrackFill={false} stepSize={5} value={size} onChange={this.handleSizeChange} /> <Switch checked={hasValue} label="Known value" onChange={this.handleIndeterminateChange} /> <Slider disabled={!hasValue} labelStepSize={1} min={0} max={1} onChange={this.handleValueChange} labelRenderer={this.renderLabel} stepSize={0.1} showTrackFill={false} value={value} /> </React.Fragment> ); }
Spinner
render() { const contentAreas = []; for (let i = 0; i < 5; i++) { contentAreas.push(this._createContentArea(i)); } return ( <Example data-example-id='StickyExample'> <div style={{ height: '600px', width: '400px', position: 'relative', maxHeight: 'inherit' }} > <ScrollablePane className="scrollablePaneDefaultExample"> {contentAreas.map(ele => { return ele; })} </ScrollablePane> </div> </Example> ); } _createContentArea(index) { const colors = ['#eaeaea', '#dadada', '#d0d0d0', '#c8c8c8', '#a6a6a6', '#c7e0f4', '#71afe5', '#eff6fc', '#deecf9']; const color = colors.splice(Math.floor(Math.random() * colors.length), 1)[0]; return ( <div key={index} style={{ backgroundColor: color }} > <Sticky stickyPosition={StickyPositionType.Both}> <div className="sticky">Sticky Component #{index + 1}</div> </Sticky> <div className="textContent">{this.props.lorem(100)}</div> </div> ); }
constructor(props) { super(props); this.data = []; this.state = { desc: this.props.lorem(30), expandedRow: undefined, scrollToIndex: 0, } for (let i=0; i<10000; i++) { this.data.push(`[${i}] ${lorem(10 + Math.round(Math.random() * 50))}`); } } render() { return ( <Example data-example-id='StickyGridExample'> <div style={{ height: '500px', width: '420px', position: 'relative', maxHeight: 'inherit' }} > <ScrollablePane className="scrollablePaneDefaultExample" style={{background: "#f3f3f3"}}> <Sticky stickyPosition={StickyPositionType.Header}> <div className="sticky">Search something ...</div> </Sticky> <div className="textContent">{this.state.desc}</div> <Sticky stickyPosition={StickyPositionType.Header}> <div style={{padding: 4}}> <Input placeholder="Search" fill leftElement="search" rightElement="arrow-right" intent="success"/> </div> </Sticky> <ListView items={this.data} checkboxVisibility={CheckboxVisibility.hidden} onRenderItem={this._renderRow.bind(this)} extra={{expandedRow: this.state.expandedRow}} /> </ScrollablePane> </div> </Example> ); } _renderRow(item, index) { return ( <div className="listItem"> <p className="list-textContent">{item}</p> {this.state.expandedRow !== index && <Button intent="success" small onClick={() => this.setState({expandedRow: index, scrollToIndex: this.state.scrollToIndex + 20})}>展开</Button> } {this.state.expandedRow === index && <div> <Button intent="success" small onClick={() => this.setState({expandedRow: undefined, scrollToIndex: 0})} >收起</Button> <p className="list-textContent">哈哈,增加了一些内容!</p> </div> } </div> ) }
render() { return ( <div> <SvgIcon intent="primary" use="#add" /> <SvgIcon intent="success" use="#search" /> <SvgIcon intent="warning" use="#caret-down" size={48} /> </div> ) }
constructor(props) { super(props); this.state = { focusIndex: 0 } this.onTabChange = (index: number) => { this.setState({ focusIndex: index }) } } render() { return ( <Example {...this.props}> <TabList focusIndex={this.state.focusIndex} onTabChange={this.onTabChange} > <Tab>Loki</Tab> <Tab>Thor</Tab> <Tab>Iron Man</Tab> </TabList> </Example> ) }
constructor(props) { super(props); this.state = { focusIndex: 0 } this.onTabChange = (index: number) => { this.setState({ focusIndex: index }) } } render() { return ( <Example {...this.props}> <div style={{ display: "flex", width: 100, height: 200 }}> <TabList isVertical={true} focusIndex={this.state.focusIndex} onTabChange={this.onTabChange} > <Tab>Loki</Tab> <Tab>Thor</Tab> <Tab>Iron Man</Tab> </TabList> </div> </Example> ) }
Text
constructor(props) { super(props); this.state = { textContent: "You can change the text in the input below. Hover to see full text. " + "If the text is long enough, then the content will overflow. This is done by setting " + "ellipsize to true.", }; this.onInputChange = handleStringChange((textContent) => this.setState({ textContent })); } render() { return ( <Example options={false} {...this.props}> <Text ellipsize={true}> {this.state.textContent} </Text> <TextArea fill={true} onChange={this.onInputChange} value={this.state.textContent} /> </Example> ); }
constructor(props) { super(props); this.refHandlers = { toaster: (ref: Toaster) => (this.toaster = ref), } this.POSITIONS = [ Position.TOP_LEFT, Position.TOP, Position.TOP_RIGHT, Position.BOTTOM_LEFT, Position.BOTTOM, Position.BOTTOM_RIGHT, ]; this.TOAST_BUILDERS = [ { action: { href: "https://www.google.com/search?q=toast&source=lnms&tbm=isch", target: "_blank", text: <strong>Yum.</strong>, }, button: "Procure toast", intent: Intent.PRIMARY, message: ( <React.Fragment> One toast created. <em>Toasty.</em> </React.Fragment> ), }, { action: { onClick: () => this.addToast({ icon: "ban-circle", intent: Intent.DANGER, message: "You cannot undo the past.", }), text: "Undo", }, button: "Move files", icon: "tick", intent: Intent.SUCCESS, message: "Moved 6 files.", }, { action: { onClick: () => this.addToast(this.TOAST_BUILDERS[2]), text: "Retry", }, button: "Delete root", icon: "warning-sign", intent: Intent.DANGER, message: "You do not have permissions to perform this action. \ Please contact your system administrator to request the appropriate access rights.", }, { action: { onClick: () => this.addToast({ message: "Isn't parting just the sweetest sorrow?" }), text: "Adieu", }, button: "Log out", icon: "hand", intent: Intent.WARNING, message: "Goodbye, old friend.", }, ]; this.handlePositionChange = handleStringChange((position) => this.setState({ position })); this.toggleAutoFocus = handleBooleanChange(autoFocus => this.setState({ autoFocus })); this.toggleEscapeKey = handleBooleanChange(canEscapeKeyClear => this.setState({ canEscapeKeyClear })); this.handleProgressToast = () => { let progress = 0; const key = this.toaster.show(this.renderProgress(0)); const interval = setInterval(() => { if (this.toaster == null || progress > 100) { clearInterval(interval); } else { progress += 10 + Math.random() * 20; this.toaster.show(this.renderProgress(progress), key); } }, 1000); }; this.state = { autoFocus: false, canEscapeKeyClear: true, position: Position.TOP, } } render() { return ( <Example options={this.renderOptions()} {...this.props}> {this.TOAST_BUILDERS.map(this.renderToastDemo, this)} <Button onClick={this.handleProgressToast} text="Upload file" /> <Toaster {...this.state} ref={this.refHandlers.toaster} /> </Example> ); } renderOptions() { const { autoFocus, canEscapeKeyClear, position } = this.state; return ( <React.Fragment> <H5>Props</H5> <Label> Position <HTMLSelect value={position} onChange={this.handlePositionChange} options={this.POSITIONS} /> </Label> <Switch label="Auto focus" checked={autoFocus} onChange={this.toggleAutoFocus} /> <Switch label="Can escape key clear" checked={canEscapeKeyClear} onChange={this.toggleEscapeKey} /> </React.Fragment> ); } renderToastDemo(toast, index) { // tslint:disable-next-line:jsx-no-lambda return <Button intent={toast.intent} key={index} text={toast.button} onClick={() => this.addToast(toast)} />; } renderProgress(amount) { return { icon: "cloud-upload", message: ( <ProgressBar className={classNames("docs-toast-progress", { [Classes.PROGRESS_NO_STRIPES]: amount >= 100 })} intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS} value={amount / 100} /> ), timeout: amount < 100 ? 0 : 2000, }; } addToast(toast) { toast.timeout = 5000; this.toaster.show(toast); }
constructor(props) { super(props); this.toggleControlledTooltip = this.toggleControlledTooltip.bind(this); this.state = { isOpen: false, } } render() { // using JSX instead of strings for all content so the tooltips will re-render // with every update for dark theme inheritance. const lotsOfText = ( <span> In facilisis scelerisque dui vel dignissim. Sed nunc orci, ultricies congue vehicula quis, facilisis a orci. </span> ); const jsxContent = ( <em> This tooltip contains an <strong>em</strong> tag. </em> ); return ( <Example options={false} {...this.props}> <div> Inline text can have{" "} <Tooltip className={Classes.TOOLTIP_INDICATOR} content={jsxContent}> a tooltip. </Tooltip> </div> <div> <Tooltip content={lotsOfText}>Or, hover anywhere over this whole line.</Tooltip> </div> <div> This line's tooltip{" "} <Tooltip className={Classes.TOOLTIP_INDICATOR} content={<span>disabled</span>} disabled={true}> is disabled. </Tooltip> </div> <div> This line's tooltip{" "} <Tooltip className={Classes.TOOLTIP_INDICATOR} content={<span>BRRAAAIINS</span>} isOpen={this.state.isOpen} > is controlled by external state. </Tooltip> <Switch checked={this.state.isOpen} label="Open" onChange={this.toggleControlledTooltip} style={{ display: "inline-block", marginBottom: 0, marginLeft: 20 }} /> </div> <div> <Tooltip className={Classes.TOOLTIP_INDICATOR} content="Color.PRIMARY" intent='primary' position={Position.LEFT} usePortal={false} > Available </Tooltip>{" "} <Tooltip className={Classes.TOOLTIP_INDICATOR} content="Color.SUCCESS" intent='success' position={Position.TOP} usePortal={false} > in the full </Tooltip>{" "} <Tooltip className={Classes.TOOLTIP_INDICATOR} content="Color.WARNING" intent='warning' position={Position.BOTTOM} usePortal={false} > range of </Tooltip>{" "} <Tooltip className={Classes.TOOLTIP_INDICATOR} content="Color.DANGER" intent='danger' position={Position.RIGHT} usePortal={false} > visual intents! </Tooltip> </div> <br /> <Popover content={<H1>Popover!</H1>} position={Position.RIGHT} popoverClassName={Classes.POPOVER_CONTENT_SIZING} > <Tooltip content={<span>This button also has a popover!</span>} position={Position.RIGHT} usePortal={false} > <Button intent='success' text="Hover and click me" /> </Tooltip> </Popover> </Example> ) } toggleControlledTooltip() { this.setState({ isOpen: !this.state.isOpen }); }
constructor(props) { super(props); this.treeData = [ { key: '0-0', title: 'parent 1', children: [ { key: '0-0-0', title: 'parent 1-1', children: [ { key: '0-0-0-0', title: 'parent 1-1-0' }, ], }, { key: '0-0-1', title: 'parent 1-2', children: [ { key: '0-0-1-0', title: 'parent 1-2-0', disableCheckbox: true }, { key: '0-0-1-1', title: 'parent 1-2-1' }, ], }, ], }, ]; const keys = ['0-0-0-0']; this.state = { defaultExpandedKeys: keys, defaultSelectedKeys: keys, defaultCheckedKeys: keys, } } render() { return ( <Example {...this.props}> <Tree showLine className="myCls" checkable defaultExpandAll defaultExpandedKeys={this.state.defaultExpandedKeys} defaultSelectedKeys={this.state.defaultSelectedKeys} defaultCheckedKeys={this.state.defaultCheckedKeys} > <TreeNode title="parent 1" key="0-0"> <TreeNode title="parent 1-0" key="0-0-0"> <TreeNode title="leaf" key="0-0-0-0" style={{ background: 'rgba(255, 0, 0, 0.1)' }} /> <TreeNode title="leaf" key="0-0-0-1" /> </TreeNode> <TreeNode title="parent 1-1" key="0-0-1"> <TreeNode title="parent 1-1-0" key="0-0-1-0" /> <TreeNode title="parent 1-1-1" key="0-0-1-1" /> </TreeNode> <TreeNode title="parent 1-2" key="0-0-2" disabled> <TreeNode title="parent 1-2-0" key="0-0-2-0" disabled /> <TreeNode title="parent 1-2-1" key="0-0-2-1" /> </TreeNode> </TreeNode> </Tree> </Example> ) }
export class EasyBizForm extends BaseComponent<IEasyBizFormProps> implements ITabLifecycle { get presenter(): EasyBizFormPresenter<any> { return this.props.presenter as any; } .... render() { return ( <BizFormPage {...this.presenter.getBizFormOptions()} > {...this.presenter.renderTemplateSlot()} </BizFormPage> ); } .... }
export class BizFormPage extends React.Component<IBizFormPageOptions & { tabApi?: ITabAPI }> { render() { const sections: any[] = [ <Section slot="header" key="header"> <BizFormHeader /> </Section>, <Section slot="footer" key="footer"> <BizFormFooter /> </Section>, <Section slot="fixed" key="fiexed"> <BizFormFixedContent /> </Section>, ]; .... const { entityName } = options; const entity = metadata.getEntity(entityName); if (!entity) { return <BizForm {...options}>{sections}</BizForm>; } if (entity.isDocument) { return <Archive sections={sections} {...options} />; } if (entity.isVoucherBill) { return <Bill sections={sections} {...options} />; } return <BizForm {...options}>{sections}</BizForm>; .... } }
interface IArchiveOptions extends IBizFormPageOptions { sections: any[]; } export function Archive(props: IArchiveOptions) { const { sections, displayOptions = {}, menuOptions, ...otherProps } = props; displayOptions.className = cx(displayOptions.className, 'bf-form-entity-archive'); const options = { ...otherProps, displayOptions, menuOptions, }; return <BizForm {...options}>{sections}</BizForm>; }
interface IBillOptions extends IBizFormPageOptions { sections: any[]; } export function Bill(props: IBillOptions) { const { sections, displayOptions = {}, ...otherProps } = props; displayOptions.className = cx(displayOptions.className, 'bf-form-entity-bill'); const options = { ...otherProps, displayOptions, }; return <BizForm {...options}>{sections}</BizForm>; }
export class BizForm extends React.Component<IBizFormOptions & ContextProviderProps> { ... render() { if (this.props.userScreenLoading) { if (this.loadingStatus !== LoadingStatus.Complete) { return null; } } let contentElement; if (this.props.userScreenLoading) { contentElement = this.renderContent(); } else { contentElement = ( <LoadingContainer loadingStatus={this.loadingStatus} className={this.loadingStyle.loadingClassName} style={this.loadingStyle.loadingContainerStyle} > {() => this.renderContent()} </LoadingContainer> ); } return <PresenterProvider value={this.presenter}>{contentElement}</PresenterProvider>; } private renderContent = () => { if (this.props.onRenderContent) { return this.props.onRenderContent({ presenter: this.presenter, form: this.form, renderFormContent: this.renderFormContent }); } return this.renderFormContent(); }; ... private renderFormContent = () => { return ( <Observer> {() => { ... const entityName = this.presenter.options.entityName; const className = cx( displayOptions.className, `bf-form-layout-${mode}`, `bf-form-entity-${entityName}`, ); return ( <Page layout="BizFormLayout" className={className}> {/* 内容区,根据 template 进行布局并显示主体内容 */} <Section slot="content"> <Observer render={this.renderFormBody} /> </Section> </Page> ); }} </Observer> ); } ... private renderFormBody = () => { const authController = this.presenter.getBean(BeanNames.AuthController); if (!authController.hasAuthority) { return this.renderAuthorizedFailed(this.presenter); } return <BizFormBody key={`${this.randomKey}`} />; }; }
export class BizFormBody extends React.Component<{presenter?: BizFormPresenter;tabApi?: ITabAPI;}> { ... render() { return ( <Observer> {() => ( <LoadingContainer loadingStatus={this.loadingStatus}> {() => <QwertRegion circluar={false}>{this.renderZones()}</QwertRegion>} </LoadingContainer> )} </Observer> ); } renderZones() { const zones = this.presenter.template.zones; const zonesList = zones.map((zone, index) => { if (zone.type === BizFormZoneType.form) { return <FormZone key={index} template={zone as any} index={index} />; } else if (zone.type === BizFormZoneType.grid) { return <GridZone key={index} template={zone as any} />; } }); const gridZones = zones.filter(zone => zone.type === BizFormZoneType.grid); if (!gridZones.length && this.needRenderEmptyGrid) { const template: any = { // TODO 暂时这样 后面调整 layout: FormZoneLayoutType.flow, type: BizFormZoneType.grid }; zonesList.push(<GridZone key={zones.length} template={template} />); } // 编辑态是否显示表尾区 const footerZone = zones.find(zone => zone.type === BizFormZoneType.footer); if (footerZone) { const displayFooterWhenEdit = !footerZone.hiddenInEdit; if (displayFooterWhenEdit) { zonesList.push(<FormZone key={zones.length} template={footerZone as any} />); } } return zonesList; } ... }
export class FormZone extends React.Component<FormZoneProps & { presenter?: BizFormPresenter }> { render() { ... const content = template.layout === FormZoneLayoutType.flow ? <FormZoneInFlow key="flow" {...this.props} sections={normalSections} /> : <FormZoneInTab key="tab" {...this.props} sections={normalSections} />; return ( <QwertElement> {() => { return <> ... {content} </> }} </QwertElement> ); } }
export class FormZoneInFlow extends React.Component<FormZoneProps> { render() { return ( <QwertRegion> <Observer> {() => ( <div className="bf-zone bf-form-zone"> {(this.props.sections || []) .filter((section, index) => { if (!section.isCustomized) { const { id } = section; if(id){ return this.masterController.isSectionVisibleById(id); } return this.masterController.isSectionVisible(index); } return true; }) .map((section, index) => { if (section.isCustomized) { return this.renderCustomizedSection(section as ICustomizedSection, index); } return this.renderSection(section as IFormZoneSection, index); })} </div> )} </Observer> </QwertRegion> ); } renderSection(section: IFormZoneSection, index: number) { const cornerMark = { name: section.cornerMark, }; return ( <div key={`${index}`} className="bf-form-section"> <Section title={section.title} icon={'iconbiaotiqianzhui'} cornerMark={cornerMark}> <MasterForm template={section} /> </Section> </div> ); } renderCustomizedSection(section: ICustomizedSection, index: number) { return ( <div key={`${index}`} className={cx('bf-form-section', section.warpperClassName)} > <Section title={section.title} icon="iconbiaotiqianzhui" className={section.className} rightElement={section.rightElement}> {section.render({ section, index, type: FormZoneLayoutType.flow, presenter: this.props.presenter, })} </Section> </div> ); } }
export class Section extends React.Component<ISectionProps> { render() { const { titleClass, contentClass, className, icon, title, children, rightClass, rightElement, isEmpty = false, dragPreviewRef, cornerMark = {}, } = this.props; const { name: cornerMarkName, position = 'top-right' } = cornerMark; return ( <div className={cx('atx-section', styles.section, className)} onClick={this.props.onClick}> {cornerMarkName && ( <div className={cx(styles['corner-mark'], styles[position])}> <span>{cornerMarkName}</span> </div> )} {title && ( <div ref={dragPreviewRef} className={cx('atx-section-header', styles.header, titleClass)}> {icon && <SvgIcon className={styles.icon} use={`#${icon}`} />} <div className={cx('atx-section-header-title', styles.title)}>{title}</div> <div className={cx('atx-section-header-right', styles.right, rightClass)}> {rightElement} </div> </div> )} <Observer> {() => !isEmpty && ( <div className={cx('atx-section-content', styles.content, contentClass)}> {children} </div> ) } </Observer> </div> ); } }
export class MasterForm extends React.Component<MasterFormProps & {presenter?: BizFormPresenter; qwertElementParams?: IQwertElementParams }> { ... render() { const { template, presenter } = this.props; ... return ( <QwertRegion> <FormLayout columnSize={template.columnSize} disableError={this.disableError}> <> {template.fields .filter((field) => field.visible) .map((fieldTemplate, index) => { // 读取字段的 模板信息 const { fieldName, isSlotField } = fieldTemplate; if (isSlotField) { return this.renderSlotField(fieldTemplate); } const fieldModel = presenter.model.master.fieldIndex[fieldName]; const isExtend = presenter.model.master.isExtendField(fieldName); const extendModel = presenter.model.master.extendFieldIndex[fieldName]; const props = { path: fieldName, form: formController.form, template: fieldTemplate, model: fieldModel, index: index, isExtend: isExtend, extendModel: extendModel }; return masterRenderController.renderField(props); })} </> </FormLayout> </QwertRegion> ); } renderSlotField(template: IMasterField) { const formController = this.props.presenter.getBean(BeanNames.FormController); const { displayOptions = {} } = this.props.presenter.options; if (displayOptions.masterSlot && displayOptions.masterSlot[template.fieldName]) { return displayOptions.masterSlot[template.fieldName]({ form: formController.form, path: template.fieldName }); } return <span>`这个插槽还没有被使用: ${template.fieldName}`</span>; } }
export enum Alignment { Left = 'Left', Right = 'Right', Center = 'Center', } export interface FormLayoutProps { className?: string; disableError?: boolean; columnSize?: number; // labelWidth?: number; labelAlignment?: Alignment; horizontalSpacing?: number; verticalSpacing?: number; layoutSize?: 'small' | 'normal' | 'large'; children: Array<JSX.Element> | JSX.Element; } /** * 前端组件 */ export enum ComponentType { CheckBox = 'CheckBox', Text = 'Text', Number = 'Number', DatePicker = 'DatePicker', Refer = 'Refer', Enum = 'Enum', List = 'List', MultiLineText = 'MultiLineText', TimeInput = 'TimeInput', Unknown = 'Unknown', } export class FormLayout extends React.PureComponent<FormLayoutProps> { static defaultProps = { columnSize: 1, layoutSize: 'normal', horizontalSpacing: 4, verticalSpacing: 4, }; private getClassNames() { const { columnSize, className, disableError, layoutSize, horizontalSpacing, verticalSpacing, } = this.props; const runtimeStyle = css` .formElement{ width: ${100 / columnSize}%; padding-right: ${horizontalSpacing}px; &.double{ width: ${(100 / columnSize) * 2}%; } &.triple{ width: ${(100 / columnSize) * 3}%; } &.quatary{ width: ${(100 / columnSize) * 4}%; } &.fivetimes{ width: ${(100 / columnSize) * 5}%; } &.sixtimes{ width: ${(100 / columnSize) * 6}%; } /* margin-bottom: ${verticalSpacing}px; */ } `; return cx( styles.root, className, { [`at-FormLayout--disable-error`]: disableError, [`${styles.root}-${layoutSize}`]: layoutSize, }, runtimeStyle, ); } render() { ... return ( <div className={this.getClassNames()}> {childrens} </div> ); } }
export interface FormElementProps { className?: string; disableError?: boolean; label?: string; colspan?: number; isRequired?: boolean; suppressShowLabel?: boolean; errorMessage?: string; description?: string; suffix?: any; children?: any | ((options) => any); showTooltip?: boolean; // 是否是显示 tooltip labelRenderer?: () => JSX.Element; contentRenderer?: () => JSX.Element; componentType?: ComponentType; } export class FormElement extends React.Component<FormElementProps> { private labelRenderer = () => { if (this.props.labelRenderer) { return this.props.labelRenderer(); } const { label, isRequired, disableError, errorMessage } = this.props; return ( <FormElementLabel label={label} isRequired={isRequired} disableError={disableError} errorMessage={errorMessage} /> ); }; private contentRenderer = () => { if (this.props.contentRenderer) { return this.props.contentRenderer(); } const { disableError, errorMessage, children, suffix, description, ...otherProps } = this.props; return ( <FormElementContent disableError={disableError} errorMessage={errorMessage} description={description} children={children} suffix={suffix} {...otherProps} /> ); }; render() { const { disableError, suppressShowLabel, colspan = 1, className, componentType, errorMessage } = this.props; return ( <FormElementSkeleton classNames={cx(className, { 'formElement--disableError': disableError})} colspan={colspan} suppressShowLabel={suppressShowLabel} labelRenderer={() => this.labelRenderer()} contentRenderer={() => this.contentRenderer()} componentType={componentType} /> ); } }
/** * 快捷 FormElement 布局组件 */ export interface FormElementSkeletonProps { classNames?: string; colspan?: number; suppressShowLabel?: boolean; labelRenderer?: () => JSX.Element; contentRenderer: () => JSX.Element; componentType?: ComponentType; } export class FormElementSkeleton extends React.PureComponent<FormElementSkeletonProps> { private getClass = () => { const { componentType } = this.props; switch (componentType) { case ComponentType.MultiLineText: return 'multiLineTextElement'; } return ''; }; render() { const { colspan = 1, classNames = '', suppressShowLabel, labelRenderer, contentRenderer, } = this.props; const colspanArray = ['', 'double', 'triple', 'quatary', 'fivetimes', 'sixtimes']; const contentStyle = suppressShowLabel ? { paddingLeft: 0 } : null; // 多行文本高度自适应 return ( <div className={cx(classNames, 'formElement ' + colspanArray[colspan - 1], this.getClass())}> {!suppressShowLabel ? <div className="formLabel">{labelRenderer()}</div> : null} <div style={contentStyle} className="formContent"> {contentRenderer()} </div> </div> ); } }
export interface FormElementLabelProps { disableError?: boolean; label: string; isRequired?: boolean; errorMessage?: string; } /** * 快捷 FormElementLabel 组件 */ export class FormElementLabel extends React.PureComponent<FormElementLabelProps> { render() { const { label, isRequired, disableError, errorMessage } = this.props; return ( <React.Fragment> {disableError && errorMessage && <ErrorTip error={errorMessage} />} {isRequired && <Text className="required">*</Text>} <Text ellipsize={true}>{label}</Text> </React.Fragment> ); } }
export interface FormElementContentProps { disableError?: boolean; errorMessage?: string; description?: string; suffix?: any; children: any | ((options) => any); showTooltip?: boolean; // 是否是显示 tooltip } /** * 快捷 FormElementContent 组件 */ export class FormElementContent extends React.PureComponent<FormElementContentProps> { private formInputRef: any; render() { const { disableError, errorMessage, suffix, description } = this.props; const children = this.renderChildren(); return ( <Observer> {() => ( <React.Fragment> <div className="formInput" ref={this.handleRef}> {children} {suffix} </div> {!disableError && errorMessage && <div className="formError" title={errorMessage}>{errorMessage}</div>} {!errorMessage && description && <div className="formDescription">{description}</div>} </React.Fragment> )} </Observer> ); } renderChildren() { const { children, showTooltip = false } = this.props; if (showTooltip) { return ( <TooltipElement getMaxWidth={this.getMaxWidth}> {children} </TooltipElement> ); } return children; } handleRef = (ref) => { if (ref) { this.formInputRef = ref; } }; getMaxWidth = () => { let maxWidth = 0; if (this.formInputRef) { maxWidth = this.formInputRef.clientWidth; } return maxWidth; }; }
export class GridZone extends React.Component<GridZoneProps & { presenter?: BizFormPresenter }> { render() { ... return ( <QwertElement> {() => { if (template.layout === FormZoneLayoutType.flow) { return <GridZoneInFlow {...this.props} />; } else { return <GridZoneInTab {...this.props} />; } }} </QwertElement> ); } }
export interface GridZoneProps { template: IGridZone; } // 在 GridZone 中,一个 Grid 的定义 export interface IGridZoneSection { // 子表字段 fieldName: string; // 标题 title?: string; // 图标 icon?: string; // 表体字段 fields: Array<IDetailField>; // 自定义渲染 customerRender?: (presenter: any) => JSX.Element; // 自定义类名 className?: string; // 右侧自定义渲染 rightElement?: (() => JSX.Element) | JSX.Element; } export class GridZoneInFlow extends React.Component<GridZoneProps> { render() { return ( <div className="bf-zone bg-grid-zone-wrapper"> <div className="bf-zone bf-grid-zone"> {(this.props.sections || []).map((section, index) => { return this.renderGridSection(section as IGridZoneSection, index); })} </div> </div> ); } renderGridSection(section: IGridZoneSection, index: number) { const contentView = ( <ul className='atx-grid'> {(section.fields || []).map((field, index) => { const styleRules = { width: field.width } return <li style={styleRules}>{field.title}</li> })} </ul> ); return ( <Section title={section.title}> {contentView} </Section> ); } }
mkdir customize_components cd customize_components cnpm init -y touch .gitignore
npm i react @types/react react-dom @types/react-dom -S npm i webpack webpack-cli webpack-dev-server -D npm i typescript ts-loader source-map-loader style-loader css-loader less-loader less file-loader url-loader html-webpack-plugin -D npm i axios express qs @types/qs -D
模块名 |
使用方式 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tsc --init { "compilerOptions": { "target": "es5", "module": "commonjs", "jsx": "react", "outDir": "./dist", "rootDir": "./src", "noImplicitAny":true, "esModuleInterop": true }, "include": [ "./src/**/*", "./typings/**/*" ] }
参数 |
含义 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const path = require('path'); module.exports = { mode: 'development', entry: "./src/index.tsx", output: { path: path.join(__dirname, 'dist') }, devtool: "source-map", devServer: { hot: true, contentBase: path.join(__dirname, 'dist'), historyApiFallback: { index: './index.html' } }, resolve: { extensions: [".ts", ".tsx", ".js", ".json"] }, module: { rules: [{ test: /\.tsx?$/, loader: "ts-loader" }, { enforce: "pre", test: /\.tsx$/, loader: "source-map-loader" }, { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }, { test: /\.(jpg|png|gif|svg)$/, loader: "url-loader" } ] }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }), new webpack.HotModuleReplacementPlugin() ], };
"scripts": { "build": "webpack", "dev": "webpack-dev-server", }
<body> <div id="root"></div> </body>
import React from 'react'; import ReactDOM from 'react-dom'; import Tree from './components/tree'; import data from './data'; ReactDOM.render(, document.getElementById('root'));
export interface TreeData { name: string; key: string; type: string; collapsed: boolean; children?: Array<TreeData>; parent?: TreeData; checked?: boolean; loading?: boolean; }
import { TreeData } from './typings'; const data: TreeData = { name: '父亲', key: '1', type: 'folder', collapsed: false, children: [ { name: '儿子1', key: '1-1', type: 'folder', collapsed: false, children: [ { name: '孙子1', key: '1-1-1', type: 'folder', collapsed: false, children: [ { name: '重孙1', key: '1-1-1-1', type: 'file', collapsed: false, children: [] } ] } ] }, { name: '儿子2', key: '1-2', type: 'folder', collapsed: true } ] } export default data;
import React from 'react'; import './index.less'; import { TreeData } from '../typings'; import TreeNode from './tree-node'; import { getChildren } from '../api'; interface Props { data: TreeData; } interface KeyToNodeMap { [key: string]: TreeData } interface State { data: TreeData; fromNode?: TreeData; } class Tree extends React.Component<Props, State> { data: TreeData; keyToNodeMap: KeyToNodeMap; constructor(props: Props) { super(props); this.state = { data: this.props.data }; this.data = props.data; this.buildKeyMap(); } buildKeyMap = () => { let data = this.data; this.keyToNodeMap = {}; this.keyToNodeMap[data.key] = data; if (data.children && data.children.length > 0) { this.walk(data.children, data); } this.setState({ data: this.state.data }); } walk = (children: Array<TreeData>, parent: TreeData): void => { children.map((item: TreeData) => { item.parent = parent; this.keyToNodeMap[item.key] = item; if (item.children && item.children.length > 0) { this.walk(item.children, item); } }); } onCollapse = async (key: string) => { let data = this.keyToNodeMap[key]; if (data) { let { children } = data; if (!children) { data.loading = true; this.setState({ data: this.state.data }); let result = await getChildren(data); if (result.code == 0) { data.children = result.data; data.collapsed = false; data.loading = false; this.buildKeyMap(); } else { alert('加载失败'); } } else { data.collapsed = !data.collapsed; this.setState({ data: this.state.data }); } } } onCheck = (key: string) => { let data: TreeData = this.keyToNodeMap[key]; if (data) { data.checked = !data.checked; if (data.checked) { this.checkChildren(data.children, true); this.checkParentCheckAll(data.parent); } else { this.checkChildren(data.children, false); this.checkParent(data.parent, false); } this.setState({ data: this.state.data }); } } checkParentCheckAll = (parent: TreeData) => { while (parent) { parent.checked = parent.children.every(item => item.checked); parent = parent.parent; } } checkParent = (parent: TreeData, checked: boolean) => { while (parent) { parent.checked = checked; parent = parent.parent; } } checkChildren = (children: Array<TreeData> = [], checked: boolean) => { children.forEach((item: TreeData) => { item.checked = checked; this.checkChildren(item.children, checked); }); } setFromNode = (fromNode: TreeData) => { this.setState({ ...this.state, fromNode }); } onMove = (toNode: TreeData) => { let fromNode = this.state.fromNode; let fromChildren = fromNode.parent.children, toChildren = toNode.parent.children; let fromIndex = fromChildren.findIndex((item: TreeData) => item === fromNode); let toIndex = toChildren.findIndex(item => item === toNode); fromChildren.splice(fromIndex, 1, toNode); toChildren.splice(toIndex, 1, fromNode); this.buildKeyMap(); } render() { return ( <div className="tree"> <div className="tree-nodes"> <TreeNode data={this.props.data} onCollapse={this.onCollapse} onCheck={this.onCheck} setFromNode={this.setFromNode} onMove={this.onMove} /> </div> </div> ) } } export default Tree;
import React from 'react'; import { TreeData } from '../typings'; import file from '../assets/file.png'; import closedFolder from '../assets/closed-folder.png'; import openedFolder from '../assets/opened-folder.png'; import loadingSrc from '../assets/loading.gif'; interface Props { data: TreeData, onCollapse: any, onCheck: any; setFromNode: any; onMove: any } class TreeNode extends React.Component<Props> { treeNodeRef: React.RefObject<HTMLDivElement>; constructor(props: Props) { super(props); this.treeNodeRef = React.createRef(); } componentDidMount() { this.treeNodeRef.current.addEventListener('dragstart', (event: DragEvent): void => { this.props.setFromNode(this.props.data); event.stopPropagation(); }, false);//useCapture=false this.treeNodeRef.current.addEventListener('dragenter', (event: DragEvent) => { event.preventDefault(); event.stopPropagation(); }, false); this.treeNodeRef.current.addEventListener('dragover', (event: DragEvent) => { event.preventDefault(); event.stopPropagation(); }, false); this.treeNodeRef.current.addEventListener('drop', (event: DragEvent) => { event.preventDefault(); this.props.onMove(this.props.data); event.stopPropagation(); }, false); } render() { let { data: { name, children, collapsed = false, key, checked = false, loading } } = this.props; let caret, icon; if (children) { if (children.length > 0) { caret = ( <span className={`collapse ${collapsed ? 'caret-right' : 'caret-down'}`} onClick={() => this.props.onCollapse(key)} /> ) icon = collapsed ? closedFolder : openedFolder; } else { caret = null; icon = file; } } else { caret = ( loading ? <img className="collapse" src={loadingSrc} style={{ width: 14, top: '50%', marginTop: -7 }} /> : <span className={`collapse caret-right`} onClick={() => this.props.onCollapse(key)} /> ) icon = closedFolder; } return ( <div className="tree-node" draggable={true} ref={this.treeNodeRef}> <div className="inner"> {caret} <span className="content"> <input type="checkbox" checked={checked} onChange={() => this.props.onCheck(key)} /> <img style={{ width: 20 }} src={icon} /> {name} </span> </div> { (children && children.length > 0 && !collapsed) && ( <div className="children"> { children.map((item: TreeData) => ( <TreeNode onCollapse={this.props.onCollapse} onCheck={this.props.onCheck} key={item.key} setFromNode={this.props.setFromNode} onMove={this.props.onMove} data={item} /> )) } </div> ) } </div> ) } } export default TreeNode;
.tree { width: 80%; overflow-x: hidden; overflow-y: auto; background-color: #fff; .tree-nodes { position: relative; overflow: hidden; .tree-node { .inner { color: #000; font-size: 16px; position: relative; cursor: pointer; padding-left: 10px; .collapse { position: absolute; left: 0; cursor: pointer; } .caret-right:before { content: '\25B8'; } .caret-down:before { content: '\25BE'; } .content { display: inline-block; width: 100%; padding: 4px 5px; } } .children { padding-left: 20px; } } } }
declare module '*.svg'; declare module '*.png'; declare module '*.jpg'; declare module '*.jpeg'; declare module '*.gif'; declare module '*.bmp'; declare module '*.tiff';
import axios from 'axios'; import qs from 'qs'; axios.defaults.baseURL = 'http://localhost:3000'; export const getChildren = (data: any) => { return axios.get(`/getChildren?${qs.stringify({ key: data.key, name: data.name })}`).then(res => res.data).catch(function (error) { console.log(error); }); }
let express = require('express'); let app = express(); app.use((req, res, next) => { res.setHeader('Access-Control-Allow-Origin', '*'); if (req.method === 'OPTIONS') { return res.sendStatus(200); } next(); }); app.get('/getChildren', (req, res) => { let data = req.query; setTimeout(function () { res.json({ code: 0, data: [ { name: data.name', key: `${data.key}-1`, type: 'folder', collapsed: true }, { name: data.name', key: `${data.key}-2`, type: 'folder', collapsed: true } ] }); }, 2000) }); app.listen(3000, () => { console.log(`接口服务器在${3000}上启动`); });