Skip to content
Merged
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
48 changes: 48 additions & 0 deletions packages/uniwind/src/bundler/artifacts/css/insets.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
const types = ['margin', 'padding', 'inset'] as const
const sides = ['inset', 'x', 'y', 'top', 'bottom', 'left', 'right'] as const
const logicalSides = ['start', 'end'] as const
const safeAreaTypes = ['safe', 'safe-or-*', 'safe-offset-*'] as const
const spacing = '--spacing(--value(integer))'
const length = '--value([length], --spacing-*)'

type Side = (typeof sides)[number]
type LogicalSide = (typeof logicalSides)[number]
type TypeName = (typeof types)[number]
type SafeAreaType = (typeof safeAreaTypes)[number]
type Inset = 'top' | 'bottom' | 'left' | 'right'
Expand Down Expand Up @@ -54,6 +56,30 @@ const generateCSSForInsets = () => {
return `${typeName}-${inset}`
}

const getLogicalUtilityName = (typeName: TypeName, side: LogicalSide, safeAreaType: SafeAreaType) => {
if (typeName === 'inset') {
return `${side}-${safeAreaType}`
}

return `${typeName.at(0)}${side.at(0)}-${safeAreaType}`
}

const getLogicalStyleProperty = (typeName: TypeName, side: LogicalSide) => {
if (typeName === 'inset') {
return `inset-inline-${side}`
}

return `${typeName}-inline-${side}`
}

const getLogicalInset = (side: LogicalSide, rtl: boolean): Inset => {
if (side === 'start') {
return rtl ? 'right' : 'left'
}

return rtl ? 'left' : 'right'
}

const getStylesForSafeAreaType = (safeAreaType: SafeAreaType, styles: Array<string>) => {
switch (safeAreaType) {
case 'safe':
Expand Down Expand Up @@ -99,6 +125,28 @@ const generateCSSForInsets = () => {
].join('\n')
})
})

logicalSides.forEach(side => {
const styleProperty = getLogicalStyleProperty(type, side)
const ltrStyles = [`${styleProperty}: env(safe-area-inset-${getLogicalInset(side, false)});`]
const rtlStyles = [`${styleProperty}: env(safe-area-inset-${getLogicalInset(side, true)});`]

safeAreaTypes.forEach(safeAreaType => {
const utilityName = getLogicalUtilityName(type, side, safeAreaType)

css += [
`@utility ${utilityName} {`,
...getStylesForSafeAreaType(safeAreaType, ltrStyles).map(style => ` ${style}`),
'',
' @variant rtl {',
...getStylesForSafeAreaType(safeAreaType, rtlStyles).map(style => ` ${style}`),
' }',
'}',
'',
'',
].join('\n')
})
})
})

// Remove the last newline character
Expand Down
14 changes: 14 additions & 0 deletions packages/uniwind/src/bundler/css-processor/rn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,20 @@ export class RN {
.replace('Block', 'Vertical')
}

if (x.includes('inset')) {
if (x === 'insetInlineStart') {
return 'start'
}

if (x === 'insetInlineEnd') {
return 'end'
}

return x
.replace('InlineStart', 'Start')
.replace('InlineEnd', 'End')
}

