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:
parent
ee85a4b4f2
commit
9dd2a49e26
5
.changeset/lovely-rivers-bow.md
Normal file
5
.changeset/lovely-rivers-bow.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@primer/css": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add AppFrame component
|
142
docs/src/stories/components/Layout/AppFrame.stories.jsx
Normal file
142
docs/src/stories/components/Layout/AppFrame.stories.jsx
Normal 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
155
src/layout/app-frame.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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';
|
||||||
|
Loading…
Reference in New Issue
Block a user