-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Better modal #2554
Better modal #2554
Changes from 74 commits
4d384a9
bbf0bfa
6931880
79e88d6
5c73711
828d0dc
7a0c18c
600b3f4
dc6f6e8
bea32af
6c79b77
e9e2c42
a86360d
3574bbb
17740bf
bc07e83
64bc474
36b816e
ea110a2
c4d6648
0cf9cb9
1c59fca
582af6b
61ddc2f
4a3719d
687a05e
c0b0a59
c0a9952
3412751
ef23fa3
8e83253
7b8a88e
33fe6c1
f8224ed
0eb7e75
684fc4d
9f54e97
d461018
fdc6409
dfcb028
51faabd
003dbf4
7c930bb
64fbbf6
ad51de1
470c77a
313de3d
c663c05
c5a3c52
1cdcf54
bf06546
fafdb7b
98ed5ab
32b420b
038915b
040accd
959447b
d5cd20c
d911d6a
38f46e1
6891491
0da999e
14a3961
47c22c8
c955455
0c6e825
b47cc39
ca6ab34
d7629c8
0a3c5c2
7893d26
c7b51ac
c660dc1
d4454bf
ea53667
90efb7b
4617564
9cc1d9c
ff37c71
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import React from 'react' | ||
import PropTypes from 'prop-types' | ||
import styled from 'styled-components' | ||
|
||
const BuilderOuterContainer = styled.div` | ||
margin-top: 10px; | ||
margin-bottom: 10px; | ||
` | ||
|
||
// The inner container is inline-block so that its width matches its columns. | ||
const BuilderInnerContainer = styled.div` | ||
display: inline-block; | ||
padding: 11px 14px 10px; | ||
border-radius: 4px; | ||
background: #eef; | ||
` | ||
|
||
const BuilderContainer = ({ children }) => ( | ||
<BuilderOuterContainer> | ||
<BuilderInnerContainer>{children}</BuilderInnerContainer> | ||
</BuilderOuterContainer> | ||
) | ||
BuilderContainer.propTypes = { | ||
children: PropTypes.oneOfType([ | ||
PropTypes.arrayOf(PropTypes.node), | ||
PropTypes.node, | ||
]), | ||
} | ||
|
||
const labelFont = ` | ||
font-family: system-ui; | ||
font-size: 11px; | ||
text-transform: lowercase; | ||
` | ||
|
||
const BuilderLabel = styled.label` | ||
${labelFont} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
` | ||
|
||
const BuilderCaption = styled.span` | ||
${labelFont} | ||
color: #999; | ||
` | ||
|
||
export { BuilderContainer, BuilderLabel, BuilderCaption } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import React from 'react' | ||
import PropTypes from 'prop-types' | ||
import posed from 'react-pose' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks really cool! I haven't done much frontend webdev work in a while (most of the things I work on at work are more backend-related these days), but this library looks really good. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was impressed with how well it worked out of the box! Though I feel like I hit a moderate hill after an initially easy learning curve. I would definitely use it again. |
||
import styled from 'styled-components' | ||
|
||
const ContentAnchor = styled.span` | ||
position: relative; | ||
display: inline-block; | ||
` | ||
|
||
// 100vw allows providing styled content which is wider than its container. | ||
const ContentContainer = styled.span` | ||
width: 100vw; | ||
position: absolute; | ||
left: 50%; | ||
transform: translateX(-50%); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't seen this centering hack in a while 😛 Does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hehe. I'm still learning layout. I fiddled with it for a long time and I'd love to find a better way. 😝 This is what it looks like now. It glides up, centered over the button as intended. Normally it would then disappear but I turned that off for the screenshot. If I remove Btw this is all live in the review app. Click one of the examples with colons, fill in the boxes, and click the button, and you can see what the animation is supposed to do. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From a design / UX perspective, I think it would be nicer if didn't have the actual text was copied but just a simple 'copied' as the content of the indicator. I've seen that on other sites used a lot and think that would be a better solution since no one would be able to read the text in that short period of time. On the other hand, it would be nice to see the text before it gets copied to the clipboard. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm fine with "copied." We could experiment with showing the actual text. Seeing unfinished markup before you start filling the form looks like a mistake and I think is confusing. We could show an empty rectangle with a message "your markup will appear here" or "some parameters missing," similarly to the way the preview badge is handled. During editing, I think the changing text will be distracting for some and comforting for others. It's about adding something new, so let's revisit it. If we decide on a design process in #1899 that will put us on good ground for the future work. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree. I'll write it down and we leave like this here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I updated the indicator to show Copied. |
||
will-change: opacity, top; | ||
pointer-events: none; | ||
` | ||
|
||
const PosedContentContainer = posed(ContentContainer)({ | ||
hidden: { opacity: 0, transition: { duration: 100 } }, | ||
effectStart: { top: '-10px', opacity: 1.0, transition: { duration: 0 } }, | ||
effectEnd: { top: '-75px', opacity: 0.5 }, | ||
}) | ||
|
||
// When `trigger()` is called, render copied content that floats up, then | ||
// disappears. | ||
export default class CopiedContentIndicator extends React.Component { | ||
state = { | ||
pose: 'hidden', | ||
} | ||
|
||
trigger() { | ||
this.setState({ pose: 'effectStart' }) | ||
} | ||
|
||
handlePoseComplete = () => { | ||
const { pose } = this.state | ||
if (pose === 'effectStart') { | ||
this.setState({ pose: 'effectEnd' }) | ||
} else { | ||
this.setState({ pose: 'hidden' }) | ||
} | ||
} | ||
|
||
render() { | ||
const { pose } = this.state | ||
return ( | ||
<ContentAnchor> | ||
<PosedContentContainer | ||
pose={pose} | ||
onPoseComplete={this.handlePoseComplete} | ||
> | ||
{this.props.copiedContent} | ||
</PosedContentContainer> | ||
{this.props.children} | ||
</ContentAnchor> | ||
) | ||
} | ||
} | ||
CopiedContentIndicator.propTypes = { | ||
copiedContent: PropTypes.oneOfType([ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did we end up with any consensus about whether to use static typing (like Flow or TypeScript) rather than runtime type checking? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We decided not to do it for the server because it raises the barrier to contributing, especially among non-JS devs, and doesn't create enough payoff to compensate for that. I'd like to do it for the frontend, because I think would actually lower the barrier. The types make the code easier to change, and when it comes to the frontend, I'd rather try to appeal to seasoned React devs than non-frontend devs. |
||
PropTypes.arrayOf(PropTypes.node), | ||
PropTypes.node, | ||
]), | ||
children: PropTypes.oneOfType([ | ||
PropTypes.arrayOf(PropTypes.node), | ||
PropTypes.node, | ||
]), | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,11 +2,15 @@ import React from 'react' | |
import PropTypes from 'prop-types' | ||
import Modal from 'react-modal' | ||
import styled from 'styled-components' | ||
import { badgeUrlFromPath, badgeUrlFromPattern } from '../../lib/make-badge-url' | ||
import generateAllMarkup from '../lib/generate-image-markup' | ||
import { advertisedStyles } from '../../supported-features.json' | ||
import { Snippet } from './snippet' | ||
import { BaseFont, H3, Badge, BlockInput } from './common' | ||
import { | ||
badgeUrlFromPath, | ||
badgeUrlFromPattern, | ||
} from '../../../lib/make-badge-url' | ||
import { advertisedStyles } from '../../../supported-features.json' | ||
import generateAllMarkup from '../../lib/generate-image-markup' | ||
import { Snippet } from '../snippet' | ||
import { BaseFont, H3, Badge, BlockInput } from '../common' | ||
import MarkupModalContent from './markup-modal-content' | ||
|
||
const ContentContainer = styled(BaseFont)` | ||
text-align: center; | ||
|
@@ -176,6 +180,8 @@ export default class MarkupModal extends React.Component { | |
const { onRequestClose, example: { title } = {} } = this.props | ||
const { link, badgeUrl, exampleUrl, style } = this.state | ||
|
||
const hasModernExample = isOpen && this.props.example.example.pattern | ||
|
||
const common = { | ||
autoComplete: 'off', | ||
autoCorrect: 'off', | ||
|
@@ -190,63 +196,69 @@ export default class MarkupModal extends React.Component { | |
contentLabel="Example Modal" | ||
ariaHideApp={false} | ||
> | ||
<ContentContainer> | ||
<form action=""> | ||
<H3>{title}</H3> | ||
{isOpen && this.renderLivePreview()} | ||
<p> | ||
<label> | ||
Link | ||
<BlockInput | ||
type="url" | ||
value={link} | ||
onChange={event => { | ||
this.setState({ link: event.target.value }) | ||
}} | ||
{...common} | ||
/> | ||
</label> | ||
</p> | ||
<p> | ||
<label> | ||
Path | ||
<BlockInput | ||
type="url" | ||
value={badgeUrl} | ||
onChange={event => { | ||
this.setState({ badgeUrl: event.target.value }) | ||
}} | ||
{...common} | ||
/> | ||
</label> | ||
</p> | ||
{exampleUrl && ( | ||
{hasModernExample ? ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nothing has changed here besides adding the conditional. |
||
<ContentContainer> | ||
<MarkupModalContent {...this.props} /> | ||
</ContentContainer> | ||
) : ( | ||
<ContentContainer> | ||
<form action=""> | ||
<H3>{title}</H3> | ||
{isOpen && this.renderLivePreview()} | ||
<p> | ||
<label> | ||
Link | ||
<BlockInput | ||
type="url" | ||
value={link} | ||
onChange={event => { | ||
this.setState({ link: event.target.value }) | ||
}} | ||
{...common} | ||
/> | ||
</label> | ||
</p> | ||
<p> | ||
<label> | ||
Path | ||
<BlockInput | ||
type="url" | ||
value={badgeUrl} | ||
onChange={event => { | ||
this.setState({ badgeUrl: event.target.value }) | ||
}} | ||
{...common} | ||
/> | ||
</label> | ||
</p> | ||
{exampleUrl && ( | ||
<p> | ||
Example | ||
<Snippet fontSize="10pt" snippet={exampleUrl} /> | ||
</p> | ||
)} | ||
<p> | ||
Example | ||
<Snippet fontSize="10pt" snippet={exampleUrl} /> | ||
<label> | ||
Style | ||
<select | ||
value={style} | ||
onChange={event => { | ||
this.setState({ style: event.target.value }) | ||
}} | ||
> | ||
{advertisedStyles.map(style => ( | ||
<option key={style} value={style}> | ||
{style} | ||
</option> | ||
))} | ||
</select> | ||
</label> | ||
</p> | ||
)} | ||
<p> | ||
<label> | ||
Style | ||
<select | ||
value={style} | ||
onChange={event => { | ||
this.setState({ style: event.target.value }) | ||
}} | ||
> | ||
{advertisedStyles.map(style => ( | ||
<option key={style} value={style}> | ||
{style} | ||
</option> | ||
))} | ||
</select> | ||
</label> | ||
</p> | ||
{isOpen && this.renderMarkup()} | ||
{isOpen && this.renderDocumentation()} | ||
</form> | ||
</ContentContainer> | ||
{isOpen && this.renderMarkup()} | ||
{isOpen && this.renderDocumentation()} | ||
</form> | ||
</ContentContainer> | ||
)} | ||
</Modal> | ||
) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Maybe do
Object.freeze
on this to ensure it's not mutated anywhere?If the repo were using strong typing, it'd be fine (eg. in Flow you can annotate an object as
$ReadOnly
and then Flow will throw a type error if you try to mutate it.