diff --git a/README.md b/README.md index 46e4f80..9ed4235 100644 --- a/README.md +++ b/README.md @@ -21,20 +21,16 @@ On iOS, install pods in your app's `ios` directory. Android links automatically ## Usage ```tsx -import { View } from "react-native"; -import { - ReservedRegionsProvider, - useReservedRegions, -} from "react-native-reserved-regions"; +import { View } from 'react-native'; +import { ReservedRegionsProvider, useReservedRegions } from 'react-native-reserved-regions'; function Screen() { const fold = useReservedRegions().find( - (region) => - region.kind === "division" && region.frame.height > region.frame.width, + (region) => region.kind === 'division' && region.frame.height > region.frame.width, ); if (!fold) return ; return ( - + @@ -62,12 +58,12 @@ The public type is a tagged union: ```ts type ReservedRegion = | { - kind: "division"; + kind: 'division'; frame: { x: number; y: number; width: number; height: number }; occludesContent: boolean; } | { - kind: "occlusion"; + kind: 'occlusion'; frame: { x: number; y: number; width: number; height: number }; }; ``` diff --git a/website/docusaurus.config.mjs b/website/docusaurus.config.mjs index f34dbae..0804631 100644 --- a/website/docusaurus.config.mjs +++ b/website/docusaurus.config.mjs @@ -3,7 +3,7 @@ import { themes } from 'prism-react-renderer'; export default { title: 'Reserved Regions', tagline: 'Display divisions and occlusions in your React Native view coordinates.', - favicon: 'img/mark.svg', + favicon: 'img/logo.png', url: 'https://appandflow.github.io', baseUrl: '/react-native-reserved-regions/', organizationName: 'appandflow', diff --git a/website/src/component/ConceptCard.jsx b/website/src/component/ConceptCard.jsx new file mode 100644 index 0000000..3d5e50e --- /dev/null +++ b/website/src/component/ConceptCard.jsx @@ -0,0 +1,33 @@ +import React from 'react'; +import Link from '@docusaurus/Link'; +import Illustration from './Illustration'; +import styles from '../css/ConceptCard.module.css'; +import shared from '../css/shared.module.css'; + +export default function ConceptCard({ concept, isFirst }) { + return ( +
+

{concept.title}

+

{concept.description}

