mirror of
https://github.com/desktop/desktop
synced 2024-09-12 21:01:16 +00:00
Merge pull request #709 from desktop/the-component-revolution
The People's Glorious Component Revolution
This commit is contained in:
commit
92ddb95a3b
|
@ -3,6 +3,10 @@ import * as React from 'react'
|
|||
|
||||
import { Dispatcher } from '../../lib/dispatcher'
|
||||
import { initGitRepository, isGitRepository } from '../../lib/git'
|
||||
import { Button } from '../lib/button'
|
||||
import { Form } from '../lib/form'
|
||||
import { TextBox } from '../lib/text-box'
|
||||
import { Row } from '../lib/row'
|
||||
|
||||
const untildify: (str: string) => string = require('untildify')
|
||||
|
||||
|
@ -28,27 +32,21 @@ export class AddExistingRepository extends React.Component<IAddExistingRepositor
|
|||
public render() {
|
||||
const disabled = this.state.path.length === 0 || this.state.isGitRepository == null
|
||||
return (
|
||||
<div id='add-existing-repository'>
|
||||
<div className='add-repo-form'>
|
||||
<label>Local Path</label>
|
||||
<Form onSubmit={this.addRepository}>
|
||||
<Row>
|
||||
<TextBox
|
||||
value={this.state.path}
|
||||
label='Local Path'
|
||||
placeholder='repository path'
|
||||
onChange={this.onPathChanged}
|
||||
onKeyDown={this.onKeyDown}/>
|
||||
<Button onClick={this.showFilePicker}>Choose…</Button>
|
||||
</Row>
|
||||
|
||||
<div className='file-picker'>
|
||||
<input value={this.state.path}
|
||||
type='text'
|
||||
placeholder='repository path'
|
||||
onChange={this.onPathChanged}
|
||||
onKeyDown={this.onKeyDown}/>
|
||||
|
||||
<button onClick={this.showFilePicker}>Choose…</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='popup-actions'>
|
||||
<button disabled={disabled} onClick={this.addRepository}>
|
||||
{this.state.isGitRepository ? 'Add Repository' : 'Create & Add Repository'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Button disabled={disabled} type='submit'>
|
||||
{this.state.isGitRepository ? 'Add Repository' : 'Create & Add Repository'}
|
||||
</Button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,10 @@ import * as FS from 'fs'
|
|||
import { Dispatcher } from '../../lib/dispatcher'
|
||||
import { initGitRepository } from '../../lib/git'
|
||||
import { sanitizedRepositoryName } from './sanitized-repository-name'
|
||||
import { Form } from '../lib/form'
|
||||
import { TextBox } from '../lib/text-box'
|
||||
import { Button } from '../lib/button'
|
||||
import { Row } from '../lib/row'
|
||||
|
||||
interface ICreateRepositoryProps {
|
||||
readonly dispatcher: Dispatcher
|
||||
|
@ -73,33 +77,30 @@ export class CreateRepository extends React.Component<ICreateRepositoryProps, IC
|
|||
public render() {
|
||||
const disabled = this.state.path.length === 0 || this.state.name.length === 0
|
||||
return (
|
||||
<div id='create-repository' className='panel'>
|
||||
<div>
|
||||
<label>Name
|
||||
<input value={this.state.name}
|
||||
placeholder='repository name'
|
||||
onChange={this.onNameChanged}/>
|
||||
</label>
|
||||
</div>
|
||||
<Form>
|
||||
<TextBox
|
||||
value={this.state.name}
|
||||
label='Name'
|
||||
placeholder='repository name'
|
||||
onChange={this.onNameChanged}/>
|
||||
|
||||
{this.renderError()}
|
||||
|
||||
<div className='file-picker'>
|
||||
<label>Local Path
|
||||
<input value={this.state.path}
|
||||
placeholder='repository path'
|
||||
onChange={this.onPathChanged}/>
|
||||
</label>
|
||||
|
||||
<button onClick={this.showFilePicker}>Choose…</button>
|
||||
</div>
|
||||
<Row>
|
||||
<TextBox
|
||||
value={this.state.path}
|
||||
label='Local Path'
|
||||
placeholder='repository path'
|
||||
onChange={this.onPathChanged}/>
|
||||
<Button onClick={this.showFilePicker}>Choose…</Button>
|
||||
</Row>
|
||||
|
||||
<hr/>
|
||||
|
||||
<button disabled={disabled} onClick={this.createRepository}>
|
||||
<Button type='submit' disabled={disabled} onClick={this.createRepository}>
|
||||
Create Repository
|
||||
</button>
|
||||
</div>
|
||||
</Button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import { Welcome } from './welcome'
|
|||
import { AppMenu } from './app-menu'
|
||||
import { UpdateAvailable } from './updates'
|
||||
import { shouldRenderApplicationMenu } from './lib/features'
|
||||
import { Button } from './lib/button'
|
||||
|
||||
/** The interval at which we should check for updates. */
|
||||
const UpdateCheckInterval = 1000 * 60 * 60 * 4
|
||||
|
@ -415,7 +416,7 @@ export class App extends React.Component<IAppProps, IAppState> {
|
|||
{msgs.map((msg, i) => <pre className='popup-error-output' key={i}>{msg}</pre>)}
|
||||
|
||||
<div className='popup-actions'>
|
||||
<button onClick={this.clearErrors}>OK</button>
|
||||
<Button onClick={this.clearErrors}>OK</Button>
|
||||
</div>
|
||||
</Popuppy>
|
||||
)
|
||||
|
|
|
@ -2,6 +2,7 @@ import * as React from 'react'
|
|||
import { List } from '../list'
|
||||
import { IAutocompletionProvider } from './index'
|
||||
import { fatalError } from '../../lib/fatal-error'
|
||||
import * as classNames from 'classnames'
|
||||
|
||||
interface IPosition {
|
||||
readonly top: number
|
||||
|
@ -192,8 +193,13 @@ export abstract class AutocompletingTextInput<ElementType extends HTMLInputEleme
|
|||
}
|
||||
|
||||
public render() {
|
||||
const tagName = this.getElementTagName()
|
||||
const className = classNames('autocompletion-container', this.props.className, {
|
||||
'text-box-component': tagName === 'input',
|
||||
'text-area-component': tagName === 'textarea',
|
||||
})
|
||||
return (
|
||||
<div className={`autocompletion-container ${this.props.className || ''}`}>
|
||||
<div className={className}>
|
||||
{this.renderAutocompletions()}
|
||||
|
||||
{this.renderTextInput()}
|
||||
|
|
|
@ -51,6 +51,7 @@ export class Checkbox extends React.Component<ICheckboxProps, void> {
|
|||
public render() {
|
||||
return (
|
||||
<input
|
||||
className='checkbox-component'
|
||||
tabIndex={this.props.tabIndex}
|
||||
type='checkbox'
|
||||
onChange={this.onChange}
|
||||
|
|
|
@ -8,6 +8,7 @@ import { CommitIdentity } from '../../models/commit-identity'
|
|||
import { ICommitMessage } from '../../lib/app-state'
|
||||
import { Dispatcher } from '../../lib/dispatcher'
|
||||
import { Repository } from '../../models/repository'
|
||||
import { Button } from '../lib/button'
|
||||
|
||||
interface ICommitMessageProps {
|
||||
readonly onCreateCommit: (message: ICommitMessage) => void
|
||||
|
@ -111,9 +112,8 @@ export class CommitMessage extends React.Component<ICommitMessageProps, ICommitM
|
|||
})
|
||||
}
|
||||
|
||||
private handleSubmit = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
private handleSubmit = () => {
|
||||
this.createCommit()
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
private createCommit() {
|
||||
|
@ -159,16 +159,12 @@ export class CommitMessage extends React.Component<ICommitMessageProps, ICommitM
|
|||
)
|
||||
}
|
||||
|
||||
private onFormSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
public render() {
|
||||
const branchName = this.props.branch ? this.props.branch : 'master'
|
||||
const buttonEnabled = this.canCommit()
|
||||
|
||||
return (
|
||||
<form id='commit-message' onSubmit={this.onFormSubmit}>
|
||||
<div id='commit-message'>
|
||||
<div className='summary'>
|
||||
{this.renderAvatar()}
|
||||
|
||||
|
@ -187,10 +183,10 @@ export class CommitMessage extends React.Component<ICommitMessageProps, ICommitM
|
|||
onKeyDown={this.onKeyDown}
|
||||
autocompletionProviders={this.props.autocompletionProviders}/>
|
||||
|
||||
<button className='button commit-button' onClick={this.handleSubmit} disabled={!buttonEnabled}>
|
||||
<Button type='submit' className='commit-button' onClick={this.handleSubmit} disabled={!buttonEnabled}>
|
||||
<div>Commit to <strong>{branchName}</strong></div>
|
||||
</button>
|
||||
</form>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import * as React from 'react'
|
|||
import { Commit } from '../../models/commit'
|
||||
import { EmojiText } from '../lib/emoji-text'
|
||||
import { RelativeTime } from '../relative-time'
|
||||
import { Button } from '../lib/button'
|
||||
|
||||
interface IUndoCommitProps {
|
||||
/** The function to call when the Undo button is clicked. */
|
||||
|
@ -25,7 +26,7 @@ export class UndoCommit extends React.Component<IUndoCommitProps, void> {
|
|||
<EmojiText emoji={this.props.emoji} className='summary'>{this.props.commit.summary}</EmojiText>
|
||||
</div>
|
||||
<div className='actions'>
|
||||
<button className='button' onClick={this.props.onUndo}>Undo</button>
|
||||
<Button type='submit' onClick={this.props.onUndo}>Undo</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -4,6 +4,10 @@ import { Repository } from '../../models/repository'
|
|||
import { Dispatcher } from '../../lib/dispatcher'
|
||||
import { sanitizedBranchName } from './sanitized-branch-name'
|
||||
import { Branch } from '../../models/branch'
|
||||
import { Form } from '../lib/form'
|
||||
import { TextBox } from '../lib/text-box'
|
||||
import { Button } from '../lib/button'
|
||||
import { Select } from '../lib/select'
|
||||
|
||||
interface ICreateBranchProps {
|
||||
readonly repository: Repository
|
||||
|
@ -50,29 +54,30 @@ export class CreateBranch extends React.Component<ICreateBranchProps, ICreateBra
|
|||
const disabled = !proposedName.length || !!this.state.currentError
|
||||
const currentBranch = this.props.currentBranch
|
||||
return (
|
||||
<form id='create-branch' className='panel' onSubmit={this.createBranch}>
|
||||
<Form onSubmit={this.createBranch}>
|
||||
<div className='header'>Create New Branch</div>
|
||||
<hr/>
|
||||
|
||||
<label>Name
|
||||
<input type='text'
|
||||
autoFocus={true}
|
||||
onChange={this.onBranchNameChange}
|
||||
onKeyDown={this.onKeyDown}/>
|
||||
</label>
|
||||
<TextBox
|
||||
label='Name'
|
||||
autoFocus={true}
|
||||
onChange={this.onBranchNameChange}
|
||||
onKeyDown={this.onKeyDown}/>
|
||||
|
||||
{this.renderError()}
|
||||
|
||||
<label>From
|
||||
<select onChange={this.onBaseBranchChange}
|
||||
defaultValue={currentBranch ? currentBranch.name : undefined}>
|
||||
{this.props.branches.map(branch => <option key={branch.name} value={branch.name}>{branch.name}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<Select
|
||||
label='From'
|
||||
onChange={this.onBaseBranchChange}
|
||||
defaultValue={currentBranch ? currentBranch.name : undefined}>
|
||||
{this.props.branches.map(branch =>
|
||||
<option key={branch.name} value={branch.name}>{branch.name}</option>
|
||||
)}
|
||||
</Select>
|
||||
|
||||
<hr/>
|
||||
<button type='submit' disabled={disabled}>Create Branch</button>
|
||||
</form>
|
||||
<Button type='submit' disabled={disabled}>Create Branch</Button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -110,9 +115,7 @@ export class CreateBranch extends React.Component<ICreateBranchProps, ICreateBra
|
|||
})
|
||||
}
|
||||
|
||||
private createBranch = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault()
|
||||
|
||||
private createBranch = () => {
|
||||
const name = this.state.sanitizedName
|
||||
const baseBranch = this.state.baseBranch
|
||||
if (name.length > 0 && baseBranch) {
|
||||
|
|
|
@ -3,6 +3,8 @@ import * as React from 'react'
|
|||
import { Dispatcher } from '../../lib/dispatcher'
|
||||
import { Repository } from '../../models/repository'
|
||||
import { Branch } from '../../models/branch'
|
||||
import { Form } from '../lib/form'
|
||||
import { Button } from '../lib/button'
|
||||
|
||||
interface IDeleteBranchProps {
|
||||
readonly dispatcher: Dispatcher
|
||||
|
@ -13,19 +15,17 @@ interface IDeleteBranchProps {
|
|||
export class DeleteBranch extends React.Component<IDeleteBranchProps, void> {
|
||||
public render() {
|
||||
return (
|
||||
<form className='panel' onSubmit={this.cancel}>
|
||||
<Form onSubmit={this.cancel}>
|
||||
<div>Delete branch "{this.props.branch.name}"?</div>
|
||||
<div>This cannot be undone.</div>
|
||||
|
||||
<button type='submit'>Cancel</button>
|
||||
<button onClick={this.deleteBranch}>Delete</button>
|
||||
</form>
|
||||
<Button type='submit'>Cancel</Button>
|
||||
<Button onClick={this.deleteBranch}>Delete</Button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
private cancel = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault()
|
||||
|
||||
private cancel = () => {
|
||||
this.props.dispatcher.closePopup()
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ import * as React from 'react'
|
|||
import { Repository } from '../../models/repository'
|
||||
import { Dispatcher } from '../../lib/dispatcher'
|
||||
import { WorkingDirectoryFileChange } from '../../models/status'
|
||||
import { Form } from '../lib/form'
|
||||
import { Button } from '../lib/button'
|
||||
|
||||
interface IDiscardChangesProps {
|
||||
readonly repository: Repository
|
||||
|
@ -15,19 +17,17 @@ export class DiscardChanges extends React.Component<IDiscardChangesProps, void>
|
|||
public render() {
|
||||
const paths = this.props.files.map(f => f.path).join(', ')
|
||||
return (
|
||||
<form className='panel' onSubmit={this.cancel}>
|
||||
<Form onSubmit={this.cancel}>
|
||||
<div>Confirm Discard Changes</div>
|
||||
<div>Are you sure you want to discard all changes to {paths}?</div>
|
||||
|
||||
<button type='submit'>Cancel</button>
|
||||
<button onClick={this.discard}>Discard Changes</button>
|
||||
</form>
|
||||
<Button type='submit'>Cancel</Button>
|
||||
<Button onClick={this.discard}>Discard Changes</Button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
private cancel = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault()
|
||||
|
||||
private cancel = () => {
|
||||
this.props.dispatcher.closePopup()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import * as React from 'react'
|
||||
import { LinkButton } from '../lib/link-button'
|
||||
import { Button } from '../lib/button'
|
||||
import { Octicon, OcticonSymbol } from '../octicons'
|
||||
import {
|
||||
createAuthorization,
|
||||
|
@ -13,6 +12,10 @@ import { User } from '../../models/user'
|
|||
import { assertNever } from '../../lib/fatal-error'
|
||||
import { askUserToOAuth } from '../../lib/oauth'
|
||||
import { Loading } from './loading'
|
||||
import { Form } from './form'
|
||||
import { Button } from './button'
|
||||
import { TextBox } from './text-box'
|
||||
import { Errors } from './errors'
|
||||
|
||||
interface IAuthenticationFormProps {
|
||||
/** The endpoint against which the user is authenticating. */
|
||||
|
@ -49,13 +52,13 @@ export class AuthenticationForm extends React.Component<IAuthenticationFormProps
|
|||
|
||||
public render() {
|
||||
return (
|
||||
<form className='sign-in-form' onSubmit={this.signIn}>
|
||||
<Form className='sign-in-form' onSubmit={this.signIn}>
|
||||
{this.renderUsernamePassword()}
|
||||
|
||||
{this.renderError()}
|
||||
|
||||
{this.renderSignInWithBrowser()}
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -65,15 +68,22 @@ export class AuthenticationForm extends React.Component<IAuthenticationFormProps
|
|||
const disabled = this.state.loading
|
||||
return (
|
||||
<div>
|
||||
<div className='field-group'>
|
||||
<label htmlFor='sign-in-name'>Username or email address</label>
|
||||
<input id='sign-in-name' className='text-field sign-in-field' disabled={disabled} autoFocus={true} onChange={this.onUsernameChange}/>
|
||||
</div>
|
||||
<TextBox
|
||||
label='Username or email address'
|
||||
disabled={disabled}
|
||||
autoFocus={true}
|
||||
onChange={this.onUsernameChange}/>
|
||||
|
||||
<div className='field-group'>
|
||||
<label htmlFor='sign-in-password'>Password</label>
|
||||
<input id='sign-in-password' className='sign-in-field' type='password' disabled={disabled} onChange={this.onPasswordChange}/>
|
||||
<LinkButton className='forgot-password-link' uri={this.getForgotPasswordURL()}>Forgot password?</LinkButton>
|
||||
<div className='password-container'>
|
||||
<TextBox
|
||||
label='Password'
|
||||
secure={true}
|
||||
disabled={disabled}
|
||||
onChange={this.onPasswordChange}/>
|
||||
|
||||
<LinkButton className='forgot-password-link' uri={this.getForgotPasswordURL()}>
|
||||
Forgot password?
|
||||
</LinkButton>
|
||||
</div>
|
||||
|
||||
{this.renderActions()}
|
||||
|
@ -115,13 +125,13 @@ export class AuthenticationForm extends React.Component<IAuthenticationFormProps
|
|||
if (!response) { return null }
|
||||
|
||||
switch (response.kind) {
|
||||
case AuthorizationResponseKind.Failed: return <div className='form-errors'>The username or password are incorrect.</div>
|
||||
case AuthorizationResponseKind.Failed: return <Errors>The username or password are incorrect.</Errors>
|
||||
case AuthorizationResponseKind.Error: {
|
||||
const error = response.response.error
|
||||
if (error) {
|
||||
return <div className='form-errors'>An error occurred: {error.message}</div>
|
||||
return <Errors>An error occurred: {error.message}</Errors>
|
||||
} else {
|
||||
return <div className='form-errors'>An unknown error occurred: {response.response.statusCode}: {response.response.body}</div>
|
||||
return <Errors>An unknown error occurred: {response.response.statusCode}: {response.response.body}</Errors>
|
||||
}
|
||||
}
|
||||
case AuthorizationResponseKind.TwoFactorAuthenticationRequired: return null
|
||||
|
@ -157,9 +167,7 @@ export class AuthenticationForm extends React.Component<IAuthenticationFormProps
|
|||
this.props.onDidSignIn(user)
|
||||
}
|
||||
|
||||
private signIn = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault()
|
||||
|
||||
private signIn = async () => {
|
||||
const username = this.state.username
|
||||
const password = this.state.password
|
||||
this.setState({
|
||||
|
|
|
@ -16,19 +16,29 @@ interface IButtonProps {
|
|||
|
||||
/** CSS class names */
|
||||
readonly className?: string
|
||||
|
||||
/**
|
||||
* The `ref` for the underlying <button> element.
|
||||
*
|
||||
* Ideally this would be named `ref`, but TypeScript seems to special-case its
|
||||
* handling of the `ref` type into some ungodly monstrosity. Hopefully someday
|
||||
* this will be unnecessary.
|
||||
*/
|
||||
readonly reference?: React.Ref<HTMLButtonElement>
|
||||
}
|
||||
|
||||
/** A button component. */
|
||||
export class Button extends React.Component<IButtonProps, void> {
|
||||
public render() {
|
||||
const className = classNames('button', this.props.className)
|
||||
const className = classNames('button-component', this.props.className)
|
||||
|
||||
return (
|
||||
<button
|
||||
className={className}
|
||||
disabled={this.props.disabled}
|
||||
onClick={this.onClick}
|
||||
type={this.props.type}>
|
||||
type={this.props.type}
|
||||
ref={this.props.reference}>
|
||||
{this.props.children}
|
||||
</button>
|
||||
)
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import * as React from 'react'
|
||||
import { Button } from './button'
|
||||
import { getEnterpriseAPIURL, fetchMetadata } from '../../lib/api'
|
||||
import { Loading } from './loading'
|
||||
import { validateURL, InvalidURLErrorName, InvalidProtocolErrorName } from './enterprise-validate-url'
|
||||
import { Form } from './form'
|
||||
import { TextBox } from './text-box'
|
||||
import { Button } from './button'
|
||||
import { Errors } from './errors'
|
||||
|
||||
/** The authentication methods server allows. */
|
||||
export enum AuthenticationMethods {
|
||||
|
@ -38,20 +41,19 @@ export class EnterpriseServerEntry extends React.Component<IEnterpriseServerEntr
|
|||
const disableEntry = this.state.loading
|
||||
const disableSubmission = !this.state.serverAddress.length || this.state.loading
|
||||
return (
|
||||
<form className='sign-in-form' id='enterprise-server-entry' onSubmit={this.onSubmit}>
|
||||
<div className='field-group'>
|
||||
<label htmlFor='enterprise-address'>Enterprise server address</label>
|
||||
<input id='enterprise-address' className='text-field sign-in-field' autoFocus={true} disabled={disableEntry} onChange={this.onServerAddressChanged}/>
|
||||
</div>
|
||||
<Form onSubmit={this.onSubmit}>
|
||||
<TextBox
|
||||
label='Enterprise server address'
|
||||
autoFocus={true}
|
||||
disabled={disableEntry}
|
||||
onChange={this.onServerAddressChanged}/>
|
||||
|
||||
<div className='actions'>
|
||||
<Button type='submit' disabled={disableSubmission}>Continue</Button>
|
||||
</div>
|
||||
<Button type='submit' disabled={disableSubmission}>Continue</Button>
|
||||
|
||||
{this.state.loading ? <Loading/> : null}
|
||||
|
||||
<div>{this.state.error ? this.state.error.message : null }</div>
|
||||
</form>
|
||||
{this.state.error ? <Errors>{this.state.error.message}</Errors> : null}
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -82,9 +84,7 @@ export class EnterpriseServerEntry extends React.Component<IEnterpriseServerEntr
|
|||
}
|
||||
}
|
||||
|
||||
private onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault()
|
||||
|
||||
private onSubmit = async () => {
|
||||
const userEnteredAddress = this.state.serverAddress
|
||||
let address: string
|
||||
try {
|
||||
|
|
22
app/src/ui/lib/errors.tsx
Normal file
22
app/src/ui/lib/errors.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import * as React from 'react'
|
||||
import * as classNames from 'classnames'
|
||||
|
||||
interface IErrorsProps {
|
||||
/** The class name for the internal element. */
|
||||
readonly className?: string
|
||||
|
||||
/** The children to be displayed as an error message. */
|
||||
readonly children?: ReadonlyArray<JSX.Element>
|
||||
}
|
||||
|
||||
/** An Errors element with app-standard styles. */
|
||||
export class Errors extends React.Component<IErrorsProps, void> {
|
||||
public render() {
|
||||
const className = classNames('errors-component', this.props.className)
|
||||
return (
|
||||
<div className={className}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
30
app/src/ui/lib/form.tsx
Normal file
30
app/src/ui/lib/form.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
import * as React from 'react'
|
||||
import * as classNames from 'classnames'
|
||||
|
||||
interface IFormProps {
|
||||
/** The class name for the form. */
|
||||
readonly className?: string
|
||||
|
||||
/** Called when the form is submitted. */
|
||||
readonly onSubmit?: () => void
|
||||
}
|
||||
|
||||
/** A form element with app-standard styles. */
|
||||
export class Form extends React.Component<IFormProps, void> {
|
||||
public render() {
|
||||
const className = classNames('form-component', this.props.className)
|
||||
return (
|
||||
<form className={className} onSubmit={this.onSubmit}>
|
||||
{this.props.children}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
private onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault()
|
||||
|
||||
if (this.props.onSubmit) {
|
||||
this.props.onSubmit()
|
||||
}
|
||||
}
|
||||
}
|
22
app/src/ui/lib/row.tsx
Normal file
22
app/src/ui/lib/row.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import * as React from 'react'
|
||||
import * as classNames from 'classnames'
|
||||
|
||||
interface IRowProps {
|
||||
/** The class name for the internal element. */
|
||||
readonly className?: string
|
||||
|
||||
/** The children to be displayed in a row. */
|
||||
readonly children?: ReadonlyArray<JSX.Element>
|
||||
}
|
||||
|
||||
/** A horizontal row element with app-standard styles. */
|
||||
export class Row extends React.Component<IRowProps, void> {
|
||||
public render() {
|
||||
const className = classNames('row-component', this.props.className)
|
||||
return (
|
||||
<div className={className}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
36
app/src/ui/lib/select.tsx
Normal file
36
app/src/ui/lib/select.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import * as React from 'react'
|
||||
|
||||
interface ISelectProps {
|
||||
/** The label for the select control. */
|
||||
readonly label?: string
|
||||
|
||||
/** The value of the select control. */
|
||||
readonly value?: string
|
||||
|
||||
/** The default value of the select control. */
|
||||
readonly defaultValue?: string
|
||||
|
||||
/** Called when the user changes the selected valued. */
|
||||
readonly onChange?: (event: React.FormEvent<HTMLSelectElement>) => void
|
||||
|
||||
/** The <option>'s for the select control. */
|
||||
readonly children?: ReadonlyArray<JSX.Element>
|
||||
}
|
||||
|
||||
/** A select element with app-standard styles. */
|
||||
export class Select extends React.Component<ISelectProps, void> {
|
||||
public render() {
|
||||
return (
|
||||
<label className='select-component'>
|
||||
{this.props.label}
|
||||
|
||||
<select
|
||||
onChange={this.props.onChange}
|
||||
value={this.props.value}
|
||||
defaultValue={this.props.defaultValue}>
|
||||
{this.props.children}
|
||||
</select>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
}
|
56
app/src/ui/lib/text-box.tsx
Normal file
56
app/src/ui/lib/text-box.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import * as React from 'react'
|
||||
import * as classNames from 'classnames'
|
||||
|
||||
interface ITextBoxProps {
|
||||
/** The label for the input field. */
|
||||
readonly label?: string
|
||||
|
||||
/** The class name for the label. */
|
||||
readonly labelClassName?: string
|
||||
|
||||
/** The class name for the input field. */
|
||||
readonly inputClassName?: string
|
||||
|
||||
/** The placeholder for the input field. */
|
||||
readonly placeholder?: string
|
||||
|
||||
/** The current value of the input field. */
|
||||
readonly value?: string
|
||||
|
||||
/** Whether the input field should be for secure entry. */
|
||||
readonly secure?: boolean
|
||||
|
||||
/** Whether the input field should auto focus when mounted. */
|
||||
readonly autoFocus?: boolean
|
||||
|
||||
/** Whether the input field is disabled. */
|
||||
readonly disabled?: boolean
|
||||
|
||||
/** Called when the user changes the value in the input field. */
|
||||
readonly onChange?: (event: React.FormEvent<HTMLInputElement>) => void
|
||||
|
||||
/** Called on key down. */
|
||||
readonly onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void
|
||||
}
|
||||
|
||||
/** An input element with app-standard styles. */
|
||||
export class TextBox extends React.Component<ITextBoxProps, void> {
|
||||
public render() {
|
||||
const className = classNames('text-box-component', this.props.labelClassName)
|
||||
return (
|
||||
<label className={className}>
|
||||
{this.props.label}
|
||||
|
||||
<input
|
||||
autoFocus={this.props.autoFocus}
|
||||
className={this.props.inputClassName}
|
||||
disabled={this.props.disabled}
|
||||
type={!this.props.secure ? 'text' : 'password'}
|
||||
placeholder={this.props.placeholder}
|
||||
value={this.props.value}
|
||||
onChange={this.props.onChange}
|
||||
onKeyDown={this.props.onKeyDown}/>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,9 +1,12 @@
|
|||
import * as React from 'react'
|
||||
import { createAuthorization, AuthorizationResponse, fetchUser, AuthorizationResponseKind } from '../../lib/api'
|
||||
import { User } from '../../models/user'
|
||||
import { Button } from './button'
|
||||
import { assertNever } from '../../lib/fatal-error'
|
||||
import { Loading } from './loading'
|
||||
import { Button } from './button'
|
||||
import { TextBox } from './text-box'
|
||||
import { Form } from './form'
|
||||
import { Errors } from './errors'
|
||||
|
||||
interface ITwoFactorAuthenticationProps {
|
||||
/** The endpoint to authenticate against. */
|
||||
|
@ -43,20 +46,19 @@ export class TwoFactorAuthentication extends React.Component<ITwoFactorAuthentic
|
|||
authentication code and verify your identity.
|
||||
</p>
|
||||
|
||||
<form id='2fa-form' className='sign-in-form' onSubmit={this.signIn}>
|
||||
<div className='field-group'>
|
||||
<label htmlFor='two-factor-code'>Authentication code</label>
|
||||
<input id='two-factor-code' className='text-field sign-in-field' disabled={textEntryDisabled} autoFocus={true} onChange={this.onOTPChange}/>
|
||||
</div>
|
||||
<Form onSubmit={this.signIn}>
|
||||
<TextBox
|
||||
label='Authentication code'
|
||||
disabled={textEntryDisabled}
|
||||
autoFocus={true}
|
||||
onChange={this.onOTPChange}/>
|
||||
|
||||
{this.renderError()}
|
||||
|
||||
<div className='actions'>
|
||||
<Button type='submit' disabled={signInDisabled}>Verify</Button>
|
||||
<Button type='submit' disabled={signInDisabled}>Verify</Button>
|
||||
|
||||
{this.state.loading ? <Loading/> : null}
|
||||
</div>
|
||||
</form>
|
||||
{this.state.loading ? <Loading/> : null}
|
||||
</Form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -67,14 +69,14 @@ export class TwoFactorAuthentication extends React.Component<ITwoFactorAuthentic
|
|||
|
||||
switch (response.kind) {
|
||||
case AuthorizationResponseKind.Authorized: return null
|
||||
case AuthorizationResponseKind.Failed: return <div className='form-errors'>Failed</div>
|
||||
case AuthorizationResponseKind.TwoFactorAuthenticationRequired: return <div className='form-errors'>2fa</div>
|
||||
case AuthorizationResponseKind.Failed: return <Errors>Failed</Errors>
|
||||
case AuthorizationResponseKind.TwoFactorAuthenticationRequired: return <Errors>2fa</Errors>
|
||||
case AuthorizationResponseKind.Error: {
|
||||
const error = response.response.error
|
||||
if (error) {
|
||||
return <div className='form-errors'>An error occurred: {error.message}</div>
|
||||
return <Errors>An error occurred: {error.message}</Errors>
|
||||
} else {
|
||||
return <div className='form-errors'>An unknown error occurred: {response.response.statusCode}: {response.response.body}</div>
|
||||
return <Errors>An unknown error occurred: {response.response.statusCode}: {response.response.body}</Errors>
|
||||
}
|
||||
}
|
||||
default: return assertNever(response, `Unknown response: ${response}`)
|
||||
|
@ -89,7 +91,7 @@ export class TwoFactorAuthentication extends React.Component<ITwoFactorAuthentic
|
|||
})
|
||||
}
|
||||
|
||||
private signIn = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
private signIn = async () => {
|
||||
event.preventDefault()
|
||||
|
||||
this.setState({
|
||||
|
|
|
@ -3,6 +3,10 @@ import { Dispatcher } from '../../lib/dispatcher'
|
|||
import { Repository } from '../../models/repository'
|
||||
import { User } from '../../models/user'
|
||||
import { API, IAPIUser, getDotComAPIEndpoint } from '../../lib/api'
|
||||
import { Form } from '../lib/form'
|
||||
import { TextBox } from '../lib/text-box'
|
||||
import { Button } from '../lib/button'
|
||||
import { Select } from '../lib/select'
|
||||
|
||||
interface IPublishRepositoryProps {
|
||||
readonly dispatcher: Dispatcher
|
||||
|
@ -107,9 +111,7 @@ export class PublishRepository extends React.Component<IPublishRepositoryProps,
|
|||
return this.state.selectedUser
|
||||
}
|
||||
|
||||
private publishRepository = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault()
|
||||
|
||||
private publishRepository = () => {
|
||||
const owningAccount = this.findOwningUserForSelectedUser()!
|
||||
this.props.dispatcher.publishRepository(this.props.repository, this.state.name, this.state.description, this.state.private, owningAccount, this.selectedOrg)
|
||||
this.props.dispatcher.closePopup()
|
||||
|
@ -147,25 +149,19 @@ export class PublishRepository extends React.Component<IPublishRepositoryProps,
|
|||
const value = JSON.stringify(this.state.selectedUser)
|
||||
|
||||
return (
|
||||
<select value={value} onChange={this.onAccountChange}>
|
||||
<Select label='Account' value={value} onChange={this.onAccountChange}>
|
||||
{optionGroups}
|
||||
</select>
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
|
||||
public render() {
|
||||
const disabled = !this.state.name.length
|
||||
return (
|
||||
<form id='publish-repository' className='panel' onSubmit={this.publishRepository}>
|
||||
<label>
|
||||
Name:
|
||||
<input type='text' value={this.state.name} autoFocus={true} onChange={this.onNameChange}/>
|
||||
</label>
|
||||
<Form onSubmit={this.publishRepository}>
|
||||
<TextBox label='Name' value={this.state.name} autoFocus={true} onChange={this.onNameChange}/>
|
||||
|
||||
<label>
|
||||
Description:
|
||||
<input type='text' value={this.state.description} onChange={this.onDescriptionChange}/>
|
||||
</label>
|
||||
<TextBox label='Description' value={this.state.description} onChange={this.onDescriptionChange}/>
|
||||
|
||||
<hr/>
|
||||
|
||||
|
@ -174,13 +170,10 @@ export class PublishRepository extends React.Component<IPublishRepositoryProps,
|
|||
<input type='checkbox' checked={this.state.private} onChange={this.onPrivateChange}/>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Account:
|
||||
{this.renderAccounts()}
|
||||
</label>
|
||||
{this.renderAccounts()}
|
||||
|
||||
<button type='submit' disabled={disabled}>Publish Repository</button>
|
||||
</form>
|
||||
<Button type='submit' disabled={disabled}>Publish Repository</Button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@ import { Dispatcher } from '../../lib/dispatcher'
|
|||
import { Repository } from '../../models/repository'
|
||||
import { Branch } from '../../models/branch'
|
||||
import { sanitizedBranchName } from '../create-branch/sanitized-branch-name'
|
||||
import { Form } from '../lib/form'
|
||||
import { TextBox } from '../lib/text-box'
|
||||
import { Button } from '../lib/button'
|
||||
|
||||
interface IRenameBranchProps {
|
||||
readonly dispatcher: Dispatcher
|
||||
|
@ -34,19 +37,19 @@ export class RenameBranch extends React.Component<IRenameBranchProps, IRenameBra
|
|||
public render() {
|
||||
const disabled = !this.state.newName.length
|
||||
return (
|
||||
<form className='panel' onSubmit={this.renameBranch}>
|
||||
<label>
|
||||
Name <input value={this.state.newName}
|
||||
autoFocus={true}
|
||||
onChange={this.onNameChange}
|
||||
onKeyDown={this.onKeyDown}/>
|
||||
</label>
|
||||
<Form onSubmit={this.renameBranch}>
|
||||
<TextBox
|
||||
label='Name'
|
||||
autoFocus={true}
|
||||
value={this.state.newName}
|
||||
onChange={this.onNameChange}
|
||||
onKeyDown={this.onKeyDown}/>
|
||||
|
||||
{this.renderError()}
|
||||
|
||||
<button onClick={this.cancel}>Cancel</button>
|
||||
<button type='submit' disabled={disabled}>Rename {this.props.branch.name}</button>
|
||||
</form>
|
||||
<Button onClick={this.cancel}>Cancel</Button>
|
||||
<Button type='submit' disabled={disabled}>Rename {this.props.branch.name}</Button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -64,9 +67,7 @@ export class RenameBranch extends React.Component<IRenameBranchProps, IRenameBra
|
|||
this.props.dispatcher.closePopup()
|
||||
}
|
||||
|
||||
private renameBranch = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault()
|
||||
|
||||
private renameBranch = () => {
|
||||
const name = sanitizedBranchName(this.state.newName)
|
||||
this.props.dispatcher.renameBranch(this.props.repository, this.props.branch, name)
|
||||
this.props.dispatcher.closePopup()
|
||||
|
|
|
@ -2,6 +2,7 @@ import * as React from 'react'
|
|||
import { Octicon, OcticonSymbol } from '../octicons'
|
||||
import * as classNames from 'classnames'
|
||||
import { assertNever } from '../../lib/fatal-error'
|
||||
import { Button } from '../lib/button'
|
||||
|
||||
/** The button style. */
|
||||
export enum ToolbarButtonStyle {
|
||||
|
@ -77,11 +78,11 @@ export class ToolbarButton extends React.Component<IToolbarButtonProps, void> {
|
|||
return (
|
||||
<div className={className}>
|
||||
{preContent}
|
||||
<button onClick={this.onClick} ref={this.onButtonRef}>
|
||||
<Button onClick={this.onClick} reference={this.onButtonRef}>
|
||||
{icon}
|
||||
{this.renderText()}
|
||||
{this.props.children}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@ import { Commit } from '../../models/commit'
|
|||
import { getGlobalConfigValue, setGlobalConfigValue } from '../../lib/git/config'
|
||||
import { CommitListItem } from '../history/commit-list-item'
|
||||
import { User } from '../../models/user'
|
||||
import { Form } from '../lib/form'
|
||||
import { Button } from '../lib/button'
|
||||
import { TextBox } from '../lib/text-box'
|
||||
import { CommitIdentity } from '../../models/commit-identity'
|
||||
|
||||
interface IConfigureGitProps {
|
||||
|
@ -71,22 +74,16 @@ export class ConfigureGit extends React.Component<IConfigureGitProps, IConfigure
|
|||
This is used to identify the commits you create. Anyone will be able to see this information if you publish commits.
|
||||
</p>
|
||||
|
||||
<form className='sign-in-form' onSubmit={this.continue}>
|
||||
<div className='field-group'>
|
||||
<label htmlFor='git-name'>Name</label>
|
||||
<input id='git-name' className='sign-in-field text-field' placeholder='Hubot' value={this.state.name} onChange={this.onNameChange}/>
|
||||
</div>
|
||||
<Form className='sign-in-form' onSubmit={this.continue}>
|
||||
<TextBox label='Name' placeholder='Hubot' value={this.state.name} onChange={this.onNameChange}/>
|
||||
|
||||
<div className='field-group'>
|
||||
<label htmlFor='git-email'>Email</label>
|
||||
<input id='git-email' className='sign-in-field text-field' placeholder='hubot@github.com' value={this.state.email} onChange={this.onEmailChange}/>
|
||||
</div>
|
||||
<TextBox label='Email' placeholder='hubot@github.com' value={this.state.email} onChange={this.onEmailChange}/>
|
||||
|
||||
<div className='actions'>
|
||||
<button type='submit'>Continue</button>
|
||||
<button className='secondary-button' onClick={this.cancel}>Cancel</button>
|
||||
<Button type='submit'>Continue</Button>
|
||||
<Button onClick={this.cancel}>Cancel</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
<div id='commit-list' className='commit-list-example'>
|
||||
<CommitListItem commit={dummyCommit1} emoji={emoji} avatarURL={null}/>
|
||||
|
@ -121,9 +118,7 @@ export class ConfigureGit extends React.Component<IConfigureGitProps, IConfigure
|
|||
return matchingUser ? matchingUser.avatarURL : null
|
||||
}
|
||||
|
||||
private continue = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault()
|
||||
|
||||
private continue = async () => {
|
||||
this.props.done()
|
||||
|
||||
const name = this.state.name
|
||||
|
@ -137,9 +132,7 @@ export class ConfigureGit extends React.Component<IConfigureGitProps, IConfigure
|
|||
}
|
||||
}
|
||||
|
||||
private cancel = (event: React.FormEvent<HTMLButtonElement>) => {
|
||||
event.preventDefault()
|
||||
|
||||
private cancel = () => {
|
||||
this.props.advance(WelcomeStep.Start)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ export class SignInDotCom extends React.Component<ISignInDotComProps, void> {
|
|||
endpoint={getDotComAPIEndpoint()}
|
||||
supportsBasicAuth={true}
|
||||
additionalButtons={[
|
||||
<Button className='secondary-button' key='cancel' onClick={this.cancel}>Cancel</Button>,
|
||||
<Button key='cancel' onClick={this.cancel}>Cancel</Button>,
|
||||
]}
|
||||
onDidSignIn={this.onDidSignIn}/>
|
||||
</div>
|
||||
|
|
|
@ -52,7 +52,7 @@ export class SignInEnterprise extends React.Component<ISignInEnterpriseProps, IS
|
|||
endpoint={step.endpoint}
|
||||
supportsBasicAuth={step.authMethods.has(AuthenticationMethods.BasicAuth)}
|
||||
additionalButtons={[
|
||||
<Button className='secondary-button' key='cancel' onClick={this.cancel}>Cancel</Button>,
|
||||
<Button key='cancel' onClick={this.cancel}>Cancel</Button>,
|
||||
]}
|
||||
onDidSignIn={this.onDidSignIn}/>
|
||||
} else {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as React from 'react'
|
||||
import { WelcomeStep } from './welcome'
|
||||
import { LinkButton } from '../lib/link-button'
|
||||
import { Button } from '../lib/button'
|
||||
|
||||
const CreateAccountURL = 'https://github.com/join?source=github-desktop'
|
||||
|
||||
|
@ -21,8 +22,8 @@ export class Start extends React.Component<IStartProps, void> {
|
|||
<h1 className='welcome-title'>Welcome to GitHub Desktop</h1>
|
||||
<h2 className='welcome-text'>Get started by signing into GitHub.com or your GitHub Enterprise server.</h2>
|
||||
<div className='actions'>
|
||||
<button className='button welcome-button' onClick={this.signInToDotCom}>GitHub.com</button>
|
||||
<button className='button welcome-button' onClick={this.signInToEnterprise}>GitHub Enterprise</button>
|
||||
<Button type='submit' className='welcome-button' onClick={this.signInToDotCom}>GitHub.com</Button>
|
||||
<Button type='submit' className='welcome-button' onClick={this.signInToEnterprise}>GitHub Enterprise</Button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
@import "ui/app";
|
||||
@import "ui/app-menu";
|
||||
@import "ui/forms";
|
||||
@import "ui/buttons";
|
||||
@import "ui/scroll";
|
||||
@import "ui/window/title-bar";
|
||||
@import "ui/file-list";
|
||||
|
@ -21,12 +19,17 @@
|
|||
@import "ui/popup";
|
||||
@import "ui/progress";
|
||||
@import "ui/branches";
|
||||
@import "ui/add-existing-repository";
|
||||
@import "ui/create-repository";
|
||||
@import "ui/publish-repository";
|
||||
@import "ui/emoji";
|
||||
@import "ui/ui-view";
|
||||
@import "ui/autocompletion";
|
||||
@import "ui/welcome";
|
||||
@import "ui/foldout";
|
||||
@import "ui/update-available";
|
||||
@import "ui/form";
|
||||
@import "ui/text-box";
|
||||
@import "ui/button";
|
||||
@import "ui/select";
|
||||
@import "ui/row";
|
||||
@import "ui/text-area";
|
||||
@import "ui/checkbox";
|
||||
@import "ui/errors";
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
--text-secondary-color: #8A9499;
|
||||
--background-color: $white;
|
||||
|
||||
--button-height: 28px;
|
||||
|
||||
--button-background: $blue;
|
||||
--button-hover-background: lighten($blue, 5%);
|
||||
--button-text-color: $white;
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
#add-existing-repository {
|
||||
.file-picker {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
input {
|
||||
flex-grow: 2;
|
||||
margin-right: var(--spacing);
|
||||
}
|
||||
}
|
||||
|
||||
.add-repo-form {
|
||||
margin-bottom: var(--spacing);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,4 @@
|
|||
button,
|
||||
input[type='submit'],
|
||||
.button {
|
||||
background-color: var(--button-background);
|
||||
color: var(--button-text-color);
|
||||
|
||||
.button-component {
|
||||
// Chrome on Windows ignores the body element
|
||||
// font-family and uses Arial so we redefine
|
||||
// it here
|
||||
|
@ -13,9 +8,13 @@ input[type='submit'],
|
|||
padding: var(--spacing-half) var(--spacing);
|
||||
|
||||
border: none;
|
||||
height: var(--button-height);
|
||||
|
||||
color: var(--secondary-button-text-color);
|
||||
background-color: var(--secondary-button-background);
|
||||
|
||||
&:not(:disabled):hover {
|
||||
background-color: var(--button-hover-background);
|
||||
background-color: var(--secondary-button-hover-background);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
|
@ -26,7 +25,16 @@ input[type='submit'],
|
|||
&:disabled { opacity: 0.3; }
|
||||
}
|
||||
|
||||
a.link-button-component {
|
||||
.button-component[type='submit'] {
|
||||
background-color: var(--button-background);
|
||||
color: var(--button-text-color);
|
||||
|
||||
&:not(:disabled):hover {
|
||||
background-color: var(--button-hover-background);
|
||||
}
|
||||
}
|
||||
|
||||
.link-button-component {
|
||||
color: var(--link-button-color);
|
||||
text-decoration: none;
|
||||
|
||||
|
@ -34,12 +42,3 @@ a.link-button-component {
|
|||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.secondary-button {
|
||||
color: var(--secondary-button-text-color);
|
||||
background-color: var(--secondary-button-background);
|
||||
|
||||
&:not(:disabled):hover {
|
||||
background-color: var(--secondary-button-hover-background);
|
||||
}
|
||||
}
|
8
app/styles/ui/_checkbox.scss
Normal file
8
app/styles/ui/_checkbox.scss
Normal file
|
@ -0,0 +1,8 @@
|
|||
.checkbox-component {
|
||||
&:focus {
|
||||
// Don't know why but on Windows this is necessary to
|
||||
// not have a 1px gap around the checkbox on the right
|
||||
// hand side. I'm guessing it's the same on mac?
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
#create-repository {
|
||||
.file-picker {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
8
app/styles/ui/_errors.scss
Normal file
8
app/styles/ui/_errors.scss
Normal file
|
@ -0,0 +1,8 @@
|
|||
.errors-component {
|
||||
background: var(--form-error-background);
|
||||
border: 1px solid var(--form-error-border-color);
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--error-color);
|
||||
font-size: var(--font-size-sm);
|
||||
padding: var(--spacing-half);
|
||||
}
|
17
app/styles/ui/_form.scss
Normal file
17
app/styles/ui/_form.scss
Normal file
|
@ -0,0 +1,17 @@
|
|||
.form-component {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
margin: var(--spacing-half) 0;
|
||||
|
||||
& > *:not(:last-child) {
|
||||
margin-bottom: var(--spacing);
|
||||
}
|
||||
|
||||
hr {
|
||||
width: 100%;
|
||||
border: none;
|
||||
height: 1px;
|
||||
border-bottom: var(--base-border);
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
input[type='text'],
|
||||
input[type='date'],
|
||||
input[type='email'],
|
||||
input[type='range'],
|
||||
input[type='search'],
|
||||
input[type='password'],
|
||||
textarea,
|
||||
.text-field {
|
||||
border: 1px solid var(--box-border-color);
|
||||
border-radius: var(--border-radius);
|
||||
color: currentColor;
|
||||
font-size: var(--font-size);
|
||||
font-family: var(--font-family-sans-serif);
|
||||
padding: var(--spacing-half);
|
||||
|
||||
resize: none;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--focus-color);
|
||||
}
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
&:focus {
|
||||
// Don't know why but on Windows this is necessary to
|
||||
// not have a 1px gap around the checkbox on the right
|
||||
// hand side. I'm guessing it's the same on mac?
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.field-group {
|
||||
margin: var(--spacing-half) 0;
|
||||
|
||||
// Setting the position to relative allows us to
|
||||
// absolutely position things based on `.field-group` box boundaries.
|
||||
// (See the `.forgot-password-link` styles in _welcome.scss for an example)
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-errors {
|
||||
background: var(--form-error-background);
|
||||
border: 1px solid var(--form-error-border-color);
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--error-color);
|
||||
font-size: var(--font-size-sm);
|
||||
padding: var(--spacing-half);
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
#publish-repository {
|
||||
|
||||
}
|
12
app/styles/ui/_row.scss
Normal file
12
app/styles/ui/_row.scss
Normal file
|
@ -0,0 +1,12 @@
|
|||
.row-component {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
& > *:not(:last-child) {
|
||||
margin-right: var(--spacing);
|
||||
}
|
||||
|
||||
.button-component {
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
8
app/styles/ui/_select.scss
Normal file
8
app/styles/ui/_select.scss
Normal file
|
@ -0,0 +1,8 @@
|
|||
.select-component {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
select {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
19
app/styles/ui/_text-area.scss
Normal file
19
app/styles/ui/_text-area.scss
Normal file
|
@ -0,0 +1,19 @@
|
|||
.text-area-component {
|
||||
textarea {
|
||||
border: 1px solid var(--box-border-color);
|
||||
border-radius: var(--border-radius);
|
||||
color: currentColor;
|
||||
font-size: var(--font-size);
|
||||
font-family: var(--font-family-sans-serif);
|
||||
padding: var(--spacing-half);
|
||||
|
||||
resize: none;
|
||||
|
||||
min-height: 100px;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--focus-color);
|
||||
}
|
||||
}
|
||||
}
|
26
app/styles/ui/_text-box.scss
Normal file
26
app/styles/ui/_text-box.scss
Normal file
|
@ -0,0 +1,26 @@
|
|||
.text-box-component {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
|
||||
input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
|
||||
border: 1px solid var(--box-border-color);
|
||||
border-radius: var(--border-radius);
|
||||
color: currentColor;
|
||||
font-size: var(--font-size);
|
||||
font-family: var(--font-family-sans-serif);
|
||||
padding: var(--spacing-half);
|
||||
margin-right: var(--spacing);
|
||||
|
||||
resize: none;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--focus-color);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -91,6 +91,10 @@
|
|||
width: 200px;
|
||||
}
|
||||
|
||||
.password-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.forgot-password-link {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
|
Loading…
Reference in a new issue