diff --git a/eslint.config.js b/eslint.config.js index 23cb3a392..7f257fad7 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -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": [ diff --git a/example/global.css b/example/global.css new file mode 100644 index 000000000..9431a57e6 --- /dev/null +++ b/example/global.css @@ -0,0 +1,5 @@ +@import "tailwindcss/theme.css" layer(theme); +@import "tailwindcss/preflight.css" layer(base); +@import "tailwindcss/utilities.css"; + +@import "nativewind/theme"; diff --git a/example/index.js b/example/index.js index f5125eeaa..68c7250d1 100644 --- a/example/index.js +++ b/example/index.js @@ -1,4 +1,5 @@ import "react-native-get-random-values"; +import "./global.css"; import { registerRootComponent } from "expo"; import App from "./src/App"; diff --git a/example/metro.config.js b/example/metro.config.js index 124d52af5..627016fbc 100644 --- a/example/metro.config.js +++ b/example/metro.config.js @@ -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/ @@ -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); diff --git a/example/nativewind-env.d.ts b/example/nativewind-env.d.ts new file mode 100644 index 000000000..60b1c7ba5 --- /dev/null +++ b/example/nativewind-env.d.ts @@ -0,0 +1,3 @@ +/// + +// 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. \ No newline at end of file diff --git a/example/package.json b/example/package.json index 56b3213b0..b6dd3ecf8 100644 --- a/example/package.json +++ b/example/package.json @@ -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", @@ -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" } } diff --git a/example/postcss.config.mjs b/example/postcss.config.mjs new file mode 100644 index 000000000..c2ddf7482 --- /dev/null +++ b/example/postcss.config.mjs @@ -0,0 +1,5 @@ +export default { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; diff --git a/example/src/App.tsx b/example/src/App.tsx index 647a3861d..23eaa14be 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -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, diff --git a/example/src/NativeWindExample.tsx b/example/src/NativeWindExample.tsx new file mode 100644 index 000000000..11c2f7a43 --- /dev/null +++ b/example/src/NativeWindExample.tsx @@ -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(null); + + return ( + +
+ +
+ +
+ + + + +
+ +
+ + + Card 1 + + + + Card 2 + + + + + Card 3 + + + +
+ +
+ + + +
+ +
+ + + + + + + + +
+ +
+ +
+ +
+ + { + "## NativeWind\n\nThis is **bold** and _italic_ text styled via `className`.\n\n- Item one\n- Item two\n- Item three" + } + +
+ +
+ +
+ +
+ ( + + + {cellValue} + + + )} + /> +
+ +
+ key} + className="w-100 h-48" + vertical={false} + loop + > + + Slide 1 + + + + Slide 2 + + + + + Slide 3 + + + +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ ); +}; + +export default NativeWindExample; diff --git a/packages/core/src/components/DeckSwiper/DeckSwiperCard.tsx b/packages/core/src/components/DeckSwiper/DeckSwiperCard.tsx index 5e0e43e67..28bada55a 100644 --- a/packages/core/src/components/DeckSwiper/DeckSwiperCard.tsx +++ b/packages/core/src/components/DeckSwiper/DeckSwiperCard.tsx @@ -12,21 +12,24 @@ import { withTheme } from "@draftbit/theme"; export interface DeckSwiperCardProps extends Omit { style?: StyleProp; + className?: string; theme: ReadTheme; } const DeckSwiperCard: React.FC< React.PropsWithChildren -> = ({ style, children, theme, ...rest }) => ( +> = ({ style, className, children, theme, ...rest }) => ( {children} diff --git a/packages/core/src/components/KeyboardAvoidingView.tsx b/packages/core/src/components/KeyboardAvoidingView.tsx index 8f1a6ceb8..57e351c2c 100644 --- a/packages/core/src/components/KeyboardAvoidingView.tsx +++ b/packages/core/src/components/KeyboardAvoidingView.tsx @@ -18,6 +18,7 @@ interface KeyboardAvoidingViewProps extends ViewProps { androidKeyboardVerticalOffset?: number; iosBehavior?: KeyboardAvoidingViewBehavior; iosKeyboardVerticalOffset?: number; + className?: string; } const KeyboardAvoidingView: React.FC = ({ @@ -27,6 +28,7 @@ const KeyboardAvoidingView: React.FC = ({ androidKeyboardVerticalOffset, iosBehavior, iosKeyboardVerticalOffset, + className, ...rest }) => { let behaviorResult: KeyboardAvoidingViewBehavior; @@ -53,6 +55,8 @@ const KeyboardAvoidingView: React.FC = ({ ); diff --git a/packages/core/src/components/LoadingIndicator.tsx b/packages/core/src/components/LoadingIndicator.tsx index 2ebd5a385..ef71f0bdf 100644 --- a/packages/core/src/components/LoadingIndicator.tsx +++ b/packages/core/src/components/LoadingIndicator.tsx @@ -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 { @@ -34,6 +34,7 @@ export enum LoadingIndicatorType { type Props = { style?: StyleProp; + className?: string; color?: string; theme: ReadTheme; type?: LoadingIndicatorType; @@ -61,10 +62,19 @@ const LoadingIndicator: React.FC> = ({ type = LoadingIndicatorType.plane, size, style, + className, ...rest }) => { const SpinnerComponent = SPINNER_COMPONENTS[type]; - return ; + return ( + + + + ); }; export default withTheme(LoadingIndicator); diff --git a/packages/core/src/components/LottieAnimation.tsx b/packages/core/src/components/LottieAnimation.tsx index 6a6224a47..a6326fb5a 100644 --- a/packages/core/src/components/LottieAnimation.tsx +++ b/packages/core/src/components/LottieAnimation.tsx @@ -4,6 +4,7 @@ import LottieView, { AnimationObject } from "lottie-react-native"; type Props = { style?: StyleProp; + className?: string; resizeMode?: "cover" | "contain" | "center"; source: string | AnimationObject | { uri: string }; autoPlay?: boolean; @@ -15,6 +16,7 @@ const LottieAnimation = forwardRef( ( { style, + className, source, autoPlay = true, loop = true, @@ -25,7 +27,12 @@ const LottieAnimation = forwardRef( ref ) => { return ( - + ; + className?: string; }; const childToString = (child?: React.ReactNode): string => { @@ -32,35 +34,42 @@ const childToString = (child?: React.ReactNode): string => { const Markdown: React.FC> = ({ children: childrenProp, style, + className, }) => { const children = React.Children.toArray(childrenProp); const text = children.map(childToString).join(""); const bodyStyle = StyleSheet.flatten(style); - const { textStyles } = extractStyles(bodyStyle); + const { viewStyles, textStyles } = extractStyles(bodyStyle); return ( - //'body' style applies to all markdown components - //@ts-ignore TS does not like the type of this named style for some reason - ( - - {node.children[0].content} - - ), - }} + - {text} - + {/*//'body' style applies to all markdown components*/} + {/*@ts-ignore TS does not like the type of this named style for some reason*/} + ( + + {node.children[0].content} + + ), + }} + > + {text} + + ); }; diff --git a/packages/core/src/components/MediaPlayer/AudioPlayer/AudioPlayerWithInterface.tsx b/packages/core/src/components/MediaPlayer/AudioPlayer/AudioPlayerWithInterface.tsx index 3ee955fec..a6c66835d 100644 --- a/packages/core/src/components/MediaPlayer/AudioPlayer/AudioPlayerWithInterface.tsx +++ b/packages/core/src/components/MediaPlayer/AudioPlayer/AudioPlayerWithInterface.tsx @@ -52,7 +52,7 @@ const AudioPlayerWithInterface = React.forwardRef< : newHeadlessAudioPlayerRef; const { - color = theme.colors.text.strong, + color, fontFamily, fontWeight, fontSize, @@ -67,7 +67,7 @@ const AudioPlayerWithInterface = React.forwardRef< } = StyleSheet.flatten(style || {}); const textStyles = { - color, + color: color ?? (className ? undefined : theme.colors.text.strong), fontFamily, fontWeight, fontSize, @@ -131,7 +131,7 @@ const AudioPlayerWithInterface = React.forwardRef< // @ts-ignore className={className} style={[ - { + !className && { backgroundColor: theme.colors.background.base, borderColor: theme.colors.border.base, }, diff --git a/packages/core/src/components/PinInput/CustomPinInputCell.tsx b/packages/core/src/components/PinInput/CustomPinInputCell.tsx index 2223f5bef..eff633b40 100644 --- a/packages/core/src/components/PinInput/CustomPinInputCell.tsx +++ b/packages/core/src/components/PinInput/CustomPinInputCell.tsx @@ -3,7 +3,8 @@ import { StyleProp, ViewStyle, View, LayoutChangeEvent } from "react-native"; interface CustomPinInputCellProps { style?: StyleProp; - onLayout: (event: LayoutChangeEvent) => void; + className?: string; + onLayout?: (event: LayoutChangeEvent) => void; } /** diff --git a/packages/core/src/components/PinInput/PinInput.tsx b/packages/core/src/components/PinInput/PinInput.tsx index 2c5892318..86edada99 100644 --- a/packages/core/src/components/PinInput/PinInput.tsx +++ b/packages/core/src/components/PinInput/PinInput.tsx @@ -35,6 +35,7 @@ interface PinInputProps extends Omit { focusedBorderWidth?: number; focusedTextColor?: string; style?: StyleProp; + className?: string; theme: ReadTheme; } @@ -55,6 +56,7 @@ const PinInput = React.forwardRef( focusedTextColor, secureTextEntry, style, + className, ...rest }, ref @@ -132,28 +134,31 @@ const PinInput = React.forwardRef( }; return ( -