if (x.includes('border')) {
return x
.replace('InlineStart', 'Start')
Expand Down
2 changes: 2 additions & 0 deletions packages/uniwind/tests/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export const TW_SPACING = 4

export const SAFE_AREA_INSET_TOP = 21
export const SAFE_AREA_INSET_BOTTOM = 42
export const SAFE_AREA_INSET_LEFT = 7
export const SAFE_AREA_INSET_RIGHT = 13

export const SCREEN_WIDTH = 390
export const SCREEN_HEIGHT = 844
Expand Down
34 changes: 31 additions & 3 deletions packages/uniwind/tests/native/styles-parsing/safe-area.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react'
import { LayoutDirection } from '../../../src'
import View from '../../../src/components/native/View'
import { SAFE_AREA_INSET_BOTTOM, SAFE_AREA_INSET_TOP, SCREEN_HEIGHT, TW_SPACING } from '../../consts'
import { SAFE_AREA_INSET_BOTTOM, SAFE_AREA_INSET_LEFT, SAFE_AREA_INSET_RIGHT, SAFE_AREA_INSET_TOP, SCREEN_HEIGHT, TW_SPACING } from '../../consts'
import { renderUniwind } from '../utils'

describe('Safe Area', () => {
Expand All @@ -18,13 +19,40 @@ describe('Safe Area', () => {

expect(getStylesFromId('p-safe').paddingTop).toBe(SAFE_AREA_INSET_TOP)
expect(getStylesFromId('p-safe').paddingBottom).toBe(SAFE_AREA_INSET_BOTTOM)
expect(getStylesFromId('p-safe').paddingLeft).toBe(SAFE_AREA_INSET_LEFT)
expect(getStylesFromId('p-safe').paddingRight).toBe(SAFE_AREA_INSET_RIGHT)
expect(getStylesFromId('pt-safe').paddingTop).toBe(SAFE_AREA_INSET_TOP + TW_SPACING * 4)
expect(getStylesFromId('mb-safe-or-4').marginBottom).toBe(SAFE_AREA_INSET_BOTTOM)
expect(getStylesFromId('h-screen-safe').height).toBe(SCREEN_HEIGHT - SAFE_AREA_INSET_TOP - SAFE_AREA_INSET_BOTTOM)
expect(getStylesFromId('inset-safe').top).toBe(SAFE_AREA_INSET_TOP + 10)
expect(getStylesFromId('inset-safe').bottom).toBe(SAFE_AREA_INSET_BOTTOM + 10)
expect(getStylesFromId('inset-safe').left).toBe(10)
expect(getStylesFromId('inset-safe').right).toBe(10)
expect(getStylesFromId('inset-safe').left).toBe(SAFE_AREA_INSET_LEFT + 10)
expect(getStylesFromId('inset-safe').right).toBe(SAFE_AREA_INSET_RIGHT + 10)
expect(getStylesFromId('p-or-test-spacing').paddingTop).toBe(123)
})

test('logical Safe Area insets follow layout direction', () => {
const { getStylesFromId } = renderUniwind(
<React.Fragment>
<View className="ms-safe me-safe-offset-4 ps-safe-or-2 pe-safe start-safe end-safe-offset-4" testID="ltr" />
<LayoutDirection rtl>
<View className="ms-safe me-safe-offset-4 ps-safe-or-2 pe-safe start-safe end-safe-offset-4" testID="rtl" />
</LayoutDirection>
</React.Fragment>,
)

expect(getStylesFromId('ltr').marginStart).toBe(SAFE_AREA_INSET_LEFT)
expect(getStylesFromId('ltr').marginEnd).toBe(SAFE_AREA_INSET_RIGHT + TW_SPACING * 4)
expect(getStylesFromId('ltr').paddingStart).toBe(TW_SPACING * 2)
expect(getStylesFromId('ltr').paddingEnd).toBe(SAFE_AREA_INSET_RIGHT)
expect(getStylesFromId('ltr').start).toBe(SAFE_AREA_INSET_LEFT)
expect(getStylesFromId('ltr').end).toBe(SAFE_AREA_INSET_RIGHT + TW_SPACING * 4)

expect(getStylesFromId('rtl').marginStart).toBe(SAFE_AREA_INSET_RIGHT)
expect(getStylesFromId('rtl').marginEnd).toBe(SAFE_AREA_INSET_LEFT + TW_SPACING * 4)
expect(getStylesFromId('rtl').paddingStart).toBe(SAFE_AREA_INSET_RIGHT)
expect(getStylesFromId('rtl').paddingEnd).toBe(SAFE_AREA_INSET_LEFT)
expect(getStylesFromId('rtl').start).toBe(SAFE_AREA_INSET_RIGHT)
expect(getStylesFromId('rtl').end).toBe(SAFE_AREA_INSET_LEFT + TW_SPACING * 4)
})
})
6 changes: 3 additions & 3 deletions packages/uniwind/tests/setup.native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Dimensions } from 'react-native'
import { UniwindBundlerConfig } from '../src/bundler/config'
import { compileCSS } from '../src/bundler/css-compiler'
import { Platform } from '../src/common/consts'
import { SAFE_AREA_INSET_BOTTOM, SAFE_AREA_INSET_TOP, SCREEN_HEIGHT, SCREEN_WIDTH } from './consts'
import { SAFE_AREA_INSET_BOTTOM, SAFE_AREA_INSET_LEFT, SAFE_AREA_INSET_RIGHT, SAFE_AREA_INSET_TOP, SCREEN_HEIGHT, SCREEN_WIDTH } from './consts'

