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
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ module.exports = [
"@typescript-eslint/no-unnecessary-type-constraint": "off",
"react-native/no-inline-styles": "off",
"react-native/no-color-literals": "off",
"react-native/no-raw-text": "off",

// ignore unused vars that start with underscore
"@typescript-eslint/no-unused-vars": [
Expand Down
5 changes: 5 additions & 0 deletions example/global.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/preflight.css" layer(base);
@import "tailwindcss/utilities.css";

@import "nativewind/theme";
1 change: 1 addition & 0 deletions example/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import "react-native-get-random-values";
import "./global.css";
import { registerRootComponent } from "expo";
import App from "./src/App";

Expand Down
3 changes: 2 additions & 1 deletion example/metro.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { getDefaultConfig } = require("expo/metro-config");
const { withNativewind } = require("nativewind/metro");
const path = require("path");

// Follows https://docs.expo.dev/guides/monorepos/
Expand All @@ -20,4 +21,4 @@ config.resolver.nodeModulesPaths = [
// 3. Force Metro to resolve (sub)dependencies only from the `nodeModulesPaths`
config.resolver.disableHierarchicalLookup = true;

module.exports = config;
module.exports = withNativewind(config);
3 changes: 3 additions & 0 deletions example/nativewind-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/// <reference types="react-native-css/types" />

// NOTE: This file should not be edited and should be committed with your source code. It is generated by react-native-css. If you need to move or disable this file, please see the documentation.
10 changes: 9 additions & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@
"expo-font": "~14.0.11",
"expo-splash-screen": "~31.0.13",
"expo-status-bar": "~3.0.9",
"nativewind": "^5.0.0-preview.4",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-native": "0.81.5",
"react-native-css": "^3.0.7",
"react-native-gesture-handler": "~2.28.0",
"react-native-get-random-values": "~1.11.0",
"react-native-reanimated": "~4.1.1",
Expand All @@ -41,9 +43,15 @@
},
"devDependencies": {
"@babel/core": "^7.24.0",
"@tailwindcss/postcss": "^4.3.0",
"@types/react": "~19.1.10",
"@types/react-native": "~0.70.6",
"babel-loader": "8.1.0",
"sharp-cli": "2.1.0"
"postcss": "^8.5.15",
"sharp-cli": "2.1.0",
"tailwindcss": "^4.3.0"
},
"resolutions": {
"lightningcss": "1.30.1"
}
}
5 changes: 5 additions & 0 deletions example/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
plugins: {
"@tailwindcss/postcss": {},
},
};
2 changes: 2 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,10 @@ import LoadingIndicatorExample from "./LoadingIndicatorExample";
import TimerExample from "./TimerExample";
import LottieAnimationExample from "./LottieAnimationExample";
import ExpoImageExample from "./ExpoImageExample";
import NativeWindExample from "./NativeWindExample";

