Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
767 changes: 736 additions & 31 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
"css-loader": "^6.7.1",
"eslint": "^8.20.0",
"html-webpack-plugin": "^5.5.0",
"node-sass": "^8.0.0",
"prettier": "^2.7.1",
"sass": "^1.89.2",
"sass-loader": "^13.2.0",
"style-loader": "^3.3.1",
"ts-loader": "^9.3.1",
Expand Down
24 changes: 20 additions & 4 deletions src/ui/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,37 @@
import styled, { css } from "styled-components"

export const ButtonBaseStyles = css`
height: 32px;
line-height: 30px;
// WCAG 2.2 AA requires minimum 44px touch target
min-height: 44px;
height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
max-width: 200px;
padding: 0 11px;

background-color: transparent;
border-radius: 6px;
border: 1px solid var(--figma-color-border-strong, #2c2c2c);

color: var(--figma-color-text, rgba(0, 0, 0, 0.9));
color: var(--figma-color-text, #0d0d0d); // Solid color for better contrast
font: inherit;
text-align: center;

cursor: default;
cursor: pointer;

&:hover {
opacity: 0.9;
}

&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
user-select: none;

// Smooth transitions for better UX
transition: opacity 0.2s ease, border-color 0.2s ease;
`

export const OutlineButton = styled.button`
Expand Down
58 changes: 46 additions & 12 deletions src/ui/components/Disclosure.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,38 @@ export interface DisclosureProps {
label: Preact.ComponentChildren
children: Preact.ComponentChildren
open?: boolean
id?: string
"aria-labelledby"?: string
}

export function Disclosure(props: DisclosureProps) {
const summaryId = React.useId()
const contentId = React.useId()

return (
<StyledDetails open={props.open}>
<summary>{props.label}</summary>
{props.children}
<StyledDetails open={props.open} id={props.id}>
<StyledSummary id={summaryId} aria-expanded={props.open}>
{props.label}
</StyledSummary>
<DisclosureContent
id={contentId}
aria-labelledby={props["aria-labelledby"] || summaryId}
>
{props.children}
</DisclosureContent>
</StyledDetails>
)
}
export default Disclosure

const StyledDetails = styled.details`
summary {
// Remove default styling for better control
&::-webkit-details-marker {
display: none;
}
`

const StyledSummary = styled.summary`
margin: 0 -16px 0 -1em;
padding: 8px 0;

Expand All @@ -32,24 +50,40 @@ const StyledDetails = styled.details`
display: inline;
}

&::marker {
&::before {
color: transparent;
transition: color 200ms ease;
transition: color 200ms ease, transform 200ms ease;
}

&:hover,
&:focus-visible {
text-decoration: underline;

&::marker {
color: var(--figma-color-icon-disabled, rgba(0, 0, 0, 0.3));
&::before {
color: var(--figma-color-icon-secondary, #666666);
}
}
}

&[open] {
summary::marker {
color: var(--figma-color-icon-disabled, rgba(0, 0, 0, 0.3));
}
// Add custom marker
&::before {
content: "▶";
display: inline-block;
margin-right: 8px;
color: transparent;
transition: color 200ms ease, transform 200ms ease;
}

&::-webkit-details-marker {
display: none;
}

&[aria-expanded="true"]::before {
transform: rotate(90deg);
color: var(--figma-color-icon-secondary, #666666);
}
`

const DisclosureContent = styled.div`
padding-top: 8px;
`
125 changes: 113 additions & 12 deletions src/ui/components/FileDropZone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,69 @@ import { classNames } from "utils/classNames"
// FileDropZone uses the same props as UploadLink and functions in largely the same way,
// but some props such as "accept" do not work.

export function FileDropZone(props: UploadLinkProps) {
interface FileDropZoneProps extends UploadLinkProps {
"aria-label"?: string
"aria-describedby"?: string
}

export function FileDropZone(props: FileDropZoneProps) {
const [isDragging, setIsDragging] = React.useState(false)
const [isFocused, setIsFocused] = React.useState(false)
const fileInputRef = React.useRef<HTMLInputElement>(null)

// Announce drag state changes to screen readers
React.useEffect(() => {
if (isDragging) {
const announcement = document.createElement("div")
announcement.setAttribute("role", "status")
announcement.setAttribute("aria-live", "polite")
announcement.className = "sr-only"
announcement.textContent = "Drop zone active. Release files to upload."
document.body.appendChild(announcement)

setTimeout(() => document.body.removeChild(announcement), 1000)
}
}, [isDragging])

return (
<DropZone
className={classNames(props.className, isDragging && "drag-over")}
style={props.style}
onDragStart={onDragStart}
onDragEnter={onDragEnter}
onDragOver={onDragOver}
onDragLeave={onDragLeave}
onDrop={onDrop}
>
{props.children}
</DropZone>
<>
<DropZone
className={classNames(
props.className,
isDragging && "drag-over",
isFocused && "focused"
)}
style={props.style}
onDragStart={onDragStart}
onDragEnter={onDragEnter}
onDragOver={onDragOver}
onDragLeave={onDragLeave}
onDrop={onDrop}
onClick={handleClick}
onKeyDown={handleKeyDown}
tabIndex={0}
role="button"
aria-label={props["aria-label"] || "Drop files here or click to browse"}
aria-describedby={props["aria-describedby"]}
aria-dropeffect={isDragging ? "copy" : "none"}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
>
{props.children}
<HiddenFileInput
ref={fileInputRef}
type="file"
multiple={props.multiple}
accept={props.accept}
onChange={handleFileChange}
tabIndex={-1}
aria-hidden="true"
/>
</DropZone>
<ScreenReaderOnly id="drop-zone-instructions">
Press Enter or Space to open file browser. Or drag and drop files onto this area.
</ScreenReaderOnly>
</>
)

function onDragStart(ev: DragEvent) {
Expand Down Expand Up @@ -51,13 +99,33 @@ export function FileDropZone(props: UploadLinkProps) {
setIsDragging(false)
if (ev.dataTransfer && ev.dataTransfer.files) props.onFileChosen(ev.dataTransfer.files)
}

function handleClick() {
fileInputRef.current?.click()
}

function handleKeyDown(ev: React.KeyboardEvent) {
if (ev.key === "Enter" || ev.key === " ") {
ev.preventDefault()
fileInputRef.current?.click()
}
}

function handleFileChange(ev: React.ChangeEvent<HTMLInputElement>) {
if (ev.target.files) {
props.onFileChosen(ev.target.files)
}
}
}
export default FileDropZone

const DropZone = styled.div`
border: 3px dashed var(--figma-color-border-disabled-strong, rgba(0, 0, 0, 0.3));
padding: 2em;
background-color: var(--figma-color-bg-secondary, #f5f5f5);
cursor: pointer;
position: relative;
transition: border-color 0.2s ease, background-color 0.2s ease;

* {
pointer-events: none;
Expand All @@ -67,4 +135,37 @@ const DropZone = styled.div`
border: 3px solid var(--figma-color-border-selected, #0d99ff);
background-color: var(--figma-color-bg-selected, #e5f4ff);
}

&.focused {
outline: 2px solid var(--figma-color-border-selected, #0d99ff);
outline-offset: 2px;
}

&:hover {
border-color: var(--figma-color-border-strong, #666666);
}
`

const HiddenFileInput = styled.input`
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
`

const ScreenReaderOnly = styled.span`
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
`
26 changes: 22 additions & 4 deletions src/ui/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,37 @@ export function Footer() {
<div>
© 2023 Microsoft
{" • "}
<StyledLink href="mailto:travis@microsoft.com?subject=Variables Import feedback" target="_blank">
<StyledLink
href="mailto:travis@microsoft.com?subject=Variables Import feedback"
aria-label="Send feedback via email"
>
Feedback
</StyledLink>
{" • "}
<StyledLink href="https://github.com/microsoft/figma-variables-import" target="_blank">
<StyledLink
href="https://github.com/microsoft/figma-variables-import"
target="_blank"
rel="noopener noreferrer"
aria-label="View source code on GitHub (opens in new tab)"
>
GitHub
</StyledLink>
{" • "}
<StyledLink href="https://go.microsoft.com/fwlink/?linkid=521839" target="_blank">
<StyledLink
href="https://go.microsoft.com/fwlink/?linkid=521839"
target="_blank"
rel="noopener noreferrer"
aria-label="Microsoft Privacy Statement (opens in new tab)"
>
Privacy
</StyledLink>
{" • "}
<StyledLink href="https://go.microsoft.com/fwlink/?linkid=206977" target="_blank">
<StyledLink
href="https://go.microsoft.com/fwlink/?linkid=206977"
target="_blank"
rel="noopener noreferrer"
aria-label="Microsoft Terms of Use (opens in new tab)"
>
Terms of use
</StyledLink>
</div>
Expand Down
Loading