jest.spyOn(Dimensions, 'get').mockReturnValue({ width: SCREEN_WIDTH, height: SCREEN_HEIGHT, scale: 1, fontScale: 1 })

Expand All @@ -20,9 +20,9 @@ beforeAll(async () => {
Uniwind.__reinit(rt => ${virtualCode}, ['light', 'dark']);
Uniwind.updateInsets({
top: ${SAFE_AREA_INSET_TOP},
left: 0,
left: ${SAFE_AREA_INSET_LEFT},
bottom: ${SAFE_AREA_INSET_BOTTOM},
right: 0,
right: ${SAFE_AREA_INSET_RIGHT},
});
`,
)
Expand Down
168 changes: 168 additions & 0 deletions packages/uniwind/uniwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,62 @@
margin-right: calc(env(safe-area-inset-right) + --value([length], --spacing-*));
}

@utility ms-safe {
margin-inline-start: env(safe-area-inset-left);

@variant rtl {
margin-inline-start: env(safe-area-inset-right);
}
}

@utility ms-safe-or-* {
margin-inline-start: max(env(safe-area-inset-left), --spacing(--value(integer)));
margin-inline-start: max(env(safe-area-inset-left), --value([length], --spacing-*));

@variant rtl {
margin-inline-start: max(env(safe-area-inset-right), --spacing(--value(integer)));
margin-inline-start: max(env(safe-area-inset-right), --value([length], --spacing-*));
}
}

@utility ms-safe-offset-* {
margin-inline-start: calc(env(safe-area-inset-left) + --spacing(--value(integer)));
margin-inline-start: calc(env(safe-area-inset-left) + --value([length], --spacing-*));

@variant rtl {
margin-inline-start: calc(env(safe-area-inset-right) + --spacing(--value(integer)));
margin-inline-start: calc(env(safe-area-inset-right) + --value([length], --spacing-*));
}
}

@utility me-safe {
margin-inline-end: env(safe-area-inset-right);

@variant rtl {
margin-inline-end: env(safe-area-inset-left);
}
}

@utility me-safe-or-* {
margin-inline-end: max(env(safe-area-inset-right), --spacing(--value(integer)));
margin-inline-end: max(env(safe-area-inset-right), --value([length], --spacing-*));

@variant rtl {
margin-inline-end: max(env(safe-area-inset-left), --spacing(--value(integer)));
margin-inline-end: max(env(safe-area-inset-left), --value([length], --spacing-*));
}
}

@utility me-safe-offset-* {
margin-inline-end: calc(env(safe-area-inset-right) + --spacing(--value(integer)));
margin-inline-end: calc(env(safe-area-inset-right) + --value([length], --spacing-*));

@variant rtl {
margin-inline-end: calc(env(safe-area-inset-left) + --spacing(--value(integer)));
margin-inline-end: calc(env(safe-area-inset-left) + --value([length], --spacing-*));
}
}

@utility p-safe {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
Expand Down Expand Up @@ -256,6 +312,62 @@
padding-right: calc(env(safe-area-inset-right) + --value([length], --spacing-*));
}

@utility ps-safe {
padding-inline-start: env(safe-area-inset-left);

@variant rtl {
padding-inline-start: env(safe-area-inset-right);
}
}

@utility ps-safe-or-* {
padding-inline-start: max(env(safe-area-inset-left), --spacing(--value(integer)));
padding-inline-start: max(env(safe-area-inset-left), --value([length], --spacing-*));

@variant rtl {
padding-inline-start: max(env(safe-area-inset-right), --spacing(--value(integer)));
padding-inline-start: max(env(safe-area-inset-right), --value([length], --spacing-*));
}
}

@utility ps-safe-offset-* {
padding-inline-start: calc(env(safe-area-inset-left) + --spacing(--value(integer)));
padding-inline-start: calc(env(safe-area-inset-left) + --value([length], --spacing-*));

@variant rtl {
padding-inline-start: calc(env(safe-area-inset-right) + --spacing(--value(integer)));
padding-inline-start: calc(env(safe-area-inset-right) + --value([length], --spacing-*));
}
}

@utility pe-safe {
padding-inline-end: env(safe-area-inset-right);

@variant rtl {
padding-inline-end: env(safe-area-inset-left);
}
}

@utility pe-safe-or-* {
padding-inline-end: max(env(safe-area-inset-right), --spacing(--value(integer)));
padding-inline-end: max(env(safe-area-inset-right), --value([length], --spacing-*));

@variant rtl {
padding-inline-end: max(env(safe-area-inset-left), --spacing(--value(integer)));
padding-inline-end: max(env(safe-area-inset-left), --value([length], --spacing-*));
}
}

@utility pe-safe-offset-* {
padding-inline-end: calc(env(safe-area-inset-right) + --spacing(--value(integer)));
padding-inline-end: calc(env(safe-area-inset-right) + --value([length], --spacing-*));

@variant rtl {
padding-inline-end: calc(env(safe-area-inset-left) + --spacing(--value(integer)));
padding-inline-end: calc(env(safe-area-inset-left) + --value([length], --spacing-*));
}
}

@utility inset-safe {
top: env(safe-area-inset-top);
bottom: env(safe-area-inset-bottom);
Expand Down Expand Up @@ -379,6 +491,62 @@
right: calc(env(safe-area-inset-right) + --value([length], --spacing-*));
}

@utility start-safe {
inset-inline-start: env(safe-area-inset-left);

@variant rtl {
inset-inline-start: env(safe-area-inset-right);
}
}

@utility start-safe-or-* {
inset-inline-start: max(env(safe-area-inset-left), --spacing(--value(integer)));
inset-inline-start: max(env(safe-area-inset-left), --value([length], --spacing-*));

@variant rtl {
inset-inline-start: max(env(safe-area-inset-right), --spacing(--value(integer)));
inset-inline-start: max(env(safe-area-inset-right), --value([length], --spacing-*));
}
}

@utility start-safe-offset-* {
inset-inline-start: calc(env(safe-area-inset-left) + --spacing(--value(integer)));
inset-inline-start: calc(env(safe-area-inset-left) + --value([length], --spacing-*));

@variant rtl {
inset-inline-start: calc(env(safe-area-inset-right) + --spacing(--value(integer)));
inset-inline-start: calc(env(safe-area-inset-right) + --value([length], --spacing-*));
}
}

@utility end-safe {
inset-inline-end: env(safe-area-inset-right);

@variant rtl {
inset-inline-end: env(safe-area-inset-left);
}
}

@utility end-safe-or-* {
inset-inline-end: max(env(safe-area-inset-right), --spacing(--value(integer)));
inset-inline-end: max(env(safe-area-inset-right), --value([length], --spacing-*));

@variant rtl {
inset-inline-end: max(env(safe-area-inset-left), --spacing(--value(integer)));
inset-inline-end: max(env(safe-area-inset-left), --value([length], --spacing-*));
}
}

@utility end-safe-offset-* {
inset-inline-end: calc(env(safe-area-inset-right) + --spacing(--value(integer)));
inset-inline-end: calc(env(safe-area-inset-right) + --value([length], --spacing-*));

@variant rtl {
inset-inline-end: calc(env(safe-area-inset-left) + --spacing(--value(integer)));
inset-inline-end: calc(env(safe-area-inset-left) + --value([length], --spacing-*));
}
}

@custom-variant disabled {
&:disabled {
@slot;
Expand Down
3 changes: 3 additions & 0 deletions skills/uniwind/references/variants-and-selectors.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ Usage: `xs:p-2 tablet:p-4 3xl:p-8`
|-------|-------------|
| `p-safe` | All sides |
| `pt-safe` / `pb-safe` / `pl-safe` / `pr-safe` | Individual sides |
| `ps-safe` / `pe-safe` | Logical start / end |
| `px-safe` / `py-safe` | Horizontal / vertical |

### Margin
Expand All @@ -177,6 +178,7 @@ Usage: `xs:p-2 tablet:p-4 3xl:p-8`
|-------|-------------|
| `m-safe` | All sides |
| `mt-safe` / `mb-safe` / `ml-safe` / `mr-safe` | Individual sides |
| `ms-safe` / `me-safe` | Logical start / end |
| `mx-safe` / `my-safe` | Horizontal / vertical |

### Positioning
Expand All @@ -185,6 +187,7 @@ Usage: `xs:p-2 tablet:p-4 3xl:p-8`
|-------|-------------|
| `inset-safe` | All sides |
| `top-safe` / `bottom-safe` / `left-safe` / `right-safe` | Individual sides |
| `start-safe` / `end-safe` | Logical start / end |
| `x-safe` / `y-safe` | Horizontal / vertical inset |

### Compound Variants
Expand Down