const ROUTES = {
NativeWind: NativeWindExample,
LottieAnimationExample: LottieAnimationExample,
Timer: TimerExample,
LoadingIndicator: LoadingIndicatorExample,
Expand Down
214 changes: 214 additions & 0 deletions example/src/NativeWindExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import React from "react";
import { View, Text } from "react-native";
import {
AudioPlayer,
CircularProgress,
CustomPinInputCell,
CustomPinInputText,
DeckSwiper,
DeckSwiperCard,
LinearProgress,
LoadingIndicator,
LottieAnimation,
Markdown,
PinInput,
Swiper,
SwiperItem,
Timer,
VideoPlayer,
WebView,
} from "@draftbit/ui";
import { LoadingIndicatorType } from "@draftbit/core/lib/typescript/src/components/LoadingIndicator";
import Section, { Container } from "./Section";

const PROGRESS_VALUE = 65;

const NativeWindExample: React.FC<{ theme?: any }> = () => {
const [pinValue, setPinValue] = React.useState("");
const timerRef = React.useRef<any>(null);

return (
<Container style={{}}>
<Section title="AudioPlayer" style={{}}>
<AudioPlayer
className="rounded-2xl bg-gray-100 h-12 border-0 w-100"
source={{
uri: "https://static.draftbit.com/audio/intro-to-draftbit-audio.mp3",
}}
/>
</Section>

<Section title="CircularProgress" style={{}}>
<View className="items-center flex-row">
<CircularProgress className="w-60" value={PROGRESS_VALUE} />
<CircularProgress
className="w-40 ml-20"
color="rgb(99,102,241)"
trackColor="rgb(224,231,255)"
value={PROGRESS_VALUE}
/>
</View>
</Section>

<Section
title="DeckSwiper + DeckSwiperCard"
style={{ paddingBottom: 30 }}
>
<DeckSwiper className="w-100" visibleCardCount={2}>
<DeckSwiperCard className="bg-red-100 p-10 items-center justify-center rounded-xl">
<Text className="text-base font-semibold text-red-700">Card 1</Text>
</DeckSwiperCard>
<DeckSwiperCard className="bg-blue-100 p-10 items-center justify-center rounded-xl">
<Text className="text-base font-semibold text-blue-700">
Card 2
</Text>
</DeckSwiperCard>
<DeckSwiperCard className="bg-green-100 p-10 items-center justify-center rounded-xl">
<Text className="text-base font-semibold text-green-700">
Card 3
</Text>
</DeckSwiperCard>
</DeckSwiper>
</Section>

<Section title="LinearProgress" style={{}}>
<LinearProgress className="w-100" value={PROGRESS_VALUE} />
<LinearProgress
className="w-100 mt-3"
color="rgb(99,102,241)"
trackColor="rgb(224,231,255)"
thickness={12}
value={PROGRESS_VALUE}
/>
<LinearProgress
className="w-150 mt-10"
thickness={8}
trackThickness={8}
dashWidth={12}
dashGap={8}
value={PROGRESS_VALUE}
/>
</Section>

<Section title="LoadingIndicator" style={{}}>
<View className="flex-row">
<LoadingIndicator
className="m-2 border-2 p-4"
type={LoadingIndicatorType.circle}
/>
<LoadingIndicator className="m-2" type={LoadingIndicatorType.wave} />

<LoadingIndicator
className="m-2"
type={LoadingIndicatorType.bounce}
/>

<LoadingIndicator className="m-2" type={LoadingIndicatorType.pulse} />
</View>
</Section>

<Section title="LottieAnimation" style={{}}>
<LottieAnimation
className="w-48 h-48 border-2"
source={require("./assets/lottie_animation_example.json")}
/>
</Section>

<Section title="Markdown" style={{}}>
<Markdown className="w-200">
{
"## NativeWind\n\nThis is **bold** and _italic_ text styled via `className`.\n\n- Item one\n- Item two\n- Item three"
}
</Markdown>
</Section>

<Section title="PinInput (default)" style={{}}>
<PinInput
className="border-2 w-100"
value={pinValue}
onChangeText={setPinValue}
/>
</Section>

<Section
title="PinInput (CustomPinInputCell + CustomPinInputText)"
style={{}}
>
<PinInput
value={pinValue}
onChangeText={setPinValue}
className="w-100"
renderItem={({ cellValue, isFocused }) => (
<CustomPinInputCell
className={`w-14 h-14 mx-1.5 rounded-xl bg-white items-center justify-center ${
isFocused ? "border-2 border-red-500" : "border border-gray-300"
}`}
>
<CustomPinInputText
className={`text-xl font-bold ${
isFocused ? "text-indigo-500" : "text-gray-800"
}`}
isFocused={isFocused}
>
{cellValue}
</CustomPinInputText>
</CustomPinInputCell>
)}
/>
</Section>

<Section title="Swiper + SwiperItem" style={{}}>
<Swiper
keyExtractor={(key) => key}
className="w-100 h-48"
vertical={false}
loop
>
<SwiperItem className="items-center justify-center bg-red-100">
<Text className="text-lg font-semibold text-red-700">Slide 1</Text>
</SwiperItem>
<SwiperItem className="items-center justify-center bg-indigo-100">
<Text className="text-lg font-semibold text-indigo-700">
Slide 2
</Text>
</SwiperItem>
<SwiperItem className="items-center justify-center bg-green-100">
<Text className="text-lg font-semibold text-green-700">
Slide 3
</Text>
</SwiperItem>
</Swiper>
</Section>

<Section title="Timer" style={{}}>
<Timer
ref={timerRef}
className="text-5xl text-red-800"
initialTime={0}
format="mm:ss"
countDirection="up"
/>
</Section>

<Section title="VideoPlayer" style={{}}>
<VideoPlayer
className="w-100 h-56 rounded-xl overflow-hidden"
source={{
uri: "http://static.draftbit.com/videos/intro-to-draftbit.mp4",
}}
useNativeControls
resizeMode="cover"
/>
</Section>

<Section title="WebView" style={{}}>
<WebView
className="w-200 h-100 rounded-xl overflow-hidden"
source={{ uri: "https://docs.expo.io/" }}
/>
</Section>
</Container>
);
};

export default NativeWindExample;
7 changes: 5 additions & 2 deletions packages/core/src/components/DeckSwiper/DeckSwiperCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,24 @@ import { withTheme } from "@draftbit/theme";
export interface DeckSwiperCardProps
extends Omit<ViewProps, "style" | "children"> {
style?: StyleProp<ViewStyle>;
className?: string;
theme: ReadTheme;
}

const DeckSwiperCard: React.FC<
React.PropsWithChildren<DeckSwiperCardProps>
> = ({ style, children, theme, ...rest }) => (
> = ({ style, className, children, theme, ...rest }) => (
<View
style={[
styles.card,
{
!className && {
backgroundColor: theme.colors.background.base,
borderColor: theme.colors.border.base,
},
Comment on lines +25 to 28
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Conditionally omitting the default background and border styles when className is present is highly fragile. If a developer passes a className for an unrelated style (such as padding or margins, e.g., className="p-4"), the component will completely lose its default background and border colors. Always include the default styles in the style array; NativeWind's compiled styles from className will naturally override these defaults because they are merged/appended at the end of the style array.

Suggested change
!className && {
backgroundColor: theme.colors.background.base,
borderColor: theme.colors.border.base,
},
{
backgroundColor: theme.colors.background.base,
borderColor: theme.colors.border.base,
},

style,
]}
// @ts-ignore
className={className}
{...rest}
>
{children}
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/components/KeyboardAvoidingView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface KeyboardAvoidingViewProps extends ViewProps {
androidKeyboardVerticalOffset?: number;
iosBehavior?: KeyboardAvoidingViewBehavior;
iosKeyboardVerticalOffset?: number;
className?: string;
}

const KeyboardAvoidingView: React.FC<KeyboardAvoidingViewProps> = ({
Expand All @@ -27,6 +28,7 @@ const KeyboardAvoidingView: React.FC<KeyboardAvoidingViewProps> = ({
androidKeyboardVerticalOffset,
iosBehavior,
iosKeyboardVerticalOffset,
className,
...rest
}) => {
let behaviorResult: KeyboardAvoidingViewBehavior;
Expand All @@ -53,6 +55,8 @@ const KeyboardAvoidingView: React.FC<KeyboardAvoidingViewProps> = ({
<KeyboardAvoidingViewComponent
behavior={behaviorResult ?? undefined}
keyboardVerticalOffset={keyboardVerticalOffsetResult}
// @ts-ignore
className={className}
{...rest}
/>
);
Expand Down
14 changes: 12 additions & 2 deletions packages/core/src/components/LoadingIndicator.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from "react";
import { StyleProp, ViewStyle } from "react-native";
import { View, StyleProp, ViewStyle } from "react-native";
import { withTheme } from "@draftbit/theme";
import type { ReadTheme } from "@draftbit/theme";
import {
Expand Down Expand Up @@ -34,6 +34,7 @@ export enum LoadingIndicatorType {

type Props = {
style?: StyleProp<ViewStyle>;
className?: string;
color?: string;
theme: ReadTheme;
type?: LoadingIndicatorType;
Expand Down Expand Up @@ -61,10 +62,19 @@ const LoadingIndicator: React.FC<React.PropsWithChildren<Props>> = ({
type = LoadingIndicatorType.plane,
size,
style,
className,
...rest
}) => {
const SpinnerComponent = SPINNER_COMPONENTS[type];
return <SpinnerComponent size={size} color={color} style={style} {...rest} />;
return (
<View
style={style}
// @ts-ignore
className={className}
>
<SpinnerComponent size={size} color={color} {...rest} />
</View>
);
};

export default withTheme(LoadingIndicator);
Loading