@hzblj/kara-components 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/README.md +2 -3
- package/lib/module/atoms/h-stack/h-stack.android.js +2 -5
- package/lib/module/atoms/h-stack/h-stack.android.js.map +1 -1
- package/lib/module/atoms/v-stack/v-stack.android.js +2 -5
- package/lib/module/atoms/v-stack/v-stack.android.js.map +1 -1
- package/lib/module/atoms/z-stack/z-stack.android.js +2 -5
- package/lib/module/atoms/z-stack/z-stack.android.js.map +1 -1
- package/lib/module/molecules/index.js +1 -0
- package/lib/module/molecules/index.js.map +1 -1
- package/lib/module/molecules/labeled-content/labeled-content.js +2 -0
- package/lib/module/molecules/labeled-content/labeled-content.js.map +1 -1
- package/lib/module/molecules/list-item/list-item.js +12 -1
- package/lib/module/molecules/list-item/list-item.js.map +1 -1
- package/lib/module/molecules/navigation-bar/navigation-bar.android.js +65 -35
- package/lib/module/molecules/navigation-bar/navigation-bar.android.js.map +1 -1
- package/lib/module/molecules/navigation-bar/navigation-bar.ios.js +48 -13
- package/lib/module/molecules/navigation-bar/navigation-bar.ios.js.map +1 -1
- package/lib/module/molecules/rn-list-item/index.js +5 -0
- package/lib/module/molecules/rn-list-item/index.js.map +1 -0
- package/lib/module/molecules/rn-list-item/rn-list-item.js +71 -0
- package/lib/module/molecules/rn-list-item/rn-list-item.js.map +1 -0
- package/lib/module/molecules/rn-list-item/rn-list-item.types.js +4 -0
- package/lib/module/molecules/rn-list-item/rn-list-item.types.js.map +1 -0
- package/lib/module/organisms/context-menu/context-menu.android.js +83 -38
- package/lib/module/organisms/context-menu/context-menu.android.js.map +1 -1
- package/lib/module/organisms/sheet/index.js +2 -1
- package/lib/module/organisms/sheet/index.js.map +1 -1
- package/lib/module/organisms/sheet/sheet-header.js +7 -0
- package/lib/module/organisms/sheet/sheet-header.js.map +1 -0
- package/lib/module/organisms/sheet/sheet-scroll.js +62 -0
- package/lib/module/organisms/sheet/sheet-scroll.js.map +1 -0
- package/lib/module/organisms/sheet/sheet.js +136 -0
- package/lib/module/organisms/sheet/sheet.js.map +1 -0
- package/lib/module/organisms/sheet/split-sheet-children.js +21 -0
- package/lib/module/organisms/sheet/split-sheet-children.js.map +1 -0
- package/lib/module/organisms/sheet/use-sheet-header.js +118 -0
- package/lib/module/organisms/sheet/use-sheet-header.js.map +1 -0
- package/lib/module/ripple-clip.js +36 -0
- package/lib/module/ripple-clip.js.map +1 -0
- package/lib/module/use-stack-modifiers.js +35 -0
- package/lib/module/use-stack-modifiers.js.map +1 -0
- package/lib/typescript/molecules/index.d.ts +1 -0
- package/lib/typescript/molecules/labeled-content/labeled-content.types.d.ts +3 -0
- package/lib/typescript/molecules/list-item/list-item.types.d.ts +6 -0
- package/lib/typescript/molecules/navigation-bar/navigation-bar.types.d.ts +56 -17
- package/lib/typescript/molecules/rn-list-item/index.d.ts +2 -0
- package/lib/typescript/molecules/rn-list-item/rn-list-item.d.ts +2 -0
- package/lib/typescript/molecules/rn-list-item/rn-list-item.types.d.ts +10 -0
- package/lib/typescript/organisms/context-menu/context-menu.types.d.ts +2 -1
- package/lib/typescript/organisms/sheet/index.d.ts +1 -0
- package/lib/typescript/organisms/sheet/sheet-header.d.ts +3 -0
- package/lib/typescript/organisms/sheet/sheet-scroll.d.ts +9 -0
- package/lib/typescript/organisms/sheet/sheet.d.ts +7 -1
- package/lib/typescript/organisms/sheet/sheet.types.d.ts +86 -3
- package/lib/typescript/organisms/sheet/split-sheet-children.d.ts +9 -0
- package/lib/typescript/organisms/sheet/use-sheet-header.d.ts +24 -0
- package/lib/typescript/ripple-clip.d.ts +12 -0
- package/lib/typescript/use-stack-modifiers.d.ts +8 -0
- package/package.json +6 -4
- package/src/atoms/h-stack/h-stack.android.tsx +2 -8
- package/src/atoms/v-stack/v-stack.android.tsx +2 -8
- package/src/atoms/z-stack/z-stack.android.tsx +2 -8
- package/src/molecules/index.ts +1 -0
- package/src/molecules/labeled-content/labeled-content.tsx +2 -1
- package/src/molecules/labeled-content/labeled-content.types.ts +3 -0
- package/src/molecules/list-item/list-item.tsx +12 -4
- package/src/molecules/list-item/list-item.types.ts +6 -0
- package/src/molecules/navigation-bar/navigation-bar.android.tsx +50 -23
- package/src/molecules/navigation-bar/navigation-bar.ios.tsx +54 -15
- package/src/molecules/navigation-bar/navigation-bar.types.ts +56 -17
- package/src/molecules/rn-list-item/index.ts +2 -0
- package/src/molecules/rn-list-item/rn-list-item.tsx +48 -0
- package/src/molecules/rn-list-item/rn-list-item.types.ts +13 -0
- package/src/organisms/context-menu/context-menu.android.tsx +63 -38
- package/src/organisms/context-menu/context-menu.types.ts +2 -1
- package/src/organisms/sheet/index.ts +1 -0
- package/src/organisms/sheet/sheet-header.tsx +6 -0
- package/src/organisms/sheet/sheet-scroll.tsx +69 -0
- package/src/organisms/sheet/sheet.tsx +140 -0
- package/src/organisms/sheet/sheet.types.ts +93 -3
- package/src/organisms/sheet/split-sheet-children.ts +23 -0
- package/src/organisms/sheet/use-sheet-header.tsx +135 -0
- package/src/ripple-clip.ts +47 -0
- package/src/use-stack-modifiers.ts +24 -0
- package/lib/module/organisms/sheet/sheet.android.js +0 -26
- package/lib/module/organisms/sheet/sheet.android.js.map +0 -1
- package/lib/module/organisms/sheet/sheet.ios.js +0 -25
- package/lib/module/organisms/sheet/sheet.ios.js.map +0 -1
- package/lib/typescript/organisms/sheet/sheet.ios.d.ts +0 -2
- package/src/organisms/sheet/sheet.android.tsx +0 -25
- package/src/organisms/sheet/sheet.ios.tsx +0 -23
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { clickable, testID as testIdModifier } from '@expo/ui/jetpack-compose/modifiers';
|
|
4
|
+
import { useSxModifiers } from '@hzblj/kara-sx';
|
|
5
|
+
import { useMemo } from 'react';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A Compose stack's modifiers in the order a pressable one needs: its frame, shape and background, then the press,
|
|
9
|
+
* then its padding. Compose clips a `clickable` to what comes after it, so with the padding first the ripple would stop
|
|
10
|
+
* at the padding and a row pressed by its edge would light up only in the middle.
|
|
11
|
+
*/
|
|
12
|
+
export const useStackModifiers = (sx, onPress, testID) => {
|
|
13
|
+
const {
|
|
14
|
+
p,
|
|
15
|
+
pb,
|
|
16
|
+
pl,
|
|
17
|
+
pr,
|
|
18
|
+
pt,
|
|
19
|
+
px,
|
|
20
|
+
py,
|
|
21
|
+
...frame
|
|
22
|
+
} = sx;
|
|
23
|
+
const frameModifiers = useSxModifiers(frame);
|
|
24
|
+
const paddingModifiers = useSxModifiers({
|
|
25
|
+
p,
|
|
26
|
+
pb,
|
|
27
|
+
pl,
|
|
28
|
+
pr,
|
|
29
|
+
pt,
|
|
30
|
+
px,
|
|
31
|
+
py
|
|
32
|
+
});
|
|
33
|
+
return useMemo(() => [...frameModifiers, ...(testID ? [testIdModifier(testID)] : []), ...(onPress ? [clickable(onPress)] : []), ...paddingModifiers], [frameModifiers, onPress, paddingModifiers, testID]);
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=use-stack-modifiers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["clickable","testID","testIdModifier","useSxModifiers","useMemo","useStackModifiers","sx","onPress","p","pb","pl","pr","pt","px","py","frame","frameModifiers","paddingModifiers"],"sourceRoot":"../../src","sources":["use-stack-modifiers.ts"],"mappings":";;AAAA,SAAQA,SAAS,EAAuBC,MAAM,IAAIC,cAAc,QAAO,oCAAoC;AAC3G,SAA2BC,cAAc,QAAO,gBAAgB;AAChE,SAAQC,OAAO,QAAO,OAAO;;AAE7B;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,iBAAiB,GAAGA,CAACC,EAAgB,EAAEC,OAAoB,EAAEN,MAAe,KAAuB;EAC9G,MAAM;IAACO,CAAC;IAAEC,EAAE;IAAEC,EAAE;IAAEC,EAAE;IAAEC,EAAE;IAAEC,EAAE;IAAEC,EAAE;IAAE,GAAGC;EAAK,CAAC,GAAGT,EAAE;EAChD,MAAMU,cAAc,GAAGb,cAAc,CAACY,KAAK,CAAC;EAC5C,MAAME,gBAAgB,GAAGd,cAAc,CAAC;IAACK,CAAC;IAAEC,EAAE;IAAEC,EAAE;IAAEC,EAAE;IAAEC,EAAE;IAAEC,EAAE;IAAEC;EAAE,CAAC,CAAC;EAEpE,OAAOV,OAAO,CACZ,MAAM,CACJ,GAAGY,cAAc,EACjB,IAAIf,MAAM,GAAG,CAACC,cAAc,CAACD,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAC3C,IAAIM,OAAO,GAAG,CAACP,SAAS,CAACO,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,EACxC,GAAGU,gBAAgB,CACpB,EACD,CAACD,cAAc,EAAET,OAAO,EAAEU,gBAAgB,EAAEhB,MAAM,CACpD,CAAC;AACH,CAAC","ignoreList":[]}
|
|
@@ -7,6 +7,7 @@ export * from './list-item';
|
|
|
7
7
|
export * from './navigation-bar';
|
|
8
8
|
export * from './pager';
|
|
9
9
|
export * from './radio-group';
|
|
10
|
+
export * from './rn-list-item';
|
|
10
11
|
export * from './rn-tooltip';
|
|
11
12
|
export * from './scroll-edge-container';
|
|
12
13
|
export * from './search-bar';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SpaceSize } from '@hzblj/kara-theme';
|
|
1
2
|
import type { ReactNode } from 'react';
|
|
2
3
|
import type { IconName } from '../../atoms/icon';
|
|
3
4
|
/**
|
|
@@ -9,6 +10,8 @@ export type LabeledContentProps = {
|
|
|
9
10
|
icon?: IconName;
|
|
10
11
|
label: string;
|
|
11
12
|
onPress?: () => void;
|
|
13
|
+
/** Horizontal padding inside the row — inside its press area too, so Android's ripple reaches the card's edges. */
|
|
14
|
+
px?: SpaceSize;
|
|
12
15
|
testID?: string;
|
|
13
16
|
value?: string;
|
|
14
17
|
};
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
import type { SpaceSize } from '@hzblj/kara-theme';
|
|
1
2
|
import type { ReactNode } from 'react';
|
|
2
3
|
import type { IconName } from '../../atoms/icon';
|
|
3
4
|
export type ListItemProps = {
|
|
4
5
|
icon?: IconName;
|
|
5
6
|
onPress?: () => void;
|
|
7
|
+
/**
|
|
8
|
+
* Horizontal padding inside the row — inside its press area too, so Android's ripple reaches the card's edges. For
|
|
9
|
+
* rows in a card of your own; a `List` insets its rows by itself.
|
|
10
|
+
*/
|
|
11
|
+
px?: SpaceSize;
|
|
6
12
|
subtitle?: string;
|
|
7
13
|
testID?: string;
|
|
8
14
|
title: string;
|
|
@@ -1,24 +1,63 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
1
|
+
import type { ReactNode, Ref } from 'react';
|
|
2
|
+
import type { IconName } from '../../atoms/icon';
|
|
3
|
+
type NavigationBarItemBase = {
|
|
4
|
+
onPress: () => void;
|
|
5
|
+
/**
|
|
6
|
+
* iOS 26: neighbouring items on one side share a glass capsule; this gives the item its own. A close next to
|
|
7
|
+
* actions usually wants it. Material has no grouped capsules, so Android ignores it.
|
|
8
|
+
*/
|
|
9
|
+
separated?: boolean;
|
|
10
|
+
/** iOS: the item's accessibility identifier; Android: its test tag. */
|
|
11
|
+
testID?: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* One item of the bar. `back` and `close` are the platform's own — a chevron and the system close item on iOS, the
|
|
15
|
+
* Material arrow and cross on Android — with `Back` / `Close` as their labels unless given others. `icon` is one of the
|
|
16
|
+
* app's icons and names itself for VoiceOver and TalkBack.
|
|
17
|
+
*/
|
|
18
|
+
export type NavigationBarItem = (NavigationBarItemBase & {
|
|
19
|
+
label?: string;
|
|
20
|
+
type: 'back' | 'close';
|
|
21
|
+
}) | (NavigationBarItemBase & {
|
|
22
|
+
icon: IconName;
|
|
23
|
+
label: string;
|
|
24
|
+
type: 'icon';
|
|
25
|
+
});
|
|
26
|
+
export type NavigationBarSearchRef = {
|
|
27
|
+
/** Empties the field and ends editing. */
|
|
28
|
+
clear: () => void;
|
|
29
|
+
};
|
|
30
|
+
/** A search field in place of the title — iOS only for now; Android keeps the title. */
|
|
31
|
+
export type NavigationBarSearch = {
|
|
32
|
+
onChangeText: (text: string) => void;
|
|
33
|
+
onFocusChange?: (focused: boolean) => void;
|
|
34
|
+
placeholder: string;
|
|
35
|
+
ref?: Ref<NavigationBarSearchRef>;
|
|
36
|
+
testID?: string;
|
|
37
|
+
};
|
|
38
|
+
export type NavigationBarTitleAlignment = 'center' | 'leading';
|
|
2
39
|
/**
|
|
3
|
-
* A title with
|
|
4
|
-
* `UINavigationBar`, so the items press and render exactly like a native-stack header's
|
|
5
|
-
*
|
|
6
|
-
*
|
|
40
|
+
* A title with items on either side, for a header that is not a navigation screen's — `Sheet.Header` is built on it.
|
|
41
|
+
* On iOS a standalone `UINavigationBar`, so the items press and render exactly like a native-stack header's: on iOS 26
|
|
42
|
+
* neighbours share a glass capsule instead of swelling as standalone glass buttons do. On Android Material's top app
|
|
43
|
+
* bar. Where an item goes is the caller's choice — the bar places nothing by itself, so a close can sit on either side.
|
|
44
|
+
* The React Native tier: it needs no host.
|
|
7
45
|
*/
|
|
8
46
|
export type NavigationBarProps = {
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
/** iOS: the close item's accessibility identifier. */
|
|
16
|
-
closeTestID?: string;
|
|
17
|
-
/** Shows the back item. */
|
|
18
|
-
onBack?: () => void;
|
|
19
|
-
/** Shows the close item. */
|
|
20
|
-
onClose?: () => void;
|
|
47
|
+
/** Light items and title, for a bar laid over an image or a dark hero. */
|
|
48
|
+
inverted?: boolean;
|
|
49
|
+
/** In screen order, from the leading edge. */
|
|
50
|
+
leadingItems?: NavigationBarItem[];
|
|
51
|
+
/** Replaces the title with a search field (iOS). */
|
|
52
|
+
search?: NavigationBarSearch;
|
|
21
53
|
testID?: string;
|
|
22
54
|
title?: string;
|
|
55
|
+
/** `center` by default — iOS's own; `leading` is Material's top app bar. */
|
|
56
|
+
titleAlignment?: NavigationBarTitleAlignment;
|
|
57
|
+
/** Fades the title out and in without moving the items — for a large title that scrolls under the bar. */
|
|
58
|
+
titleVisible?: boolean;
|
|
59
|
+
/** In screen order, toward the trailing edge. */
|
|
60
|
+
trailingItems?: NavigationBarItem[];
|
|
23
61
|
};
|
|
24
62
|
export type NavigationBarComponent = (props: NavigationBarProps) => ReactNode;
|
|
63
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { ListItemProps } from '../list-item';
|
|
3
|
+
/**
|
|
4
|
+
* `ListItem`'s row in the React Native tier — plain views, no host. For lists long enough that a native host per row
|
|
5
|
+
* would cost too much to mount (a navigation list, a FlashList of rows), with the platform's own press feedback: a
|
|
6
|
+
* ripple across the whole row on Android, a highlight on iOS. In a rounded card, give the card `overflow="hidden"`:
|
|
7
|
+
* Android draws the ripple as the row's foreground, which only a parent's clip keeps inside rounded corners.
|
|
8
|
+
*/
|
|
9
|
+
export type RnListItemProps = ListItemProps;
|
|
10
|
+
export type RnListItemComponent = (props: RnListItemProps) => ReactNode;
|
|
@@ -4,7 +4,8 @@ import type { MenuAction } from '../menu';
|
|
|
4
4
|
/**
|
|
5
5
|
* A context menu on a React Native subtree — a card in a list, say — opened by a long press. iOS: a
|
|
6
6
|
* `UIContextMenuInteraction`, with an optional React Native `preview` lifted while the menu is open and a callback for
|
|
7
|
-
* a tap on it. Android
|
|
7
|
+
* a tap on it. Android, where Material has no lifted preview: a ripple and the long-press haptic, then a Material
|
|
8
|
+
* dropdown where the finger is, with the trigger highlighted while it is open. The trigger is ordinary React Native layout, so it
|
|
8
9
|
* needs no `Screen` or `Island`.
|
|
9
10
|
*/
|
|
10
11
|
export type ContextMenuProps = ResponsiveViewSx & {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SheetScrollContextValue, SheetScrollViewProps } from './sheet.types';
|
|
2
|
+
export declare const SheetScrollContext: import("react").Context<SheetScrollContextValue | null>;
|
|
3
|
+
/**
|
|
4
|
+
* For a scroll view of your own inside a sheet (a `RnFlatList`, say): pad its content by `topInset`, render
|
|
5
|
+
* `largeTitle` first, and forward `onScroll` — which is what `<Sheet.ScrollView>` does.
|
|
6
|
+
*/
|
|
7
|
+
export declare const useSheetScroll: () => SheetScrollContextValue;
|
|
8
|
+
/** The sheet's scrolling body: starts below the header on iOS 26, carries a large title, and reports its scroll. */
|
|
9
|
+
export declare const SheetScrollView: ({ children, content, keyboardShouldPersistTaps, onScroll, testID, }: SheetScrollViewProps) => import("react").JSX.Element;
|
|
@@ -1 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import type { SheetComponent } from './sheet.types';
|
|
2
|
+
/**
|
|
3
|
+
* A native sheet with an optional native header. `visible` presents and dismisses it; a swipe or a tap outside calls
|
|
4
|
+
* `onDismiss`, which should set `visible` back. `<Sheet.Header>` (a direct child) gives it a bar with a title and
|
|
5
|
+
* items; `<Sheet.ScrollView>` is the body of a `scrollable` sheet.
|
|
6
|
+
*/
|
|
7
|
+
export declare const Sheet: SheetComponent;
|
|
@@ -1,8 +1,91 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ResponsiveViewSx } from '@hzblj/kara-sx';
|
|
2
|
+
import type { ColorToken } from '@hzblj/kara-theme';
|
|
3
|
+
import type { ReactElement, ReactNode, Ref } from 'react';
|
|
4
|
+
import type { NativeScrollEvent, NativeSyntheticEvent, ScrollViewProps } from 'react-native';
|
|
5
|
+
import type { NavigationBarItem, NavigationBarSearch } from '../../molecules/navigation-bar';
|
|
6
|
+
/** `auto` fits the content; a number is a fraction of the screen's height. */
|
|
7
|
+
export type SheetDetent = 'auto' | number;
|
|
8
|
+
/**
|
|
9
|
+
* `center` is iOS's own, `leading` Material's. `large` puts the title above the content in a large face, and once
|
|
10
|
+
* that scrolls under the bar the bar's own title fades in — centred on iOS, leading on Android.
|
|
11
|
+
*/
|
|
12
|
+
export type SheetTitleStyle = 'center' | 'large' | 'leading';
|
|
13
|
+
/**
|
|
14
|
+
* The sheet's header: a `NavigationBar` with the title and items, and anything else that should stay put above the
|
|
15
|
+
* content (`children` — a segmented control, a search hint). On iOS 26 a scrollable sheet's content scrolls under it
|
|
16
|
+
* with the system's scroll edge effect; elsewhere it sits above the content.
|
|
17
|
+
*/
|
|
18
|
+
export type SheetHeaderProps = {
|
|
19
|
+
/** Pinned under the bar. */
|
|
20
|
+
children?: ReactNode;
|
|
21
|
+
/** Light items and title, for a sheet with a dark or image background. */
|
|
22
|
+
inverted?: boolean;
|
|
23
|
+
/** In screen order, from the leading edge. */
|
|
24
|
+
leadingItems?: NavigationBarItem[];
|
|
25
|
+
/** A search field in place of the title (iOS). */
|
|
26
|
+
search?: NavigationBarSearch;
|
|
27
|
+
title?: string;
|
|
28
|
+
/** `center` by default. */
|
|
29
|
+
titleStyle?: SheetTitleStyle;
|
|
30
|
+
/** In screen order, toward the trailing edge — the close item, by convention. */
|
|
31
|
+
trailingItems?: NavigationBarItem[];
|
|
32
|
+
};
|
|
33
|
+
export type SheetRef = {
|
|
34
|
+
dismiss: () => Promise<void>;
|
|
35
|
+
/** Presents at a detent — the first by default. */
|
|
36
|
+
present: (detentIndex?: number) => Promise<void>;
|
|
37
|
+
/** Moves to another detent. */
|
|
38
|
+
resize: (detentIndex: number) => Promise<void>;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* A native sheet — `UISheetPresentationController` on iOS, Material's bottom sheet on Android — with an optional
|
|
42
|
+
* native header (`<Sheet.Header>` as a direct child). The React Native tier: it needs no host, and must not sit inside
|
|
43
|
+
* one; native atoms in its content go in an `Island`.
|
|
44
|
+
*/
|
|
2
45
|
export type SheetProps = {
|
|
46
|
+
/** The sheet's colour. `surface.primary` by default. */
|
|
47
|
+
bg?: ColorToken;
|
|
3
48
|
children: ReactNode;
|
|
4
|
-
|
|
49
|
+
/** Where the sheet can rest, smallest first. `['auto']` — fit the content — by default. */
|
|
50
|
+
detents?: SheetDetent[];
|
|
51
|
+
/** Dims what is behind. On by default. */
|
|
52
|
+
dimmed?: boolean;
|
|
53
|
+
/** A swipe down or a tap outside closes it. On by default. */
|
|
54
|
+
dismissible?: boolean;
|
|
55
|
+
/** It follows the finger between detents. On by default. */
|
|
56
|
+
draggable?: boolean;
|
|
57
|
+
/** Pinned to the bottom, above the content and the keyboard. */
|
|
58
|
+
footer?: ReactElement;
|
|
59
|
+
/** The drag handle. On by default. */
|
|
60
|
+
grabber?: boolean;
|
|
61
|
+
/** The user dismissed it — `visible` going false does not call this. */
|
|
5
62
|
onDismiss: () => void;
|
|
63
|
+
onDetentChange?: (detentIndex: number) => void;
|
|
64
|
+
onPresent?: () => void;
|
|
65
|
+
ref?: Ref<SheetRef>;
|
|
66
|
+
/**
|
|
67
|
+
* The content is a scroll view (`<Sheet.ScrollView>`) the sheet drives: dragging past its top resizes the sheet,
|
|
68
|
+
* and a header scrolls with the iOS 26 edge effect. Off by default — set it with a scrolling body.
|
|
69
|
+
*/
|
|
70
|
+
scrollable?: boolean;
|
|
71
|
+
testID?: string;
|
|
6
72
|
visible: boolean;
|
|
7
73
|
};
|
|
8
|
-
export type
|
|
74
|
+
export type SheetScrollHandler = (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
|
75
|
+
/** What a scroll view inside a sheet needs: the room the header takes, the large title, the scroll listener. */
|
|
76
|
+
export type SheetScrollContextValue = {
|
|
77
|
+
largeTitle: ReactNode;
|
|
78
|
+
onScroll?: SheetScrollHandler;
|
|
79
|
+
topInset: number;
|
|
80
|
+
};
|
|
81
|
+
export type SheetScrollViewProps = Pick<ScrollViewProps, 'keyboardShouldPersistTaps'> & {
|
|
82
|
+
children: ReactNode;
|
|
83
|
+
/** Styles the scrolling content — padding and gap belong here, as with `RnScrollView`. */
|
|
84
|
+
content?: ResponsiveViewSx;
|
|
85
|
+
onScroll?: SheetScrollHandler;
|
|
86
|
+
testID?: string;
|
|
87
|
+
};
|
|
88
|
+
export type SheetComponent = ((props: SheetProps) => ReactNode) & {
|
|
89
|
+
Header: (props: SheetHeaderProps) => ReactNode;
|
|
90
|
+
ScrollView: (props: SheetScrollViewProps) => ReactNode;
|
|
91
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { SheetHeaderProps } from './sheet.types';
|
|
3
|
+
type SheetChildren = {
|
|
4
|
+
content: ReactNode;
|
|
5
|
+
header?: SheetHeaderProps;
|
|
6
|
+
};
|
|
7
|
+
/** Takes `<Sheet.Header>` out of the children: its props become the native header, the rest is the content. */
|
|
8
|
+
export declare const splitSheetChildren: (children: ReactNode) => SheetChildren;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ViewStyle } from 'react-native';
|
|
2
|
+
import type { SheetHeaderProps, SheetScrollContextValue } from './sheet.types';
|
|
3
|
+
type UseSheetHeaderParams = {
|
|
4
|
+
header?: SheetHeaderProps;
|
|
5
|
+
scrollable: boolean;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Turns `<Sheet.Header>`'s props into what the native sheet takes: its header element, how that is laid out, the scroll
|
|
9
|
+
* edge effects, and the context a `<Sheet.ScrollView>` reads. On iOS 26 a scrollable sheet's header is laid over the
|
|
10
|
+
* content (which is then inset by the header's measured height) so the system's soft edge blurs what scrolls under it;
|
|
11
|
+
* everywhere else the header simply sits above the content.
|
|
12
|
+
*/
|
|
13
|
+
export declare const useSheetHeader: ({ header: headerProps, scrollable }: UseSheetHeaderParams) => {
|
|
14
|
+
header: import("react").JSX.Element | undefined;
|
|
15
|
+
headerStyle: ViewStyle | undefined;
|
|
16
|
+
scrollableOptions: {
|
|
17
|
+
readonly bottomScrollEdgeEffect: 'soft';
|
|
18
|
+
readonly topScrollEdgeEffect: 'soft';
|
|
19
|
+
} | undefined;
|
|
20
|
+
scrollContext: SheetScrollContextValue;
|
|
21
|
+
/** Without a scroll view to carry it, a large title simply leads the content. */
|
|
22
|
+
staticLargeTitle: import("react").JSX.Element | null;
|
|
23
|
+
};
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ViewStyle } from 'react-native';
|
|
2
|
+
/**
|
|
3
|
+
* Splits a resolved style for a pressable with an Android ripple. The ripple is drawn as the pressable's foreground,
|
|
4
|
+
* which its own `overflow: hidden` does not clip — so it would spill past rounded corners. A parent clips its
|
|
5
|
+
* children's foregrounds, though: the outer view takes the shape, background, border and outer layout and clips; the
|
|
6
|
+
* pressable inside takes the padding and the content layout and fills it, so the ripple covers the whole card and
|
|
7
|
+
* stops at its corners.
|
|
8
|
+
*/
|
|
9
|
+
export declare const splitForRipple: (style: ViewStyle | undefined) => {
|
|
10
|
+
inner: ViewStyle[];
|
|
11
|
+
outer: ViewStyle;
|
|
12
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ModifierConfig } from '@expo/ui/jetpack-compose/modifiers';
|
|
2
|
+
import { type ResponsiveSx } from '@hzblj/kara-sx';
|
|
3
|
+
/**
|
|
4
|
+
* A Compose stack's modifiers in the order a pressable one needs: its frame, shape and background, then the press,
|
|
5
|
+
* then its padding. Compose clips a `clickable` to what comes after it, so with the padding first the ripple would stop
|
|
6
|
+
* at the padding and a row pressed by its edge would light up only in the middle.
|
|
7
|
+
*/
|
|
8
|
+
export declare const useStackModifiers: (sx: ResponsiveSx, onPress?: () => void, testID?: string) => ModifierConfig[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hzblj/kara-components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "The atoms, molecules, organisms and templates of @hzblj/kara-ui — real SwiftUI and Jetpack Compose views through @expo/ui.",
|
|
5
5
|
"author": "Jan Blazej",
|
|
6
6
|
"license": "MIT",
|
|
@@ -59,13 +59,14 @@
|
|
|
59
59
|
]
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@hzblj/kara-native": "^0.
|
|
63
|
-
"@hzblj/kara-sx": "^0.
|
|
64
|
-
"@hzblj/kara-theme": "^0.
|
|
62
|
+
"@hzblj/kara-native": "^0.4.0",
|
|
63
|
+
"@hzblj/kara-sx": "^0.4.0",
|
|
64
|
+
"@hzblj/kara-theme": "^0.4.0",
|
|
65
65
|
"sf-symbols-typescript": "^2.2.0"
|
|
66
66
|
},
|
|
67
67
|
"peerDependencies": {
|
|
68
68
|
"@expo/ui": "~57.0.0",
|
|
69
|
+
"@lodev09/react-native-true-sheet": ">=3.11.0",
|
|
69
70
|
"@shopify/flash-list": ">=2.0.0",
|
|
70
71
|
"expo": "~57.0.0",
|
|
71
72
|
"expo-haptics": "*",
|
|
@@ -76,6 +77,7 @@
|
|
|
76
77
|
},
|
|
77
78
|
"devDependencies": {
|
|
78
79
|
"@expo/ui": "~57.0.18",
|
|
80
|
+
"@lodev09/react-native-true-sheet": "3.11.18",
|
|
79
81
|
"@shopify/flash-list": "^2.3.2",
|
|
80
82
|
"@storybook/react-native": "^10.6.0",
|
|
81
83
|
"@types/react": "~19.2.18",
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import {Row} from '@expo/ui/jetpack-compose'
|
|
2
|
-
import {clickable, testID as testIdModifier} from '@expo/ui/jetpack-compose/modifiers'
|
|
3
|
-
import {useSxModifiers} from '@hzblj/kara-sx'
|
|
4
2
|
import {spacing} from '@hzblj/kara-theme'
|
|
5
|
-
import {useMemo} from 'react'
|
|
6
3
|
import {useHostGuard} from '../../host-context'
|
|
4
|
+
import {useStackModifiers} from '../../use-stack-modifiers'
|
|
7
5
|
import type {HStackAlign, HStackComponent, HStackProps} from './h-stack.types'
|
|
8
6
|
|
|
9
7
|
const ALIGNMENT = {
|
|
@@ -21,11 +19,7 @@ export const HStack: HStackComponent = ({
|
|
|
21
19
|
...sx
|
|
22
20
|
}: HStackProps) => {
|
|
23
21
|
useHostGuard('HStack')
|
|
24
|
-
const
|
|
25
|
-
const modifiers = useMemo(
|
|
26
|
-
() => [...sxModifiers, ...(testID ? [testIdModifier(testID)] : []), ...(onPress ? [clickable(onPress)] : [])],
|
|
27
|
-
[onPress, sxModifiers, testID]
|
|
28
|
-
)
|
|
22
|
+
const modifiers = useStackModifiers(sx, onPress, testID)
|
|
29
23
|
|
|
30
24
|
return (
|
|
31
25
|
<Row
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import {Column} from '@expo/ui/jetpack-compose'
|
|
2
|
-
import {clickable, testID as testIdModifier} from '@expo/ui/jetpack-compose/modifiers'
|
|
3
|
-
import {useSxModifiers} from '@hzblj/kara-sx'
|
|
4
2
|
import {spacing} from '@hzblj/kara-theme'
|
|
5
|
-
import {useMemo} from 'react'
|
|
6
3
|
import {useHostGuard} from '../../host-context'
|
|
4
|
+
import {useStackModifiers} from '../../use-stack-modifiers'
|
|
7
5
|
import type {VStackAlign, VStackComponent, VStackProps} from './v-stack.types'
|
|
8
6
|
|
|
9
7
|
const ALIGNMENT = {
|
|
@@ -21,11 +19,7 @@ export const VStack: VStackComponent = ({
|
|
|
21
19
|
...sx
|
|
22
20
|
}: VStackProps) => {
|
|
23
21
|
useHostGuard('VStack')
|
|
24
|
-
const
|
|
25
|
-
const modifiers = useMemo(
|
|
26
|
-
() => [...sxModifiers, ...(testID ? [testIdModifier(testID)] : []), ...(onPress ? [clickable(onPress)] : [])],
|
|
27
|
-
[onPress, sxModifiers, testID]
|
|
28
|
-
)
|
|
22
|
+
const modifiers = useStackModifiers(sx, onPress, testID)
|
|
29
23
|
|
|
30
24
|
return (
|
|
31
25
|
<Column
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import {Box} from '@expo/ui/jetpack-compose'
|
|
2
|
-
import {clickable, testID as testIdModifier} from '@expo/ui/jetpack-compose/modifiers'
|
|
3
|
-
import {useSxModifiers} from '@hzblj/kara-sx'
|
|
4
|
-
import {useMemo} from 'react'
|
|
5
2
|
import {useHostGuard} from '../../host-context'
|
|
3
|
+
import {useStackModifiers} from '../../use-stack-modifiers'
|
|
6
4
|
import type {ZStackAlign, ZStackComponent, ZStackProps} from './z-stack.types'
|
|
7
5
|
|
|
8
6
|
const ALIGNMENT = {
|
|
@@ -13,11 +11,7 @@ const ALIGNMENT = {
|
|
|
13
11
|
|
|
14
12
|
export const ZStack: ZStackComponent = ({align = 'center', children, onPress, testID, ...sx}: ZStackProps) => {
|
|
15
13
|
useHostGuard('ZStack')
|
|
16
|
-
const
|
|
17
|
-
const modifiers = useMemo(
|
|
18
|
-
() => [...sxModifiers, ...(testID ? [testIdModifier(testID)] : []), ...(onPress ? [clickable(onPress)] : [])],
|
|
19
|
-
[onPress, sxModifiers, testID]
|
|
20
|
-
)
|
|
14
|
+
const modifiers = useStackModifiers(sx, onPress, testID)
|
|
21
15
|
|
|
22
16
|
return (
|
|
23
17
|
<Box contentAlignment={ALIGNMENT[align]} modifiers={modifiers}>
|
package/src/molecules/index.ts
CHANGED
|
@@ -16,10 +16,11 @@ export const LabeledContent: LabeledContentComponent = ({
|
|
|
16
16
|
icon,
|
|
17
17
|
label,
|
|
18
18
|
onPress,
|
|
19
|
+
px,
|
|
19
20
|
testID,
|
|
20
21
|
value,
|
|
21
22
|
}: LabeledContentProps) => (
|
|
22
|
-
<HStack align="center" onPress={onPress} py="s8" spacing="s12" testID={testID} wide>
|
|
23
|
+
<HStack align="center" onPress={onPress} px={px} py="s8" spacing="s12" testID={testID} wide>
|
|
23
24
|
{icon ? <Icon color="content.secondary" name={icon} size="s20" /> : null}
|
|
24
25
|
<Text variant="body">{label}</Text>
|
|
25
26
|
<Spacer />
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type {SpaceSize} from '@hzblj/kara-theme'
|
|
1
2
|
import type {ReactNode} from 'react'
|
|
2
3
|
import type {IconName} from '../../atoms/icon'
|
|
3
4
|
|
|
@@ -10,6 +11,8 @@ export type LabeledContentProps = {
|
|
|
10
11
|
icon?: IconName
|
|
11
12
|
label: string
|
|
12
13
|
onPress?: () => void
|
|
14
|
+
/** Horizontal padding inside the row — inside its press area too, so Android's ripple reaches the card's edges. */
|
|
15
|
+
px?: SpaceSize
|
|
13
16
|
testID?: string
|
|
14
17
|
value?: string
|
|
15
18
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {Platform} from 'react-native'
|
|
2
|
+
|
|
1
3
|
import {HStack} from '../../atoms/h-stack'
|
|
2
4
|
import {Icon} from '../../atoms/icon'
|
|
3
5
|
import {Spacer} from '../../atoms/spacer'
|
|
@@ -6,10 +8,16 @@ import {VStack} from '../../atoms/v-stack'
|
|
|
6
8
|
import {Glyph} from '../../glyph'
|
|
7
9
|
import type {ListItemComponent, ListItemProps} from './list-item.types'
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Compose measures a row's children in order, so a text column that fills the line leaves the chevron no room. With a
|
|
13
|
+
* weight it is measured last and takes only what is left; SwiftUI shares the line by itself, with a `Spacer`.
|
|
14
|
+
*/
|
|
15
|
+
const TEXT_FILLS_ROW = {weight: 1}
|
|
16
|
+
|
|
17
|
+
export const ListItem: ListItemComponent = ({icon, onPress, px, subtitle, testID, title}: ListItemProps) => (
|
|
18
|
+
<HStack align="center" onPress={onPress} px={px} py="s8" spacing="s12" testID={testID} wide>
|
|
11
19
|
{icon ? <Icon color="content.secondary" name={icon} size="s20" /> : null}
|
|
12
|
-
<VStack spacing="s2">
|
|
20
|
+
<VStack android={TEXT_FILLS_ROW} spacing="s2">
|
|
13
21
|
<Text variant="body">{title}</Text>
|
|
14
22
|
{subtitle ? (
|
|
15
23
|
<Text color="content.secondary" variant="footnote">
|
|
@@ -17,7 +25,7 @@ export const ListItem: ListItemComponent = ({icon, onPress, subtitle, testID, ti
|
|
|
17
25
|
</Text>
|
|
18
26
|
) : null}
|
|
19
27
|
</VStack>
|
|
20
|
-
<Spacer />
|
|
28
|
+
{Platform.OS === 'ios' ? <Spacer /> : null}
|
|
21
29
|
{onPress ? <Glyph color="content.quaternary" name="chevronRight" size="s16" /> : null}
|
|
22
30
|
</HStack>
|
|
23
31
|
)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type {SpaceSize} from '@hzblj/kara-theme'
|
|
1
2
|
import type {ReactNode} from 'react'
|
|
2
3
|
|
|
3
4
|
import type {IconName} from '../../atoms/icon'
|
|
@@ -5,6 +6,11 @@ import type {IconName} from '../../atoms/icon'
|
|
|
5
6
|
export type ListItemProps = {
|
|
6
7
|
icon?: IconName
|
|
7
8
|
onPress?: () => void
|
|
9
|
+
/**
|
|
10
|
+
* Horizontal padding inside the row — inside its press area too, so Android's ripple reaches the card's edges. For
|
|
11
|
+
* rows in a card of your own; a `List` insets its rows by itself.
|
|
12
|
+
*/
|
|
13
|
+
px?: SpaceSize
|
|
8
14
|
subtitle?: string
|
|
9
15
|
testID?: string
|
|
10
16
|
title: string
|