From 917775ee073bc089cb58ab476f958f0b2aeda40d Mon Sep 17 00:00:00 2001 From: Jovanni Lo Date: Fri, 26 Dec 2025 00:52:40 +0800 Subject: [PATCH] feat: add elevation prop for Android and Web (#355) * feat(android): add elevation prop * feat(web): add elevation prop support --- .../com/lodev09/truesheet/TrueSheetView.kt | 4 +++ .../truesheet/TrueSheetViewController.kt | 7 +++++ .../lodev09/truesheet/TrueSheetViewManager.kt | 5 ++++ .../core/TrueSheetBottomSheetView.kt | 8 ++++++ docs/docs/reference/01-configuration.mdx | 10 ++++++- src/TrueSheet.types.ts | 9 ++++++ src/TrueSheet.web.tsx | 28 +++++++++++++++++-- src/fabric/TrueSheetViewNativeComponent.ts | 1 + 8 files changed, 69 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt b/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt index 2e8d043..bb7978b 100644 --- a/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +++ b/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt @@ -222,6 +222,10 @@ class TrueSheetView(private val reactContext: ThemedReactContext) : viewController.grabberOptions = options } + fun setSheetElevation(elevation: Float) { + viewController.sheetElevation = elevation + } + fun setDetents(newDetents: MutableList) { viewController.detents = newDetents } diff --git a/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt b/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt index 5c8bd0e..f62222b 100644 --- a/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +++ b/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt @@ -178,6 +178,12 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) : if (isPresented) sheetView?.setupBackground() } + override var sheetElevation: Float = -1f + set(value) { + field = value + if (isPresented) sheetView?.setupElevation() + } + var dismissible: Boolean = true set(value) { field = value @@ -647,6 +653,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) : setupDimmedBackground(currentDetentIndex) setupKeyboardObserver() sheet.setupBackground() + sheet.setupElevation() sheet.setupGrabber() if (shouldAnimatePresent) { diff --git a/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt b/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt index bf638ba..58ee614 100644 --- a/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +++ b/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt @@ -198,6 +198,11 @@ class TrueSheetViewManager : // iOS-specific prop - no-op on Android } + @ReactProp(name = "elevation", defaultDouble = -1.0) + override fun setElevation(view: TrueSheetView, elevation: Double) { + view.setSheetElevation(elevation.toFloat()) + } + companion object { const val REACT_CLASS = "TrueSheetView" const val TAG_NAME = "TrueSheet" diff --git a/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt b/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt index a2d51a2..45d2999 100644 --- a/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +++ b/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt @@ -18,6 +18,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior interface TrueSheetBottomSheetViewDelegate { val isTopmostSheet: Boolean val sheetCornerRadius: Float + val sheetElevation: Float val sheetBackgroundColor: Int? val grabber: Boolean val grabberOptions: GrabberOptions? @@ -37,6 +38,7 @@ class TrueSheetBottomSheetView(private val reactContext: ThemedReactContext) : F private const val GRABBER_TAG = "TrueSheetGrabber" private const val DEFAULT_CORNER_RADIUS = 16f // dp private const val DEFAULT_MAX_WIDTH = 640 // dp + private const val DEFAULT_ELEVATION = 4f // dp } // ============================================================================= @@ -138,6 +140,12 @@ class TrueSheetBottomSheetView(private val reactContext: ThemedReactContext) : F } } + fun setupElevation() { + val value = delegate?.sheetElevation ?: DEFAULT_ELEVATION + val effectiveElevation = if (value < 0) DEFAULT_ELEVATION else value + elevation = effectiveElevation.dpToPx() + } + // ============================================================================= // MARK: - Grabber // ============================================================================= diff --git a/docs/docs/reference/01-configuration.mdx b/docs/docs/reference/01-configuration.mdx index 1895a29..7f9c5ee 100644 --- a/docs/docs/reference/01-configuration.mdx +++ b/docs/docs/reference/01-configuration.mdx @@ -66,9 +66,17 @@ The sheet corner radius. | `number` | _system default_ | ✅ | ✅ | ✅ | :::info -When not provided, iOS uses the device's native corner radius automatically, while Android defaults to `28` (following Material Design 3 guidelines). +When not provided, iOS uses the device's native corner radius automatically, while Android defaults to `16` (following Material Design 3 guidelines). ::: +## `elevation` + +The elevation (shadow depth) of the sheet. + +| Type | Default | 🍎 | 🤖 | 🌐 | +| - | - | - | - | - | +| `number` | `4` | | ✅ | ✅ | + ## `maxHeight` Overrides `"large"` or `"100%"` height. diff --git a/src/TrueSheet.types.ts b/src/TrueSheet.types.ts index 2f1f0e8..ab2db87 100644 --- a/src/TrueSheet.types.ts +++ b/src/TrueSheet.types.ts @@ -399,6 +399,15 @@ export interface TrueSheetProps extends ViewProps { */ insetAdjustment?: InsetAdjustment; + /** + * The elevation (shadow depth) of the sheet. + * + * @platform android + * @platform web + * @default 4 + */ + elevation?: number; + /** * A component that is fixed at the top of the Sheet content. * Useful for search bars, titles, or other header content. diff --git a/src/TrueSheet.web.tsx b/src/TrueSheet.web.tsx index a537418..f8e3fb4 100644 --- a/src/TrueSheet.web.tsx +++ b/src/TrueSheet.web.tsx @@ -48,11 +48,30 @@ import type { } from './TrueSheet.types'; const DEFAULT_CORNER_RADIUS = 16; +const DEFAULT_ELEVATION = 4; const DEFAULT_GRABBER_COLOR = 'rgba(0, 0, 0, 0.3)'; const DEFAULT_GRABBER_WIDTH = 32; const DEFAULT_GRABBER_HEIGHT = 4; +/** + * Converts elevation to CSS box-shadow based on Material Design 3 elevation system. + * Uses a combination of ambient and key shadows for realistic depth. + */ +const getElevationShadow = (elevation: number): string => { + if (elevation <= 0) return 'none'; + + const ambientY = elevation * 0.5; + const ambientBlur = elevation * 1.5; + const ambientOpacity = 0.08 + elevation * 0.01; + + const keyY = elevation; + const keyBlur = elevation * 2; + const keyOpacity = 0.12 + elevation * 0.02; + + return `0px ${ambientY}px ${ambientBlur}px rgba(0, 0, 0, ${ambientOpacity}), 0px ${keyY}px ${keyBlur}px rgba(0, 0, 0, ${keyOpacity})`; +}; + const renderSlot = (slot: TrueSheetProps['header'] | TrueSheetProps['footer']) => { if (!slot) return null; if (isValidElement(slot)) return slot; @@ -72,6 +91,7 @@ export const TrueSheet = forwardRef((props, ref) = initialDetentIndex = -1, backgroundColor = '#ffffff', cornerRadius = DEFAULT_CORNER_RADIUS, + elevation = DEFAULT_ELEVATION, grabber = true, grabberOptions, maxHeight, @@ -425,7 +445,12 @@ export const TrueSheet = forwardRef((props, ref) = const sharedProps = { style: [ styles.root, - { backgroundColor, borderTopLeftRadius: cornerRadius, borderTopRightRadius: cornerRadius }, + { + backgroundColor, + borderTopLeftRadius: cornerRadius, + borderTopRightRadius: cornerRadius, + boxShadow: getElevationShadow(elevation), + }, ], index: snapIndex, enablePanDownToClose: dismissible, @@ -469,7 +494,6 @@ export const TrueSheet = forwardRef((props, ref) = const styles = StyleSheet.create({ root: { overflow: 'hidden', - boxShadow: '0px -2px 16px 0px rgba(9, 10, 9, 0.08)', }, handle: { position: 'absolute', diff --git a/src/fabric/TrueSheetViewNativeComponent.ts b/src/fabric/TrueSheetViewNativeComponent.ts index 0ef123c..edc4cbf 100644 --- a/src/fabric/TrueSheetViewNativeComponent.ts +++ b/src/fabric/TrueSheetViewNativeComponent.ts @@ -41,6 +41,7 @@ export interface NativeProps extends ViewProps { // Number properties - use 0 as default to avoid nil insertion maxHeight?: WithDefault; cornerRadius?: WithDefault; + elevation?: WithDefault; // Color properties backgroundColor?: ColorValue;