+ + {concept.label} + + +
+ ); +} diff --git a/website/src/component/Illustration.jsx b/website/src/component/Illustration.jsx new file mode 100644 index 0000000..246bcf4 --- /dev/null +++ b/website/src/component/Illustration.jsx @@ -0,0 +1,39 @@ +import React, { useEffect, useRef } from 'react'; +import useBaseUrl from '@docusaurus/useBaseUrl'; +import styles from '../css/Illustration.module.css'; + +export default function Illustration({ file, alt, width, height, style, reveal = false, className = '' }) { + const imageRef = useRef(null); + useEffect(() => { + if (!reveal || !('IntersectionObserver' in window)) return; + const image = imageRef.current; + image.dataset.reveal = 'pending'; + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + image.dataset.reveal = 'visible'; + observer.disconnect(); + } + }, + { + threshold: 0.15, + }, + ); + observer.observe(image); + return () => { + observer.disconnect(); + delete image.dataset.reveal; + }; + }, [reveal]); + return ( + {alt} + ); +} diff --git a/website/src/css/ConceptCard.module.css b/website/src/css/ConceptCard.module.css new file mode 100644 index 0000000..5afd7de --- /dev/null +++ b/website/src/css/ConceptCard.module.css @@ -0,0 +1,23 @@ +.concept { + padding: 28px 24px 0; + border: 1px solid rgba(18, 18, 18, 0.1); + overflow: hidden; +} + +.conceptDescription { + margin: 16px 0; +} + +.concept a:hover { + color: #008cff; +} + +.conceptIllustration { + margin: 64px auto 0; +} + +@media (width <= 600px) { + .conceptIllustration { + margin: 40px auto 0; + } +} diff --git a/website/src/css/Illustration.module.css b/website/src/css/Illustration.module.css new file mode 100644 index 0000000..fa8ae68 --- /dev/null +++ b/website/src/css/Illustration.module.css @@ -0,0 +1,18 @@ +.illustration { + display: block; + width: 100%; + height: auto; +} + +@media (prefers-reduced-motion: no-preference) { + .illustrationReveal { + transition: + opacity 600ms ease, + translate 600ms cubic-bezier(0.22, 1, 0.36, 1); + } + + .illustrationReveal[data-reveal='pending'] { + opacity: 0; + translate: 0 12px; + } +} diff --git a/website/src/css/custom.css b/website/src/css/custom.css index 4915e8c..a6b4ce4 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -1,3 +1,27 @@ +@font-face { + font-family: 'GT Maru'; + src: url('../font/GT-Maru-Bold.ttf') format('truetype'); + font-style: normal; + font-weight: 700; + font-display: swap; +} + +@font-face { + font-family: 'Inter'; + src: url('../font/InterVariable.woff2') format('woff2'); + font-style: normal; + font-weight: 100 900; + font-display: swap; +} + +@font-face { + font-family: 'JetBrains Mono'; + src: url('../font/JetBrainsMono-Regular.woff2') format('woff2'); + font-style: normal; + font-weight: 400; + font-display: swap; +} + :root { --ifm-color-primary: #315de0; --ifm-color-primary-dark: #204ccd; diff --git a/website/src/css/index.module.css b/website/src/css/index.module.css new file mode 100644 index 0000000..ee8ca12 --- /dev/null +++ b/website/src/css/index.module.css @@ -0,0 +1,263 @@ +.codeBlock { + box-shadow: none !important; + background: #fafafa !important; + border: 1px solid #e1dedef6; + border-radius: 8px; + overflow: hidden; +} + +.codeBlock pre { + background: #fafafa !important; + overflow-x: auto; +} + +.codeBlock pre, +.codeBlock code { + white-space: pre-wrap !important; + overflow-wrap: anywhere !important; + word-break: normal !important; +} + +.codeBlock button[aria-label='Toggle word wrap'] { + display: none; +} + +.codeBlock code, +.codeBlock :global(.token-line), +.codeBlock :global(.token) { + color: #663cb5 !important; +} + +.codeBlock :global(.token.plain), +.codeBlock :global(.token.variable), +.codeBlock :global(.token.parameter) { + color: #008cff !important; +} + +.codeBlock :global(.token.keyword), +.codeBlock :global(.token.operator) { + color: #f97583 !important; +} + +.codeBlock :global(.token.string) { + color: #ffab70 !important; +} + +.button { + position: relative; + overflow: hidden; + isolation: isolate; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border-radius: 40px; + background: #000; + color: #fff; + font-size: 12px; + font-weight: 500; + text-decoration: none; + white-space: nowrap; +} + +.button::before { + content: ''; + position: absolute; + inset: -50% -10%; + background: #008cff; + border-radius: 45% 55% 0 0; + transform: translateY(100%); + transition: transform 700ms ease; + pointer-events: none; + z-index: -1; +} + +.button:hover::before { + transform: translateY(0); +} + +@media (prefers-reduced-motion: reduce) { + .button::before { + transition: none; + } +} + +.page { + min-height: 100vh; + background: #fff; + color: #121212; + font-family: Inter, system-ui, sans-serif; + font-size: 14px; + line-height: 1.5; + --ifm-heading-color: #121212; + --ifm-link-color: #008cff; + --ifm-font-family-monospace: 'JetBrains Mono', ui-monospace, monospace; +} + +.page code { + font-family: 'JetBrains Mono', ui-monospace, monospace; +} + +.container { + width: calc(100% - 48px); + max-width: 520px; + margin: 0 auto; + padding: 120px 0 160px; +} + +@media (width <= 600px) { + .container { + width: calc(100% - 40px); + padding: 20px 0 160px; + } +} + +.navigation { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 16px; + margin-bottom: 48px; + font-size: 12px; + line-height: 1.4; +} + +.logo { + width: 32px; + height: 32px; +} + +.navLink { + color: #828282; +} + +.githubLink { + color: #828282; + display: flex; + gap: 5px; +} + +.githubIcon { + width: 16px; + height: 16px; + flex-shrink: 0; +} + +.title { + margin: 0 0 16px; + color: #000; + font-size: clamp(30px, 5vw, 36px); + font-family: 'GT Maru', Inter, sans-serif; + font-weight: 700; + line-height: 1.12; + letter-spacing: -0.8px; + width: min(350px, 100%); +} + +.intro { + margin: 0; +} + +.heroIllustration { + margin: 48px 0; +} + +.actions { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 16px; +} + +.install { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + min-width: 0; +} + +.installCommand { + padding: 0; + border: 0; + background: transparent; + color: #828282; + font-size: 12px; + overflow-wrap: anywhere; +} + +.copyButton { + padding: 8px; + cursor: pointer; +} + +.copyIcons { + display: grid; + width: 16px; + height: 16px; +} + +.copyIcon { + grid-area: 1 / 1; + transition: all var(--ifm-transition-fast) ease; +} + +.copySuccess { + grid-area: 1 / 1; + color: #00d600; + transition: all var(--ifm-transition-fast) ease; +} + +.concepts { + margin-top: 32px; +} + +.usage { + margin-top: 64px; +} + +.usageIntro { + padding: 20px 0; +} + +.usageDescription { + margin: 0 0 20px; +} + +.example { + padding: 20px 0; +} + +.exampleDescription { + margin: 0 0 20px; +} + +.exampleButton { + font-size: 14px; + gap: 8px; +} + +.icon { + width: 16px; + height: 16px; +} + +.footer { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 32px; + padding: 16px 0; + border-top: 1px solid rgba(18, 18, 18, 0.05); + font-size: 12px; +} + +.button:hover { + color: #fff; + text-decoration: none; +} + +.navLink:hover, +.githubLink:hover { + color: #828282; +} diff --git a/website/src/css/shared.module.css b/website/src/css/shared.module.css new file mode 100644 index 0000000..4dbaae7 --- /dev/null +++ b/website/src/css/shared.module.css @@ -0,0 +1,36 @@ +.heading { + font-family: 'Inter', sans-serif; + font-weight: 600; + font-style: normal; + font-size: 16px; + line-height: 100%; + letter-spacing: -0.02em; + margin: 0; + color: #121212; +} + +.description { + font-family: 'Inter', sans-serif; + font-weight: 400; + font-size: 14px; + line-height: 150%; + letter-spacing: -0.01em; +} + +.textLink { + color: #008cff; +} + +.link span { + display: inline-block; +} + +@media (prefers-reduced-motion: no-preference) { + .link span { + transition: transform 180ms ease; + } + + .link:is(:hover, :focus-visible) span { + transform: translate(2px, -2px); + } +} diff --git a/website/src/font/GT-Maru-Bold.ttf b/website/src/font/GT-Maru-Bold.ttf new file mode 100644 index 0000000..59871bd Binary files /dev/null and b/website/src/font/GT-Maru-Bold.ttf differ diff --git a/website/src/font/Inter-LICENSE.txt b/website/src/font/Inter-LICENSE.txt new file mode 100644 index 0000000..9b2ca37 --- /dev/null +++ b/website/src/font/Inter-LICENSE.txt @@ -0,0 +1,92 @@ +Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION AND CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/website/src/font/InterVariable.woff2 b/website/src/font/InterVariable.woff2 new file mode 100644 index 0000000..5a8d3e7 Binary files /dev/null and b/website/src/font/InterVariable.woff2 differ diff --git a/website/src/font/JetBrainsMono-OFL.txt b/website/src/font/JetBrainsMono-OFL.txt new file mode 100644 index 0000000..5ceee00 --- /dev/null +++ b/website/src/font/JetBrainsMono-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/website/src/font/JetBrainsMono-Regular.woff2 b/website/src/font/JetBrainsMono-Regular.woff2 new file mode 100644 index 0000000..66c5467 Binary files /dev/null and b/website/src/font/JetBrainsMono-Regular.woff2 differ diff --git a/website/src/pages/index.jsx b/website/src/pages/index.jsx index 94856ff..f5f41d0 100644 --- a/website/src/pages/index.jsx +++ b/website/src/pages/index.jsx @@ -1,9 +1,17 @@ -import React from 'react'; -import Layout from '@theme/Layout'; +import ConceptCard from '../component/ConceptCard'; +import Illustration from '../component/Illustration'; +import styles from '../css/index.module.css'; +import shared from '../css/shared.module.css'; +import React, { useEffect, useRef, useState } from 'react'; +import LayoutProvider from '@theme/Layout/Provider'; +import SkipToContent from '@theme/SkipToContent'; +import { PageMetadata, SkipToContentFallbackId } from '@docusaurus/theme-common'; import Link from '@docusaurus/Link'; +import useBaseUrl from '@docusaurus/useBaseUrl'; import CodeBlock from '@theme/CodeBlock'; -import styles from './index.module.css'; - +import packageJson from '../../../package.json'; +const github = 'https://github.com/appandflow/react-native-reserved-regions'; +const command = 'npm install react-native-reserved-regions'; const snippet = `const regions = useReservedRegions(); for (const region of regions) { @@ -13,183 +21,227 @@ for (const region of regions) { const { x, y, width, height } = region.frame; }`; - -const portraitFrame = { width: 669, height: 951 }; -const portraitRegions = { - division: { x: 0, y: 475.5, width: 669, height: 0 }, - occlusion: { x: 573, y: 42, width: 54, height: 54 }, -}; - -function rotateClockwise({ x, y, width, height }) { - return { - x: portraitFrame.height - y - height, - y: x, - width: height, - height: width, - }; -} - -function RegionLabel({ kind, frame }) { +const concepts = [ + { + title: 'View-relative coordinates', + description: 'Measure a whole screen or a smaller panel. Each provider establishes its own coordinate space.', + label: 'Coordinates', + to: '/docs/coordinates', + image: 'coordinates.png', + maxWidth: 456, + width: 1368, + height: 599, + alt: 'A nested provider establishes coordinates relative to its container.', + }, + { + title: 'Divisions and occlusions', + description: + 'A tagged union distinguishes divisions from occlusions. Divisions also tell you whether content is hidden.', + label: 'API reference', + to: '/docs/api', + image: 'occlusions.png', + maxWidth: 456, + width: 1368, + height: 640, + alt: 'Phone displays with camera occlusions and usable content areas.', + }, + { + title: 'iOS and Android', + description: + 'UIKit reserved regions and Android folding features share an API with documented platform differences.', + label: 'Platform support', + to: '/docs/platforms', + image: 'platforms.png', + maxWidth: 448, + width: 1344, + height: 588, + alt: 'Foldable and conventional devices on iOS and Android.', + }, +]; +export default function Home() { + const [copyStatus, setCopyStatus] = useState(''); + const resetTimer = useRef(null); + const copied = copyStatus === 'Copied'; + const copyIcon = useBaseUrl('/img/copy-05.png'); + useEffect(() => () => clearTimeout(resetTimer.current), []); + async function copyCommand() { + try { + await navigator.clipboard.writeText(command); + clearTimeout(resetTimer.current); + setCopyStatus('Copied'); + resetTimer.current = setTimeout(() => setCopyStatus(''), 2000); + } catch { + clearTimeout(resetTimer.current); + setCopyStatus('Select the command and copy it manually.'); + } + } return ( - - {kind} - - x:{frame.x} y:{frame.y} - - - w:{frame.width} h:{frame.height} pt - - - ); -} + + + +
+
+ -function GeometryPreview() { - const geometryStyle = { - '--division-y': `${(portraitRegions.division.y / portraitFrame.height) * 100}%`, - '--cutout-x': `${(portraitRegions.occlusion.x / portraitFrame.width) * 100}%`, - '--cutout-y': `${(portraitRegions.occlusion.y / portraitFrame.height) * 100}%`, - '--cutout-width': `${(portraitRegions.occlusion.width / portraitFrame.width) * 100}%`, - '--cutout-height': `${(portraitRegions.occlusion.height / portraitFrame.height) * 100}%`, - }; +
+
+

Reserved Regions for React Native

+

+ Get the bounds of folds, camera cutouts, and system UI on iOS and Android. Use them to keep content and + controls clear. +

+ - return ( -
-
- - PROVIDER COORDINATES - - 669 × 951 pt - 951 × 669 pt - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- +
+ + Get started + +
+ {command} + +
-
-
-
- + + {copyStatus} + +
+ +
+ {concepts.map((concept, index) => ( + + ))} +
+ +
+

+ Usage +

+
+

+ Wrap a view in ReservedRegionsProvider and read its regions with useReservedRegions. Pair region + frames with safe area insets when your screen needs both edge spacing and information about its + interior. +

+ + Learn more +
-
- + + + {snippet} + + +
+

+ Compare full-screen, inset, and content-box providers, with safe area insets alongside reserved + regions. +

+ + + Example app ↗ +
-
-
-
- -
- -
- - ); -} + + -export default function Home() { - return ( - -
-
-
-

REACT NATIVE · NEW ARCHITECTURE

-

- Reserved Regions -
- for React Native -

-

- Get the bounds of folds, camera cutouts, and system UI on iOS and Android. Use them to keep content and - controls clear. -

-
- - Get started +
+ + Made by{' '} + + App&Flow - - API reference - -
-

react-native-reserved-regions

-
- -
-
-
-

View-relative coordinates

-

Measure a whole screen or a smaller panel. Each provider establishes its own coordinate space.

- Coordinates → -
-
-

Divisions and occlusions

-

- A tagged union distinguishes divisions from occlusions. Divisions also tell you whether content is hidden. -

- API reference → -
-
-

iOS and Android

-

- UIKit reserved regions and Android folding features share an API with documented platform differences. -

- Platform support → -
-
-
-
-

Usage

-

- Wrap a view in ReservedRegionsProvider and read its regions with{' '} - useReservedRegions. -

-

- Pair region frames with safe area insets when your screen needs both edge spacing and information about - its interior. -

- Use it with safe area context → -
- - {snippet} - -
-
-
-

Example app

-

- The native example compares full-screen, inset, and content-box providers, with safe area insets alongside - reserved regions. -

-
- - Run the example → - -
-
-
+ + + licensed under the MIT License + + + +
); } diff --git a/website/src/pages/index.module.css b/website/src/pages/index.module.css deleted file mode 100644 index 37ea95c..0000000 --- a/website/src/pages/index.module.css +++ /dev/null @@ -1,431 +0,0 @@ -.hero, -.concepts, -.codeSection, -.exampleSection { - max-width: 1200px; - margin: 0 auto; -} -.hero { - display: grid; - grid-template-columns: 1.05fr 1fr; - gap: 64px; - align-items: center; - padding: 100px 32px 90px; -} -.eyebrow { - color: var(--ifm-color-primary); - font-size: 0.72rem; - font-weight: 700; - letter-spacing: 0.12em; - margin-bottom: 20px; -} -.hero h1 { - font-size: clamp(2.5rem, 4.1vw, 3.65rem); - line-height: 1.12; - letter-spacing: -0.06em; - margin-bottom: 25px; -} -.hero h1 span { - color: var(--ifm-color-primary); -} -.intro { - color: var(--rr-muted); - font-size: 1.1rem; - max-width: 480px; - line-height: 1.75; -} -.actions { - display: flex; - gap: 25px; - align-items: center; - margin-top: 30px; -} -.referenceLink { - color: var(--ifm-font-color-base); - font-size: 0.94rem; - font-weight: 600; -} -.packageName { - margin-top: 24px; - font: 0.78rem var(--ifm-font-family-monospace); - color: var(--rr-muted); - overflow-wrap: anywhere; -} -.preview { - margin: 0; - min-width: 0; - border: 1px solid var(--rr-border); - border-radius: 16px; - background: var(--rr-panel); - box-shadow: 0 16px 60px #172d5810; -} -.previewHeader { - display: flex; - gap: 9px; - align-items: center; - padding: 17px 20px; - font: 0.65rem var(--ifm-font-family-monospace); - letter-spacing: 0.07em; - color: var(--rr-muted); - border-bottom: 1px solid var(--rr-border); -} -.liveDot { - width: 6px; - height: 6px; - background: #315de0; - border-radius: 50%; -} -.previewUnit { - margin-left: auto; -} -.deviceStage { - display: grid; - height: 460px; - pointer-events: none; - margin: 25px 0; - place-items: center; -} -.device { - width: min(280px, calc(100% - 64px)); - box-sizing: content-box; - margin: 0; - padding: 13px; - border: 2px solid var(--rr-border); - border-radius: 23px; - background: var(--ifm-background-color); - transform-origin: center; - transition: transform 420ms cubic-bezier(0.2, 0.7, 0.2, 1); -} -.preview:has(.rotateToggle:checked) .device { - transform: rotate(90deg) scale(0.78); -} -.provider { - width: 100%; - aspect-ratio: 669 / 951; - border: 1.5px solid #527ef0; - border-radius: 12px; - position: relative; - background-image: radial-gradient(var(--rr-border) 0.7px, transparent 0.7px); - background-size: 12px 12px; -} -.panes { - position: absolute; - inset: 24px 18px; - display: grid; - grid-template-columns: 1fr; - grid-template-rows: 1fr 1fr; - gap: 42px; -} -.pane { - min-width: 0; -} -.stubTitle { - background: var(--rr-border); - height: 9px; - width: 65%; - margin-bottom: 14px; - border-radius: 3px; -} -.stub { - height: 5px; - margin-bottom: 8px; - background: var(--rr-subtle); - border-radius: 3px; -} -.tile { - height: 56px; - background: var(--rr-subtle); - border-radius: 8px; - margin: 15px 0; -} -.fold { - position: absolute; - top: var(--division-y); - right: 0; - left: 0; - border-top: 2px solid #d57839; - background: #d5783915; -} -.fold span { - position: absolute; - top: 0; - left: 50%; - transform: translate(-50%, -50%); - background: #fff4e9; - border: 1px solid #e8b992; - border-radius: 4px; - padding: 2px 7px; - font: 0.62rem var(--ifm-font-family-monospace); - color: #8a4516; - transition: transform 420ms cubic-bezier(0.2, 0.7, 0.2, 1); -} -.regionLabel { - display: grid; - width: max-content; - max-width: 160px; - gap: 1px; - white-space: nowrap; - line-height: 1.2; -} -.regionLabel b, -.regionLabel i { - font: inherit; -} -.regionLabel b { - font-weight: 700; -} -.cutout { - position: absolute; - top: var(--cutout-y); - right: auto; - left: var(--cutout-x); - box-sizing: border-box; - width: var(--cutout-width); - height: var(--cutout-height); - background: #7f4fc8; - border: 4px solid #d3bbee; - border-radius: 50%; -} -.cutout span { - position: absolute; - top: 50%; - right: auto; - left: 50%; - background: #f2eaff; - border: 1px solid #d0b8ef; - border-radius: 4px; - padding: 2px 7px; - font: 0.62rem var(--ifm-font-family-monospace); - color: #6936a7; - transform: translate(calc(-100% - 18px), -50%); - transform-origin: 0 0; - transition: transform 420ms cubic-bezier(0.2, 0.7, 0.2, 1); -} -.preview:has(.rotateToggle:checked) .fold span { - transform: translate(-50%, -50%) rotate(-90deg); -} -.preview:has(.rotateToggle:checked) .cutout span { - transform: rotate(-90deg) translate(calc(-100% - 18px), -50%); -} -.previewControls { - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - padding: 0 25px 18px; - color: var(--rr-muted); - font: 0.64rem var(--ifm-font-family-monospace); -} -.rotateButton { - display: inline-flex; - align-items: center; - gap: 7px; - border: 1px solid var(--rr-border); - border-radius: 6px; - padding: 6px 9px; - color: var(--ifm-font-color-base); - background: var(--ifm-background-color); - cursor: pointer; - font: inherit; - font-size: 0.8rem; - min-height: 44px; - position: relative; - margin: 0; -} -.rotateToggle { - position: absolute; - inset: 0; - width: 100%; - height: 100%; - margin: 0; - opacity: 0; - cursor: pointer; -} -.rotateButton:has(:focus-visible) { - outline: 2px solid var(--ifm-color-primary); - outline-offset: 3px; -} -.landscapeValue, -.preview:has(.rotateToggle:checked) .portraitValue { - display: none; -} -.preview:has(.rotateToggle:checked) .landscapeValue { - display: block; -} -.rotateButton:hover { - border-color: var(--ifm-color-primary); - color: var(--ifm-color-primary); -} -.concepts { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 48px; - padding: 44px 32px 66px; - border-top: 1px solid var(--rr-border); -} -.concepts h2 { - font-size: 1.25rem; - letter-spacing: -0.035em; - margin: 17px 0 12px; -} -.concepts p, -.codeSection p, -.exampleSection p { - color: var(--rr-muted); - font-size: 0.96rem; -} -.concepts a, -.codeSection a { - font-size: 0.87rem; - font-weight: 600; -} -.codeSection { - display: grid; - grid-template-columns: 1fr 1.15fr; - align-items: center; - gap: 72px; - padding: 65px 32px 85px; - border-top: 1px solid var(--rr-border); -} -.codeSection h2, -.exampleSection h2 { - font-size: 2rem; - letter-spacing: -0.045em; -} -.codeSection > div { - min-width: 0; -} -.codeSection pre { - font-size: 0.79rem; -} -.codeSection .eyebrow, -.exampleSection .eyebrow { - color: var(--ifm-color-primary); - font-size: 0.72rem; -} -.exampleSection { - display: flex; - align-items: center; - justify-content: space-between; - gap: 40px; - margin-bottom: 72px; - padding: 35px 40px; - border: 1px solid var(--rr-border); - border-radius: 12px; - background: var(--rr-panel); -} -.exampleSection p { - max-width: 620px; - margin-bottom: 0; -} -.exampleSection .eyebrow { - margin-bottom: 12px; -} -.exampleSection a { - flex-shrink: 0; -} -@media (max-width: 1200px) { - .exampleSection { - margin-left: 32px; - margin-right: 32px; - } -} -@media (max-width: 996px) { - .hero { - gap: 30px; - padding-top: 65px; - } - .actions { - gap: 18px; - } - .hero h1 { - font-size: 2.8rem; - } - .referenceLink { - font-size: 0.82rem; - } - .deviceStage .device { - margin: 0; - } - .concepts { - gap: 25px; - } - .codeSection { - gap: 36px; - } -} -@media (max-width: 760px) { - .hero, - .codeSection { - grid-template-columns: 1fr; - } - .hero { - padding: 48px 24px; - gap: 38px; - } - .hero h1 { - font-size: clamp(2.4rem, 8.7vw, 3.5rem); - } - .intro { - font-size: 1rem; - } - .preview { - max-width: 520px; - width: 100%; - } - .deviceStage { - height: 430px; - } - .concepts { - grid-template-columns: 1fr; - padding: 36px 24px; - gap: 32px; - } - .concepts article { - max-width: 500px; - } - .concepts h2 { - margin-top: 10px; - } - .codeSection { - padding: 40px 24px; - gap: 20px; - } - .exampleSection { - flex-direction: column; - align-items: flex-start; - padding: 28px; - margin: 0 24px 44px; - gap: 22px; - } - .exampleSection h2, - .codeSection h2 { - font-size: 1.7rem; - } -} -@media (max-width: 380px) { - .actions { - gap: 14px; - } - .actions :global(.button) { - font-size: 0.9rem; - padding: 12px 16px; - } - .deviceStage .device { - margin: 0; - padding: 10px; - } - .panes { - gap: 30px; - } - .referenceLink { - font-size: 0.76rem; - } -} -@media (prefers-reduced-motion: reduce) { - .device, - .fold span, - .cutout span { - transition: none; - } -} diff --git a/website/static/img/coordinates.png b/website/static/img/coordinates.png new file mode 100644 index 0000000..3775cb4 Binary files /dev/null and b/website/static/img/coordinates.png differ diff --git a/website/static/img/copy-05.png b/website/static/img/copy-05.png new file mode 100644 index 0000000..3df24d8 Binary files /dev/null and b/website/static/img/copy-05.png differ diff --git a/website/static/img/devices.png b/website/static/img/devices.png new file mode 100644 index 0000000..afc559f Binary files /dev/null and b/website/static/img/devices.png differ diff --git a/website/static/img/github-grey.png b/website/static/img/github-grey.png new file mode 100644 index 0000000..6985a67 Binary files /dev/null and b/website/static/img/github-grey.png differ diff --git a/website/static/img/github-white.png b/website/static/img/github-white.png new file mode 100644 index 0000000..2b94881 Binary files /dev/null and b/website/static/img/github-white.png differ diff --git a/website/static/img/logo.png b/website/static/img/logo.png new file mode 100644 index 0000000..534be66 Binary files /dev/null and b/website/static/img/logo.png differ diff --git a/website/static/img/occlusions.png b/website/static/img/occlusions.png new file mode 100644 index 0000000..4b4d7a2 Binary files /dev/null and b/website/static/img/occlusions.png differ diff --git a/website/static/img/platforms.png b/website/static/img/platforms.png new file mode 100644 index 0000000..ab85ba2 Binary files /dev/null and b/website/static/img/platforms.png differ