chore(launchpad): launchpad UI tweaks (#18369)

Co-authored-by: Jessica Sachs <jess@jessicasachs.io>
This commit is contained in:
Mark Noonan
2021-10-06 11:31:42 -04:00
committed by GitHub
parent 98f2604abd
commit dfdc537aa4
22 changed files with 155 additions and 167 deletions

View File

@@ -1,13 +1,12 @@
<template>
<button
style="width: fit-content"
class="flex items-center border rounded-sm gap-8px focus:border-indigo-600 focus:outline-transparent"
class="flex items-center border rounded gap-8px hocus-default"
:class="classes"
>
<span
v-if="prefixIcon || $slots.prefix"
:class="iconClasses"
class="justify-self-start"
class="justify-self-start flex items-center"
>
<slot name="prefix">
<Icon
@@ -21,8 +20,7 @@
</span>
<span
v-if="suffixIcon || $slots.suffix"
:class="iconClasses"
class="justify-self-end"
class="justify-self-start flex items-center"
>
<slot name="suffix">
<Icon
@@ -52,29 +50,23 @@ import { computed, useAttrs } from 'vue'
import type { ButtonHTMLAttributes, FunctionalComponent, SVGAttributes } from 'vue'
const VariantClassesTable = {
primary: 'border-indigo-600 bg-indigo-600 text-white',
outline: 'border-gray-200 text-indigo-600 bg-white',
primary: 'border-indigo-500 bg-indigo-600 text-white',
outline: 'border-gray-100 text-indigo-600',
link: 'border-transparent text-indigo-600',
text: 'border-0',
}
const SizeClassesTable = {
sm: 'px-1 py-1 text-xs',
md: 'px-2 py-1 text-sm',
lg: 'px-4 py-2 text-sm',
xl: 'px-6 py-3 text-lg',
}
const IconClassesTable = {
md: 'min-h-1.25em min-w-1.25em max-h-1.25em max-w-1.25em',
lg: 'min-h-2em min-w-2em max-h-2em max-w-2em',
xl: 'min-h-2.5em min-w-2.5em max-w-2.5em max-h-2.5em ',
sm: 'px-6px py-2px text-14px',
md: 'px-12px py-6px text-14px',
lg: 'px-16px py-8px',
'lg-wide': 'px-32px py-8px',
}
const props = defineProps<{
prefixIcon?: FunctionalComponent<SVGAttributes>
suffixIcon?: FunctionalComponent<SVGAttributes>
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
size?: 'sm' | 'md' | 'lg' | 'lg-wide'
variant?: 'primary' | 'outline' | 'link' | 'text'
prefixIconClass?: string
suffixIconClass?: string
@@ -85,8 +77,6 @@ const attrs = useAttrs() as ButtonHTMLAttributes
const variantClasses = VariantClassesTable[props.variant || 'primary']
const sizeClasses = SizeClassesTable[props.size || 'md']
const iconClasses = ['flex', 'items-center', IconClassesTable[props.size || 'md']]
const classes = computed(() => {
return [
variantClasses,

View File

@@ -4,10 +4,12 @@
<span
v-show="showCopied"
class="mx-3"
role="status"
>{{ t('clipboard.copied') }}</span>
</transition>
<button
class="bg-gray-50 px-3 py-1 rounded text-indigo-600"
tabindex="1"
class="bg-gray-50 text-14px px-2 py-1 rounded text-indigo-600 border-1 border-transparent hocus-default"
@click="copyToClipboard"
>
{{ t('clipboard.copy') }}
@@ -15,6 +17,7 @@
</div>
<textarea
ref="textElement"
tabindex="-1"
:value="text"
class="absolute -top-96"
/>

View File

@@ -7,11 +7,13 @@ describe('<Switch />', () => {
cy.mount(() => (
<div class="p-6">
<label for="test-switch">Switch</label>
<Switch
// @ts-ignore
value={valueRef.value}
// @ts-ignore
onUpdate={(newVal) => (valueRef.value = newVal)}
name="test-switch"
/>
</div>
))

View File

@@ -1,37 +1,52 @@
<template>
<button
class="rounded-md h-3 w-6 relative focus:outline-transparent"
:class="value ? 'bg-green-600' : 'bg-gray-600'"
:id="name"
class="rounded-50px relative hocus-default border-transparent border-1"
:class="[value ? 'bg-jade-400' : 'bg-gray-300', sizeClasses[size].container]"
role="switch"
:aria-checked="value"
@click="$emit('update', !value)"
>
<span
class="absolute block toggle border border-1 border-gray-300 rounded-md bg-white"
:class="value ? 'toggle-on' : ''"
class="block toggle transform rounded-50px bg-white transition-transform duration-200 ease-out"
:class="[{ [sizeClasses[size].translate]: value }, sizeClasses[size].indicator]"
/>
</button>
</template>
<script lang="ts" setup>
defineProps({
value: {
type: Boolean,
default: false,
},
withDefaults(defineProps<{
value: boolean
size?: 'sm' | 'md' | 'lg' | 'xl'
name: string // required for an id so that an external <label> can be associated with the switch
}>(), {
value: false,
size: 'lg',
})
const sizeClasses = {
'sm': {
container: 'w-16px h-10px',
indicator: 'w-6px h-6px ml-2px',
translate: 'translate-x-6px',
},
'md': {
container: 'w-24px h-12px',
indicator: 'w-8px h-8px ml-2px',
translate: 'translate-x-12px',
},
'lg': {
container: 'w-32px h-16px',
indicator: 'w-12px h-12px ml-2px',
translate: 'translate-x-14px',
},
'xl': {
container: 'w-48px h-24px',
indicator: 'w-16px h-16px ml-4px',
translate: 'translate-x-24px',
},
}
defineEmits(['update'])
</script>
<style scoped>
.toggle {
left: 1px;
top: 1px;
height: 10px;
width: 10px;
transition: left 0.2s;
}
.toggle-on {
left: 13px;
}
</style>

View File

@@ -58,6 +58,9 @@
},
"openBrowser": {
"launch": "Launch"
},
"configFile": {
"createManually": "Create file manually"
}
},
"globalPage": {

View File

@@ -26,6 +26,10 @@ export default defineConfig({
InteractionVariants,
IconDuotoneColorsPlugin,
],
shortcuts: {
'focus-default': 'focus:border focus:border-indigo-300 focus:ring-2 focus:ring-indigo-100 focus:outline-transparent transition transition-colors duration-100 disabled:hover:ring-0 disabled:hover:border-0',
'hocus-default': 'hocus:border hover:border-indigo-100 focus:border-indigo-300 hocus:ring-2 hocus:ring-indigo-100 hocus:outline-transparent transition transition-colors duration-100 disabled:ring-0 disabled:border-0',
},
extract: {
// accepts globs and file paths relative to project root
include: ['index.html', '**/*.{vue,html,tsx}', '../frontend-shared/**/*.{vue,html,tsx}'],

View File

@@ -23,7 +23,7 @@
"
:class="disabledClass
+ (isOpen ? ' border-indigo-600' : ' border-gray-200')
+ (props.disabled ? ' bg-gray-300 text-gray-600' : '')"
+ (props.disabled ? ' bg-gray-100 text-gray-800' : '')"
:disabled="props.disabled"
@click="
if (!props.disabled) {

View File

@@ -2,32 +2,23 @@
<div class="text-left relative">
<label
class="text-gray-800 text-sm my-3 block"
:class="disabledClass"
>{{
props.name
}}</label>
:class="{ 'opacity-50': disabled }"
>
{{
name
}}
</label>
<button
v-click-outside="() => (isOpen = false)"
class="
h-10
text-left
flex
justify-between
items-center
border-1
px-2
py-1
rounded
w-full
focus:border-indigo-600 focus:outline-transparent
"
class="h-10 text-left flex justify-between items-center border-1 px-2 py-1 rounded w-full focus:border-indigo-600 focus:outline-transparent"
data-cy="select-framework"
:class="disabledClass
+ (isOpen ? ' border-indigo-600' : ' border-gray-200')
+ (props.disabled ? ' bg-gray-300 text-gray-600' : '')"
:disabled="props.disabled"
:class="
[isOpen ? ' border-indigo-600' : ' border-gray-100',
{ 'bg-gray-100 text-gray-800 opacity-50': disabled }]
"
:disabled="disabled"
@click="
if (!props.disabled) {
if (!disabled) {
isOpen = !isOpen;
}
"
@@ -37,35 +28,22 @@
:src="FrameworkBundlerLogos[selectedOptionObject.type]"
class="w-5 h-5 mr-3"
>
<span>
{{ selectedOptionObject.name }}
</span>
<span>{{ selectedOptionObject.name }}</span>
</template>
<span
v-else
class="text-gray-400"
>
{{ props.placeholder }}
</span>
>{{ placeholder }}</span>
<span class="flex-grow" />
<i-fa-angle-down />
</button>
<ul
v-if="isOpen"
class="
w-full
absolute
bg-white
border-1 border-indigo-600 border-t-1 border-t-gray-100
rounded-b
flex flex-col
gap-0
z-10
"
class="w-full absolute bg-white border-1 border-indigo-600 border-t-1 border-t-gray-100 rounded-b flex flex-col gap-0 z-10"
style="margin-top: -3px"
>
<li
v-for="opt in props.options"
v-for="opt in options"
:key="opt.id"
focus="1"
class="cursor-pointer flex items-center py-1 px-2 hover:bg-gray-10"
@@ -75,9 +53,7 @@
:src="FrameworkBundlerLogos[opt.type]"
class="w-5 h-5 mr-3"
>
<span>
{{ opt.name }}
</span>
<span>{{ opt.name }}</span>
</li>
</ul>
</div>
@@ -121,5 +97,4 @@ const selectOption = (opt: FrontendFrameworkEnum) => {
emit('select', opt)
}
const disabledClass = computed(() => props.disabled ? 'opacity-50' : undefined)
</script>

View File

@@ -1,4 +1,7 @@
import ButtonBar from './ButtonBar.vue'
import { defaultMessages } from '@cy/i18n'
const { next: nextLabel, back: backLabel } = defaultMessages.setupPage.step
describe('<ButtonBar />', () => {
let nextFn: ReturnType<typeof cy.stub>
@@ -10,12 +13,12 @@ describe('<ButtonBar />', () => {
})
it('playground', () => {
cy.mount(() => <ButtonBar next="Next Step" back="Back" nextFn={nextFn} backFn={backFn} />)
cy.mount(() => <ButtonBar next={nextLabel} back={backLabel} nextFn={nextFn} backFn={backFn} />)
})
it('should trigger the next function', () => {
cy.mount(() => <ButtonBar next="Next Step" back="Back" nextFn={nextFn} backFn={backFn} canNavigateForward={true} />)
cy.contains('Next Step')
cy.mount(() => <ButtonBar next={nextLabel} back={backLabel} nextFn={nextFn} backFn={backFn} canNavigateForward={true} />)
cy.contains(nextLabel)
.click()
.then(() => {
expect(nextFn).to.have.been.calledOnce
@@ -23,8 +26,8 @@ describe('<ButtonBar />', () => {
})
it('should trigger the back function', () => {
cy.mount(() => <ButtonBar next="Next Step" back="Back" nextFn={nextFn} backFn={backFn} />)
cy.contains('Back')
cy.mount(() => <ButtonBar next={nextLabel} back={backLabel} nextFn={nextFn} backFn={backFn} />)
cy.contains(backLabel)
.click()
.then(() => {
expect(backFn).to.have.been.calledOnce
@@ -35,10 +38,10 @@ describe('<ButtonBar />', () => {
const altFunction = cy.spy()
cy.mount(() => (
<ButtonBar next="Next Step" back="Back" nextFn={nextFn} backFn={backFn} altFn={altFunction} alt="Install manually" />
<ButtonBar next={nextLabel} back={backLabel} nextFn={nextFn} backFn={backFn} altFn={altFunction} alt="Install manually" />
))
cy.contains('Install manually')
cy.findAllByLabelText('Install manually')
.click()
.then(() => {
expect(altFunction).to.have.been.calledOnce

View File

@@ -1,14 +1,16 @@
<template>
<div class="px-5 py-5 flex gap-3 bg-gray-50 border-t border-t-1 border-t-gray-200 rounded-b">
<div class="px-5 py-5 flex gap-3 bg-gray-50 border-t border-t-1 border-t-gray-100 rounded-b">
<slot>
<Button
v-if="showNext"
size="lg"
:disabled="!canNavigateForward"
@click="nextFn"
>
{{ next }}
</Button>
<Button
size="lg"
variant="outline"
@click="backFn"
>
@@ -20,10 +22,13 @@
class="flex items-center px-3"
>
<label
for="altFn"
class="text-gray-500 px-3"
@click="handleAlt"
>{{ alt }}</label>
<Switch
size="lg"
name="altFn"
:value="altValue"
@update="handleAlt"
/>

View File

@@ -1,5 +1,6 @@
// TODO: Why is this failing on CI?
import { defaultMessages } from '@cy/i18n'
import {
ConfigFileFragmentDoc,
} from '../generated/graphql-test'
@@ -27,8 +28,8 @@ describe('<ConfigFile />', () => {
})
it('should display a copy button when in manual mode', () => {
cy.contains('Copy').should('not.exist')
cy.contains('Create file manually').click()
cy.contains('Copy').should('exist')
cy.contains(defaultMessages.clipboard.copy).should('not.exist')
cy.findByLabelText(defaultMessages.setupPage.configFile.createManually).click()
cy.contains(defaultMessages.clipboard.copy).should('exist')
})
})

View File

@@ -1,7 +1,7 @@
<template>
<WizardLayout
:next="nextButtonName"
alt="Create file manually"
:alt="t('setupPage.configFile.createManually')"
:alt-fn="altFn"
:next-fn="createConfig"
:can-navigate-forward="props.gql.wizard.canNavigateForward"
@@ -9,18 +9,25 @@
<nav
class="
rounded-t
text-left text-gray-500
text-left
text-gray-500
px-5
bg-gray-50
flex
gap-2
border-b-1 border-gray-200
"
border-b-1
border-gray-200"
>
<button
v-for="lang of languages"
:key="lang.id"
class="p-4 w-28 relative focus:outline-transparent"
class="
p-4
w-28
relative
border-transparent
border-1
focus-default"
:class="language === lang.id ? 'text-indigo-600 font-semibold' : ''"
@click="language = lang.id"
>
@@ -35,8 +42,7 @@
block
h-1
bg-indigo-500
rounded-t
"
rounded-t"
/>
</button>
</nav>
@@ -69,6 +75,9 @@ import CopyButton from '@cy/components/CopyButton.vue'
import { languages } from '../utils/configFile'
import { ConfigFileFragment, ConfigFile_AppCreateConfigFileDocument } from '../generated/graphql'
import { useMutation } from '@urql/vue'
import { useI18n } from '@cy/i18n'
const { t } = useI18n()
gql`
fragment ConfigFile on Query {

View File

@@ -1,5 +1,6 @@
import InstallDependencies from './InstallDependencies.vue'
import { InstallDependenciesFragmentDoc } from '../generated/graphql-test'
import { defaultMessages } from '@cy/i18n'
describe('<InstallDependencies />', () => {
beforeEach(() => {
@@ -18,19 +19,9 @@ describe('<InstallDependencies />', () => {
cy.contains('@cypress/webpack-dev-server').should('exist')
})
xit('should infinitely toggle manual', () => {
cy.contains('@cypress/react').should('exist')
cy.contains('manually').click()
cy.contains('yarn add').should('exist')
cy.contains('Install manually').click()
cy.contains('@cypress/react').should('exist')
cy.contains('manually').click()
cy.contains('yarn add').should('exist')
cy.contains('Install manually').click()
cy.contains('@cypress/react').should('exist')
})
it('should allow to toggle to manual', () => {
cy.contains('manually').click()
it('shows expected actions', () => {
cy.contains('button', defaultMessages.clipboard.copy).should('be.visible')
cy.contains('button', defaultMessages.setupPage.install.confirmManualInstall).should('be.visible')
cy.contains('button', defaultMessages.setupPage.step.back).should('be.visible')
})
})

View File

@@ -1,16 +1,9 @@
<template>
<WizardLayout
:next="nextButtonName"
alt="Install manually"
:alt-fn="altFn"
:next=" t('setupPage.install.confirmManualInstall')"
:can-navigate-forward="props.gql.canNavigateForward"
>
<PackagesList
v-if="!isManualInstall"
:gql="props.gql"
/>
<ManualInstall
v-else
:gql="props.gql"
/>
</WizardLayout>
@@ -19,7 +12,6 @@
<script lang="ts" setup>
import { computed, ref } from 'vue'
import WizardLayout from './WizardLayout.vue'
import PackagesList from './PackagesList.vue'
import ManualInstall from './ManualInstall.vue'
import { gql } from '@urql/core'
import type { InstallDependenciesFragment } from '../generated/graphql'
@@ -33,20 +25,10 @@ fragment InstallDependencies on Wizard {
}
`
const isManualInstall = ref(false)
const props = defineProps<{
gql: InstallDependenciesFragment
}>()
const { t } = useI18n()
const nextButtonName = computed(() => {
return isManualInstall.value ?
t('setupPage.install.confirmManualInstall') :
t('setupPage.install.startButton')
})
const altFn = (val: boolean) => {
isManualInstall.value = val
}
</script>

View File

@@ -7,7 +7,7 @@
items-center
px-5
gap-2
border-b border-gray-200
border-b border-gray-100
rounded-t-md
"
>

View File

@@ -48,15 +48,17 @@
<Button
v-if="launchText"
type="submit"
class="inline px-6 py-2 mr-2"
class="inline mr-2"
:suffix-icon="openInNew"
data-testid="launch-button"
size="lg-wide"
>
{{ launchText }}
</Button>
<Button
type="button"
class="inline px-6 py-2 ml-2"
size="lg"
class="inline ml-2"
variant="outline"
@click="$emit('navigated-back')"
>

View File

@@ -1,7 +1,7 @@
<template>
<div
class="relative flex flex-col w-full border border-gray-200 rounded p-30px text text-body-gray-500"
:role="role"
class="relative flex flex-col w-full border border-gray-200 rounded p-30px text text-body-gray-500 hocus-default"
tabindex="0"
>
<div class="border h-152px">
<!-- temp wrapper for icon -->
@@ -36,7 +36,6 @@ const props = defineProps<{
id: TestingTypeEnum
title: string
description: string
role: string
}>()
const icon = TestingTypeIcons[props.id]

View File

@@ -24,10 +24,11 @@ describe('TestingTypeCards', () => {
},
}).then(() => {
// CT has been configured so we should show "launch"
cy.get('[role="launch-component-testing"]')
cy.contains('LAUNCH').should('be.visible')
// E2E has NOT been configured so we should show "setup"
cy.get('[role="setup-e2e-testing"]')
// TODO - pull this from i18n when wizard text is moved into i18n
cy.contains('Click here to configure end-to-end testing with Cypress.').should('be.visible')
})
})
})

View File

@@ -5,8 +5,10 @@
:id="ct.type"
:title="ct.title"
:description="firstTimeCT ? ct.description : 'LAUNCH'"
:role="firstTimeCT ? 'setup-component-testing' : 'launch-component-testing'"
role="button"
@click="ctNextStep"
@keyup.enter="ctNextStep"
@keyup.space="ctNextStep"
/>
<TestingTypeCard
@@ -14,8 +16,10 @@
:id="e2e.type"
:title="e2e.title"
:description="firstTimeE2E ? e2e.description : 'LAUNCH'"
:role="firstTimeE2E ? 'setup-e2e-testing' : 'launch-e2e-testing'"
role="button"
@click="e2eNextStep"
@keyup.enter="e2eNextStep"
@keyup.space="e2eNextStep"
/>
</div>
</template>

View File

@@ -1,5 +1,9 @@
<template>
<div :class="noContainer ? wrapperClasses['no-container'] : wrapperClasses.default">
<div
:class="{
'max-w-3xl mx-auto border-1 border-gray-100 rounded m-10 flex flex-col': !noContainer
}"
>
<div class="flex-grow">
<slot :backFn="backFn" />
</div>
@@ -83,9 +87,4 @@ function backFn () {
navigate.executeMutation({ direction: 'back' })
}
const wrapperClasses = {
'default': 'max-w-3xl min-h-70 mx-auto border-1 border-gray-200 rounded m-10 flex flex-col',
'no-container': '',
}
</script>

View File

@@ -1,9 +1,9 @@
import LogoWebpack from '../images/logos/webpack.svg'
import LogoVite from '../images/logos/vite.svg'
import LogoNext from '../images/logos/nextjs.svg'
import LogoNuxt from '../images/logos/nuxt.svg'
import LogoVue from '../images/logos/vue.svg'
import LogoReact from '../images/logos/react.svg'
import LogoWebpack from '../images/logos/webpack.svg?url'
import LogoVite from '../images/logos/vite.svg?url'
import LogoNext from '../images/logos/nextjs.svg?url'
import LogoNuxt from '../images/logos/nuxt.svg?url'
import LogoVue from '../images/logos/vue.svg?url'
import LogoReact from '../images/logos/react.svg?url'
import componentLogo from '../images/testingTypes/component.svg'
import e2eLogo from '../images/testingTypes/e2e.svg'

View File

@@ -103,23 +103,23 @@ export const WIZARD_STEPS = [
},
{
type: 'initializePlugins',
title: 'Project Setup',
description: 'Confirm the front-end framework and bundler fused in your project.',
title: 'Initializing Config...',
description: 'Please wait while we load your project and find browsers installed on your system.',
},
{
type: 'selectFramework',
title: 'Initializing Config...',
description: 'We need to install the following packages in order for component testing to work.',
title: 'Project Setup',
description: 'Confirm the front-end framework and bundler used in your project.',
},
{
type: 'installDependencies',
title: 'Install Dev Dependencies',
description: 'Cypress will now create the following config file in the local directory for this project.',
description: 'Paste the command below into your terminal to install the required packages.',
},
{
type: 'createConfig',
title: 'Cypress.config',
description: 'Please wait while we load your project and find browsers installed on your system.',
description: 'Cypress will now create the following config file in the local directory for this project.',
},
{
type: 'setupComplete',