1
1
mirror of https://github.com/primer/css.git synced 2024-12-23 22:24:11 +03:00

AppFrame component (#2147)

* Add AppFrame component

* Please stylelint

* Update src/layout/app-frame.scss

Co-authored-by: Katie Langerman <langermank@github.com>

* Use tokens

* Add changeset

Co-authored-by: Katie Langerman <langermank@github.com>
This commit is contained in:
Vinicius Depizzol 2022-07-01 13:00:14 -07:00 committed by GitHub
parent ee85a4b4f2
commit 9dd2a49e26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 303 additions and 0 deletions

View File

@ -0,0 +1,5 @@
---
"@primer/css": minor
---
Add AppFrame component

View File

@ -0,0 +1,142 @@
import React from 'react'
import clsx from 'clsx'
//import { AppHeaderTemplate as AppHeader } from '../App/AppHeader.stories'
export default {
title: 'Components/Layout/AppFrame',
parameters: {
layout: 'fullscreen'
},
excludeStories: ['AppFrameTemplate'],
argTypes: {
// Debug
_debug: {
control: {
type: 'boolean',
}
},
a11yNavItems: {
type: 'array',
},
// Children
headerChildren: {
description: 'creates a slot for header children',
table: {
category: 'HTML'
}
},
subheaderChildren: {
description: 'creates a slot for subheader children',
table: {
category: 'HTML'
}
},
bodyChildren: {
description: 'creates a slot for body children',
table: {
category: 'HTML'
}
},
footerChildren: {
description: 'creates a slot for footer children',
table: {
category: 'HTML'
}
},
},
}
export const AppFrameTemplate = ({
_debug,
a11yNavItems,
headerChildren,
subheaderChildren,
bodyChildren,
footerChildren,
}) => {
// Default values
a11yNavItems = a11yNavItems ?? [
{url: '#start-of-content', label: 'Skip to content'},
{url: '/', label: 'GitHub homepage'},
];
return (
<>
<div className={clsx('AppFrame')}>
<div className={clsx('AppFrame-a11yNav')}>
{a11yNavItems.map(link => (
<a className={clsx('AppFrame-a11yLink')} href={link.url}>{link.label}</a>
))}
</div>
<div className={clsx('AppFrame-main')}>
<div className={clsx('AppFrame-header-wrapper')}>
<div className={clsx('AppFrame-header')}>
{headerChildren}
</div>
<div id="start-of-content"></div>
{subheaderChildren && (
<div className={clsx('AppFrame-subheader')}>
{subheaderChildren}
</div>
)}
</div>
<div className={clsx('AppFrame-body')}>
{bodyChildren}
</div>
</div>
<div className={clsx('AppFrame-footer')}>
{footerChildren}
</div>
</div>
{_debug && (
<>
<style type="text/css">{`
.AppFrame {
}
.AppFrame-header,
.AppFrame-subheader,
.AppFrame-body,
.AppFrame-footer {
padding: 16px;
}
.AppFrame-header {
background: pink;
}
.AppFrame-subheader {
background: lightblue;
}
.AppFrame-footer {
background: pink;
}
`}</style>
</>
)}
</>
);
};
export const Playground = AppFrameTemplate.bind({})
Playground.args = {
_debug: true,
headerChildren: "Header slot",
subheaderChildren: "Subheader slot",
bodyChildren: "Body slot",
footerChildren: "Footer slot",
};

155
src/layout/app-frame.scss Normal file
View File

@ -0,0 +1,155 @@
// stylelint-disable max-nesting-depth
// stylelint-disable primer/spacing
// stylelint-disable primer/borders
.AppFrame {
// AppFrame structure
// ===================
//
// .AppFrame
// .AppFrame-a11yNav
// .AppFrame-a11yLink
//
// .AppFrame-main
// .AppFrame-header-wrapper
// .AppFrame-header
// .AppFrame-subheader
//
// #start-of-content
// .AppFrame-body
// .AppFrame-footer
// Accessibility navigation
.AppFrame-a11yNav {
position: absolute;
z-index: 1000;
display: flex;
width: 100%;
padding: var(--base-size-16, 16px);
background: var(--color-canvas-inset);
padding-block-end: calc(var(--base-size-16, 16px) - var(--primer-borderWidth-thin, 1px));
isolation: isolate;
align-items: center;
gap: var(--base-size-8, 8px);
&:not(:focus-within) {
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
border: 0;
}
&:focus-within {
top: 0;
left: 0;
// Narrow viewport
@media (max-width: #{map-get($breakpoints, 'md') - 0.02px}) {
justify-content: center;
}
}
}
.AppFrame-a11yLink {
transition: none;
&:not(:focus) {
display: block;
width: var(--base-size-8, 8px);
height: var(--base-size-8, 8px);
overflow: hidden;
text-indent: var(--base-size-128, 128px);
pointer-events: none;
background: var(--color-border-default);
border-radius: var(--primer-borderRadius-full, 100vh);
}
&:focus {
z-index: 20;
display: grid;
width: auto;
height: auto;
min-height: var(--primer-control-medium-size, 32px);
padding: 0 var(--primer-control-medium-paddingInline-spacious, 16px);
overflow: auto;
color: var(--color-fg-on-emphasis);
background: var(--color-accent-emphasis);
border-radius: var(--primer-borderRadius-full, 100vh);
align-items: center;
@media (pointer: coarse) {
&::after {
@include minTouchTarget(var(--primer-control-minTarget-coarse, 44px));
}
}
@media (prefers-reduced-motion: no-preference) {
animation: AppFrame-a11yLink-focus 200ms ease-out;
}
@keyframes AppFrame-a11yLink-focus {
0% {
color: var(--color-accent-emphasis);
transform: scale(0.3, 0.25);
}
50% {
color: var(--color-accent-emphasis);
transform: scale(1, 1);
}
55% {
color: var(--color-fg-on-emphasis);
}
100% {
transform: scaleX(1);
}
}
}
}
.AppFrame-main {
display: flex;
min-height: 100vh;
flex-direction: column;
@supports (height: 100dvh) {
min-height: 100dvh;
}
}
.AppFrame-header-wrapper {
position: relative;
height: min-content;
overflow: visible;
.AppFrame-header {
position: sticky;
top: 0;
z-index: 1;
}
}
.AppFrame-header {
flex: 0 0 auto;
}
.AppFrame-subheader {
flex: 0 0 auto;
}
.AppFrame-body {
flex: 1 0;
height: 100%;
}
.AppFrame-footer {
flex: 0 0 auto;
}
}

View File

@ -1,4 +1,5 @@
@import '../support/index.scss'; @import '../support/index.scss';
@import './app-frame.scss';
@import './mixins.scss'; @import './mixins.scss';
@import './container.scss'; @import './container.scss';
@import './grid.scss'; @import './grid.scss';