diff --git a/.github/workflows/build-and-publish-alpha.yaml b/.github/workflows/build-and-publish-alpha.yaml index 440f627168..ab597786bf 100644 --- a/.github/workflows/build-and-publish-alpha.yaml +++ b/.github/workflows/build-and-publish-alpha.yaml @@ -67,7 +67,7 @@ jobs: - name: Build run: | - pnpm --filter @factorialco/f0-react-core build + pnpm --filter @factorialco/f0-core build pnpm --filter @factorialco/f0-react-native build - name: Publish to release branch npm/alpha-pr-${{ github.event.pull_request.number }}-react-native @@ -100,7 +100,7 @@ jobs: - name: Build run: | - pnpm --filter @factorialco/f0-react-core build + pnpm --filter @factorialco/f0-core build - name: Publish to release branch npm/alpha-pr-${{ github.event.pull_request.number }}-core uses: ./.github/actions/publish-release-branch diff --git a/.npmrc b/.npmrc index 07d48bf64c..bf4cd20407 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ -side-effects-cache = false \ No newline at end of file +side-effects-cache = false +node-linker=hoisted \ No newline at end of file diff --git a/.tool-versions b/.tool-versions index 877b26309a..604be079d3 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -nodejs 22.5.1 v22.5.1 +nodejs 22.14.0 diff --git a/lefthook.yml b/lefthook.yml index a1c6db36f1..0abd36c5a5 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,11 +1,11 @@ # Validate commit messages commit-msg: commands: - "lint commit message": + 'lint commit message': run: pnpm exec commitlint --edit {1} pre-commit: commands: format: - glob: "**/*.{js,jsx,ts,tsx,json,css,md,yml,yaml,md}" + glob: '**/*.{js,jsx,ts,tsx,json,css,md,yml,yaml,md}' run: pnpm exec prettier {staged_files} --write stage_fixed: true diff --git a/package.json b/package.json index 5f4d6de3a4..2b8d885f5f 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,10 @@ }, "pnpm": { "overrides": { - "esm-env": "npm:esm-env-runtime@^0.1.1" + "esm-env": "npm:esm-env-runtime@^0.1.1", + "@types/react": "19.0.14", + "react": "19.0.0", + "react-dom": "19.0.0" } } } diff --git a/packages/react-native-playground/.gitignore b/packages/react-native-playground/.gitignore deleted file mode 100644 index 0b37b6eb4f..0000000000 --- a/packages/react-native-playground/.gitignore +++ /dev/null @@ -1,41 +0,0 @@ -# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files - -# dependencies -node_modules/ - -# Expo -.expo/ -dist/ -web-build/ - -# Native -*.orig.* -*.jks -*.p8 -*.p12 -*.key -*.mobileprovision - -# Metro -.metro-health-check* - -# debug -npm-debug.* -yarn-debug.* -yarn-error.* - -# macOS -.DS_Store -*.pem - -# local env files -.env*.local - -# typescript -*.tsbuildinfo - -# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb -# The following patterns were generated by expo-cli - -expo-env.d.ts -# @end expo-cli \ No newline at end of file diff --git a/packages/react-native-playground/.npmrc b/packages/react-native-playground/.npmrc deleted file mode 100644 index d279723f45..0000000000 --- a/packages/react-native-playground/.npmrc +++ /dev/null @@ -1,2 +0,0 @@ -node-linker=hoisted -enable-pre-post-scripts=true \ No newline at end of file diff --git a/packages/react-native-playground/.storybook/index.ts b/packages/react-native-playground/.storybook/index.ts deleted file mode 100644 index 6eea760617..0000000000 --- a/packages/react-native-playground/.storybook/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; -import { view } from './storybook.requires'; - -const StorybookUIRoot = view.getStorybookUI({ - storage: { - getItem: AsyncStorage.getItem, - setItem: AsyncStorage.setItem, - }, -}); - -export default StorybookUIRoot; diff --git a/packages/react-native-playground/.storybook/stories/ExampleComponent/ExampleComponent.stories.tsx b/packages/react-native-playground/.storybook/stories/ExampleComponent/ExampleComponent.stories.tsx deleted file mode 100644 index 6cfae4c8f7..0000000000 --- a/packages/react-native-playground/.storybook/stories/ExampleComponent/ExampleComponent.stories.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from "react"; -import { View } from "react-native"; -import type { Meta, StoryObj } from "@storybook/react"; -import { ExampleComponent } from "@factorialco/f0-react-native"; - -const meta = { - title: "Components/ExampleComponent", - component: ExampleComponent, - argTypes: { - text: { control: "text" }, - }, - args: { - text: "Hello World", - }, - decorators: [ - (Story) => ( - - - - ), - ], -} satisfies Meta; - -export default meta; - -type Story = StoryObj; - -export const Default: Story = {}; - -export const CustomText: Story = { - args: { - text: "Hello from F0!", - }, -}; - -export const LongText: Story = { - args: { - text: "This is a much longer text that demonstrates how the component handles text that might wrap to multiple lines.", - }, -}; diff --git a/packages/react-native-playground/README.md b/packages/react-native-playground/README.md deleted file mode 100644 index 44d05e0042..0000000000 --- a/packages/react-native-playground/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Starter base - -A starting point to help you set up your project quickly and use the common components provided by `react-native-reusables`. The idea is to make it easier for you to get started. - -## Features - -- NativeWind v4 -- Dark and light mode - - Android Navigation Bar matches mode - - Persistent mode -- Common components - - ThemeToggle, Avatar, Button, Card, Progress, Text, Tooltip - -starter-base-template diff --git a/packages/react-native-playground/app/+not-found.tsx b/packages/react-native-playground/app/+not-found.tsx deleted file mode 100644 index 08c97b6bbf..0000000000 --- a/packages/react-native-playground/app/+not-found.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Link, Stack } from 'expo-router'; -import { View } from 'react-native'; -import { Text } from '~/components/ui/text'; - -export default function NotFoundScreen() { - return ( - <> - - - This screen doesn't exist. - - - Go to home screen! - - - - ); -} diff --git a/packages/react-native-playground/assets/images/icon.png b/packages/react-native-playground/assets/images/icon.png deleted file mode 100644 index a0b1526fc7..0000000000 Binary files a/packages/react-native-playground/assets/images/icon.png and /dev/null differ diff --git a/packages/react-native-playground/babel.config.js b/packages/react-native-playground/babel.config.js deleted file mode 100644 index 1d9669800a..0000000000 --- a/packages/react-native-playground/babel.config.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = function (api) { - api.cache(true); - return { - presets: [ - ["babel-preset-expo", { jsxImportSource: "nativewind" }], - "nativewind/babel", - ], - plugins: ["react-native-reanimated/plugin"], - }; -}; diff --git a/packages/react-native-playground/components/Avatars/ModuleAvatar/ModuleAvatar.mdx b/packages/react-native-playground/components/Avatars/ModuleAvatar/ModuleAvatar.mdx deleted file mode 100644 index ca4b8bb481..0000000000 --- a/packages/react-native-playground/components/Avatars/ModuleAvatar/ModuleAvatar.mdx +++ /dev/null @@ -1,59 +0,0 @@ -import { Meta, Primary, Controls, Story, Canvas } from "@storybook/blocks"; -import * as ModuleAvatarStories from "./ModuleAvatar.stories"; - - - -# ModuleAvatar - -The ModuleAvatar component is used to display a module icon with a consistent background shape and styling. -It's commonly used in navigation and module identification throughout the application. - -## Features - -- Three size variants: small (sm), medium (md), and large (lg) -- Consistent squircle background shape with gradient -- Supports all module icons from the design system -- Accessible with proper ARIA roles - -## Usage - -```tsx -import { ModuleAvatar, ModuleIcons } from "@factorialco/f0-react-native"; - -function MyComponent() { - return ; -} -``` - -## Examples - -### Default - -The default ModuleAvatar uses the medium size and requires an icon prop. - - - - -### Size Variants - -ModuleAvatar comes in three sizes to accommodate different use cases: - - - -### All Available Modules - -Here's a showcase of all available module icons that can be used with ModuleAvatar: - - - -## Props - -- `icon` (required): A module icon from the ModuleIcons collection -- `size` (optional): "sm" | "md" | "lg" (defaults to "md") - -## Accessibility - -The component includes proper accessibility attributes: - -- Uses accessibilityRole="image" -- Maintains good color contrast for icon visibility diff --git a/packages/react-native-playground/components/Icon/index.stories.tsx b/packages/react-native-playground/components/Icon/index.stories.tsx deleted file mode 100644 index 11246d6be5..0000000000 --- a/packages/react-native-playground/components/Icon/index.stories.tsx +++ /dev/null @@ -1,260 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { ScrollView } from "react-native"; -import { View } from "react-native"; -import { Text } from "react-native"; -import { TextInput } from "react-native"; -import { TouchableOpacity } from "react-native"; -import type { Meta, StoryObj } from "@storybook/react"; -import { Icon, AppIcons, ModuleIcons } from "@factorialco/f0-react-native"; -import { type IconColorName } from "@factorialco/f0-react-native/src/lib/colors"; - -const meta = { - title: "Components/Icon", - component: Icon, - argTypes: { - size: { - control: "select", - options: ["xs", "sm", "md", "lg", "xl"], - }, - }, - args: { - size: "md", - icon: AppIcons.ChevronDown, - }, - decorators: [ - (Story) => ( - - - - ), - ], -} satisfies Meta; - -export default meta; - -type Story = StoryObj; - -// Helper components with proper TypeScript types -interface IconDisplayProps { - icon: any; // Using any to avoid import errors - name: string; -} - -interface SizeVariantProps extends IconDisplayProps { - size: "xs" | "sm" | "md" | "lg" | "xl"; -} - -interface StyledIconDisplayProps extends IconDisplayProps { - color: IconColorName; -} - -const IconDisplay = ({ icon, name }: IconDisplayProps) => ( - - - {name} - -); - -const SizeVariant = ({ icon, name, size }: SizeVariantProps) => ( - - - {name} - -); - -const StyledIconDisplay = ({ icon, name, color }: StyledIconDisplayProps) => ( - - - {name} - -); - -type IconType = "app" | "module"; - -export const IconsShowcase: Story = { - args: { - icon: AppIcons.Archive, - }, - render: () => { - const [searchTerm, setSearchTerm] = useState(""); - const [selectedType, setSelectedType] = useState("app"); - const [appIconList, setAppIconList] = useState< - Array<{ name: string; icon: any }> - >([]); - const [moduleIconList, setModuleIconList] = useState< - Array<{ name: string; icon: any }> - >([]); - - // Generate icon lists on component mount - useEffect(() => { - // Create array of app icons - const appIcons = Object.entries(AppIcons).map(([name, icon]) => ({ - name, - icon, - })); - - // Create array of module icons - const modIcons = Object.entries(ModuleIcons).map(([name, icon]) => ({ - name, - icon, - })); - - setAppIconList(appIcons); - setModuleIconList(modIcons); - }, []); - - // Filter icons based on search term and selected type - const filteredIcons = ( - selectedType === "app" ? appIconList : moduleIconList - ).filter((item) => - item.name.toLowerCase().includes(searchTerm.toLowerCase()), - ); - - const TabButton = ({ - type, - label, - count, - }: { - type: IconType; - label: string; - count: number; - }) => ( - - setSelectedType(type)} - className={`py-2 px-4 ${ - selectedType === type ? "bg-f1-icon-info" : "bg-f1-icon-secondary" - }`} - > - - {label} ({count}) - - - {selectedType === type && } - - ); - - return ( - - - - Search Icons - - - - - - item.name.toLowerCase().includes(searchTerm.toLowerCase()), - ).length - } - /> - - item.name.toLowerCase().includes(searchTerm.toLowerCase()), - ).length - } - /> - - - - {filteredIcons.length > 0 ? ( - - {filteredIcons.map((item) => ( - - ))} - - ) : ( - - - No icons found matching "{searchTerm}" - - - )} - - ); - }, -}; - -export const SizeVariants: Story = { - args: { - icon: AppIcons.ChevronDown, - }, - render: () => ( - - Size Variants - - - - - - - - - - - - - - - - - - - - - - - - - ), -}; - -export const Styling: Story = { - args: { - icon: AppIcons.Heart, - className: "text-f1-icon-critical", - }, - render: () => ( - - Styling Icons - - - - - - - - ), -}; diff --git a/packages/react-native-playground/components/OneChip/index.stories.tsx b/packages/react-native-playground/components/OneChip/index.stories.tsx deleted file mode 100644 index 936e789d82..0000000000 --- a/packages/react-native-playground/components/OneChip/index.stories.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { OneChip } from "@factorialco/f0-react-native"; -import type { Meta, StoryFn, StoryObj } from "@storybook/react"; -import { useState } from "react"; -import { AppIcons } from "@factorialco/f0-react-native"; -import { View } from "react-native"; - -const meta = { - component: OneChip, - title: "Components/Chip", - parameters: { - layout: "centered", - }, - decorators: [ - (Story: StoryFn) => ( - - - - ), - ], - argTypes: { - label: { - description: "The label of the chip", - }, - variant: { - description: "The variant of the chip", - options: ["default", "selected"], - control: { type: "select" }, - }, - icon: { - control: "select", - options: Object.keys(AppIcons), - mapping: AppIcons, - description: "If defined, an icon will be displayed in the chip", - }, - onClose: { - description: - "If defined, the close icon will be displayed and the chip will be clickable", - }, - }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { - label: "Label", - variant: "default", - }, -}; - -export const WithClose: Story = { - args: { - label: "Label", - variant: "default", - }, - render: () => { - const [chips, setChips] = useState([ - { id: 1, label: "First Chip" }, - { id: 2, label: "Second Chip" }, - { id: 3, label: "Third Chip" }, - ]); - - const handleClose = (id: number) => { - setChips((prevChips) => prevChips.filter((chip) => chip.id !== id)); - }; - - return ( - - {chips.map((chip) => ( - handleClose(chip.id)} - /> - ))} - - ); - }, -}; - -export const WithIcon: Story = { - args: { - label: "Label", - icon: AppIcons.Placeholder, - }, -}; - -export const SelectedWithClose: Story = { - args: { - label: "Label", - variant: "selected", - }, - render: () => { - const [chips, setChips] = useState([ - { id: 1, label: "First Chip" }, - { id: 2, label: "Second Chip" }, - { id: 3, label: "Third Chip" }, - ]); - - const handleClose = (id: number) => { - setChips((prevChips) => prevChips.filter((chip) => chip.id !== id)); - }; - - return ( - - {chips.map((chip) => ( - handleClose(chip.id)} - /> - ))} - - ); - }, -}; diff --git a/packages/react-native-playground/components/ThemeToggle.tsx b/packages/react-native-playground/components/ThemeToggle.tsx deleted file mode 100644 index c89471f9cb..0000000000 --- a/packages/react-native-playground/components/ThemeToggle.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Pressable, View } from 'react-native'; -import { setAndroidNavigationBar } from '~/lib/android-navigation-bar'; -import { MoonStar } from '~/lib/icons/MoonStar'; -import { Sun } from '~/lib/icons/Sun'; -import { useColorScheme } from '~/lib/useColorScheme'; -import { cn } from '~/lib/utils'; - -export function ThemeToggle() { - const { isDarkColorScheme, setColorScheme } = useColorScheme(); - - function toggleColorScheme() { - const newTheme = isDarkColorScheme ? 'light' : 'dark'; - setColorScheme(newTheme); - setAndroidNavigationBar(newTheme); - } - - return ( - - {({ pressed }) => ( - - {isDarkColorScheme ? ( - - ) : ( - - )} - - )} - - ); -} diff --git a/packages/react-native-playground/components/ui/avatar.tsx b/packages/react-native-playground/components/ui/avatar.tsx deleted file mode 100644 index b2343d7155..0000000000 --- a/packages/react-native-playground/components/ui/avatar.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import * as AvatarPrimitive from '@rn-primitives/avatar'; -import * as React from 'react'; -import { cn } from '~/lib/utils'; - -const AvatarPrimitiveRoot = AvatarPrimitive.Root; -const AvatarPrimitiveImage = AvatarPrimitive.Image; -const AvatarPrimitiveFallback = AvatarPrimitive.Fallback; - -const Avatar = React.forwardRef( - ({ className, ...props }, ref) => ( - - ) -); -Avatar.displayName = AvatarPrimitiveRoot.displayName; - -const AvatarImage = React.forwardRef( - ({ className, ...props }, ref) => ( - - ) -); -AvatarImage.displayName = AvatarPrimitiveImage.displayName; - -const AvatarFallback = React.forwardRef( - ({ className, ...props }, ref) => ( - - ) -); -AvatarFallback.displayName = AvatarPrimitiveFallback.displayName; - -export { Avatar, AvatarFallback, AvatarImage }; diff --git a/packages/react-native-playground/components/ui/button.tsx b/packages/react-native-playground/components/ui/button.tsx deleted file mode 100644 index 5e0e4a0975..0000000000 --- a/packages/react-native-playground/components/ui/button.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { cva, type VariantProps } from 'class-variance-authority'; -import * as React from 'react'; -import { Pressable } from 'react-native'; -import { TextClassContext } from '~/components/ui/text'; -import { cn } from '~/lib/utils'; - -const buttonVariants = cva( - 'group flex items-center justify-center rounded-md web:ring-offset-background web:transition-colors web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2', - { - variants: { - variant: { - default: 'bg-primary web:hover:opacity-90 active:opacity-90', - destructive: 'bg-destructive web:hover:opacity-90 active:opacity-90', - outline: - 'border border-input bg-background web:hover:bg-accent web:hover:text-accent-foreground active:bg-accent', - secondary: 'bg-secondary web:hover:opacity-80 active:opacity-80', - ghost: 'web:hover:bg-accent web:hover:text-accent-foreground active:bg-accent', - link: 'web:underline-offset-4 web:hover:underline web:focus:underline ', - }, - size: { - default: 'h-10 px-4 py-2 native:h-12 native:px-5 native:py-3', - sm: 'h-9 rounded-md px-3', - lg: 'h-11 rounded-md px-8 native:h-14', - icon: 'h-10 w-10', - }, - }, - defaultVariants: { - variant: 'default', - size: 'default', - }, - } -); - -const buttonTextVariants = cva( - 'web:whitespace-nowrap text-sm native:text-base font-medium text-foreground web:transition-colors', - { - variants: { - variant: { - default: 'text-primary-foreground', - destructive: 'text-destructive-foreground', - outline: 'group-active:text-accent-foreground', - secondary: 'text-secondary-foreground group-active:text-secondary-foreground', - ghost: 'group-active:text-accent-foreground', - link: 'text-primary group-active:underline', - }, - size: { - default: '', - sm: '', - lg: 'native:text-lg', - icon: '', - }, - }, - defaultVariants: { - variant: 'default', - size: 'default', - }, - } -); - -type ButtonProps = React.ComponentPropsWithoutRef & - VariantProps; - -const Button = React.forwardRef, ButtonProps>( - ({ className, variant, size, ...props }, ref) => { - return ( - - - - ); - } -); -Button.displayName = 'Button'; - -export { Button, buttonTextVariants, buttonVariants }; -export type { ButtonProps }; diff --git a/packages/react-native-playground/components/ui/card.tsx b/packages/react-native-playground/components/ui/card.tsx deleted file mode 100644 index 9f190c290e..0000000000 --- a/packages/react-native-playground/components/ui/card.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import type { TextRef, ViewRef } from '@rn-primitives/types'; -import * as React from 'react'; -import { Text, TextProps, View, ViewProps } from 'react-native'; -import { TextClassContext } from '~/components/ui/text'; -import { cn } from '~/lib/utils'; - -const Card = React.forwardRef(({ className, ...props }, ref) => ( - -)); -Card.displayName = 'Card'; - -const CardHeader = React.forwardRef(({ className, ...props }, ref) => ( - -)); -CardHeader.displayName = 'CardHeader'; - -const CardTitle = React.forwardRef>( - ({ className, ...props }, ref) => ( - - ) -); -CardTitle.displayName = 'CardTitle'; - -const CardDescription = React.forwardRef(({ className, ...props }, ref) => ( - -)); -CardDescription.displayName = 'CardDescription'; - -const CardContent = React.forwardRef(({ className, ...props }, ref) => ( - - - -)); -CardContent.displayName = 'CardContent'; - -const CardFooter = React.forwardRef(({ className, ...props }, ref) => ( - -)); -CardFooter.displayName = 'CardFooter'; - -export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }; diff --git a/packages/react-native-playground/lib/android-navigation-bar.ts b/packages/react-native-playground/lib/android-navigation-bar.ts deleted file mode 100644 index 78187e6b16..0000000000 --- a/packages/react-native-playground/lib/android-navigation-bar.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as NavigationBar from 'expo-navigation-bar'; -import { Platform } from 'react-native'; -import { NAV_THEME } from '~/lib/constants'; - -export async function setAndroidNavigationBar(theme: 'light' | 'dark') { - if (Platform.OS !== 'android') return; - await NavigationBar.setButtonStyleAsync(theme === 'dark' ? 'light' : 'dark'); - await NavigationBar.setBackgroundColorAsync( - theme === 'dark' ? NAV_THEME.dark.background : NAV_THEME.light.background - ); -} diff --git a/packages/react-native-playground/lib/constants.ts b/packages/react-native-playground/lib/constants.ts deleted file mode 100644 index 7c3d02fbc6..0000000000 --- a/packages/react-native-playground/lib/constants.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const NAV_THEME = { - light: { - background: 'hsl(0 0% 100%)', // background - border: 'hsl(240 5.9% 90%)', // border - card: 'hsl(0 0% 100%)', // card - notification: 'hsl(0 84.2% 60.2%)', // destructive - primary: 'hsl(240 5.9% 10%)', // primary - text: 'hsl(240 10% 3.9%)', // foreground - }, - dark: { - background: 'hsl(240 10% 3.9%)', // background - border: 'hsl(240 3.7% 15.9%)', // border - card: 'hsl(240 10% 3.9%)', // card - notification: 'hsl(0 72% 51%)', // destructive - primary: 'hsl(0 0% 98%)', // primary - text: 'hsl(0 0% 98%)', // foreground - }, -}; diff --git a/packages/react-native-playground/lib/icons/Info.tsx b/packages/react-native-playground/lib/icons/Info.tsx deleted file mode 100644 index 70282e4b0a..0000000000 --- a/packages/react-native-playground/lib/icons/Info.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import { Info } from 'lucide-react-native'; -import { iconWithClassName } from './iconWithClassName'; -iconWithClassName(Info); -export { Info }; \ No newline at end of file diff --git a/packages/react-native-playground/lib/icons/MoonStar.tsx b/packages/react-native-playground/lib/icons/MoonStar.tsx deleted file mode 100644 index c884cd1a3f..0000000000 --- a/packages/react-native-playground/lib/icons/MoonStar.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import { MoonStar } from 'lucide-react-native'; -import { iconWithClassName } from './iconWithClassName'; -iconWithClassName(MoonStar); -export { MoonStar }; \ No newline at end of file diff --git a/packages/react-native-playground/lib/icons/Sun.tsx b/packages/react-native-playground/lib/icons/Sun.tsx deleted file mode 100644 index 3e2a642db2..0000000000 --- a/packages/react-native-playground/lib/icons/Sun.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import { Sun } from 'lucide-react-native'; -import { iconWithClassName } from './iconWithClassName'; -iconWithClassName(Sun); -export { Sun }; \ No newline at end of file diff --git a/packages/react-native-playground/lib/useColorScheme.tsx b/packages/react-native-playground/lib/useColorScheme.tsx deleted file mode 100644 index 8e171b6e4d..0000000000 --- a/packages/react-native-playground/lib/useColorScheme.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { useColorScheme as useNativewindColorScheme } from 'nativewind'; - -export function useColorScheme() { - const { colorScheme, setColorScheme, toggleColorScheme } = useNativewindColorScheme(); - return { - colorScheme: colorScheme ?? 'dark', - isDarkColorScheme: colorScheme === 'dark', - setColorScheme, - toggleColorScheme, - }; -} diff --git a/packages/react-native-playground/lib/utils.ts b/packages/react-native-playground/lib/utils.ts deleted file mode 100644 index 2819a830d2..0000000000 --- a/packages/react-native-playground/lib/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { clsx, type ClassValue } from 'clsx'; -import { twMerge } from 'tailwind-merge'; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} diff --git a/packages/react-native-playground/nativewind-env.d.ts b/packages/react-native-playground/nativewind-env.d.ts deleted file mode 100644 index a13e3136bb..0000000000 --- a/packages/react-native-playground/nativewind-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/packages/react-native-playground/package.json b/packages/react-native-playground/package.json deleted file mode 100644 index fcd5298c40..0000000000 --- a/packages/react-native-playground/package.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "name": "react-native-playground", - "main": "index.js", - "version": "1.0.0", - "scripts": { - "dev": "expo start -c", - "dev:web": "expo start -c --web", - "dev:android": "expo start -c --android", - "dev:storybook": "STORYBOOK_ENABLED=true expo start -c", - "dev:app": "STORYBOOK_ENABLED=false expo start -c", - "android": "expo start -c --android", - "ios": "expo start -c --ios", - "web": "expo start -c --web", - "clean": "rm -rf .expo node_modules", - "postinstall": "npx tailwindcss -i ./global.css -o ./node_modules/.cache/nativewind/global.css", - "storybook-generate": "sb-rn-get-stories" - }, - "dependencies": { - "@factorialco/f0-core": "workspace:*", - "@factorialco/f0-react-native": "workspace:*", - "@react-navigation/native": "^7.0.0", - "@rn-primitives/avatar": "~1.1.0", - "@rn-primitives/portal": "~1.1.0", - "@rn-primitives/progress": "~1.1.0", - "@rn-primitives/slot": "~1.1.0", - "@rn-primitives/tooltip": "~1.1.0", - "@rn-primitives/types": "~1.1.0", - "class-variance-authority": "^0.7.0", - "clsx": "^2.1.0", - "expo": "^52.0.25", - "expo-clipboard": "^7.1.4", - "expo-linking": "~7.0.4", - "expo-navigation-bar": "~4.0.7", - "expo-splash-screen": "~0.29.20", - "expo-status-bar": "~2.0.1", - "expo-system-ui": "~4.0.7", - "lucide-react-native": "^0.378.0", - "nativewind": "^4.1.23", - "react": "18.3.1", - "react-dom": "18.3.1", - "react-native": "0.76.9", - "react-native-reanimated": "~3.16.1", - "react-native-safe-area-context": "4.12.0", - "react-native-screens": "^4.4.0", - "react-native-svg": "15.8.0", - "react-native-web": "~0.19.13", - "tailwind-merge": "^2.2.1", - "tailwindcss": "3.3.5", - "tailwindcss-animate": "^1.0.7", - "zustand": "^4.4.7" - }, - "devDependencies": { - "@babel/core": "^7.26.0", - "@gorhom/bottom-sheet": "^5.1.2", - "@react-native-async-storage/async-storage": "^2.1.2", - "@react-native-community/datetimepicker": "^8.3.0", - "@react-native-community/slider": "^4.5.6", - "@storybook/addon-ondevice-actions": "^8.6.4", - "@storybook/addon-ondevice-controls": "^8.6.4", - "@storybook/addon-ondevice-notes": "^8.6.4", - "@storybook/react-native": "^8.6.4", - "@types/react": "~18.3.12", - "babel-loader": "^8.4.1", - "react-native-gesture-handler": "^2.25.0", - "typescript": "^5.3.3" - }, - "private": true -} diff --git a/packages/react-native-playground/tailwind.config.ts b/packages/react-native-playground/tailwind.config.ts deleted file mode 100644 index 6214166a83..0000000000 --- a/packages/react-native-playground/tailwind.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -const baseConfig = require("@factorialco/f0-react-native/tailwind.config"); - -/** @type {import("tailwindcss").Config} */ -module.exports = { - ...baseConfig, - content: [ - "./app/**/*.{ts,tsx}", - "./components/**/*.{ts,tsx}", - "./node_modules/@factorialco/f0-react-native/**/*.{ts,tsx}", - ], -}; diff --git a/packages/react-native-playground/tsconfig.json b/packages/react-native-playground/tsconfig.json deleted file mode 100644 index 92a863d0b0..0000000000 --- a/packages/react-native-playground/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "expo/tsconfig.base", - "compilerOptions": { - "strict": true, - "baseUrl": ".", - "paths": { - "@factorialco/f0-core": ["../core/src"], - "~/*": ["*"] - } - }, - "include": [ - "**/*.ts", - "**/*.tsx", - ".expo/types/**/*.ts", - "expo-env.d.ts", - "nativewind-env.d.ts" - ] -} diff --git a/packages/react-native/.gitignore b/packages/react-native/.gitignore new file mode 100644 index 0000000000..d1f35cd341 --- /dev/null +++ b/packages/react-native/.gitignore @@ -0,0 +1,15 @@ + +# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb +# The following patterns were generated by expo-cli + +expo-env.d.ts +# @end expo-cli + +# Expo +.expo/ +*.expo.* + +# Distribution +dist/ +package/ +*.tgz \ No newline at end of file diff --git a/packages/react-native/.npmrc b/packages/react-native/.npmrc new file mode 100644 index 0000000000..2a5df88336 --- /dev/null +++ b/packages/react-native/.npmrc @@ -0,0 +1,3 @@ +# Monorepo configuration for React Native package +shamefully-hoist=true +strict-peer-dependencies=false diff --git a/packages/react-native/.storybook/index.ts b/packages/react-native/.storybook/index.ts new file mode 100644 index 0000000000..93fedd5e43 --- /dev/null +++ b/packages/react-native/.storybook/index.ts @@ -0,0 +1,19 @@ +import AsyncStorage from "@react-native-async-storage/async-storage"; +import { view } from "./storybook.requires"; + +const StorybookUIRoot = view.getStorybookUI({ + storage: { + getItem: AsyncStorage.getItem, + setItem: AsyncStorage.setItem, + }, + // Enable tab mode instead of drawer - much better for Android + enableWebsockets: true, + // Start with the navigator closed + isUIHidden: false, + // Use bottom tabs instead of sidebar + shouldDisableKeyboardAvoidingView: true, + // Make sure gestures work + shouldPersistSelection: true, +}); + +export default StorybookUIRoot; diff --git a/packages/react-native-playground/.storybook/main.ts b/packages/react-native/.storybook/main.ts similarity index 76% rename from packages/react-native-playground/.storybook/main.ts rename to packages/react-native/.storybook/main.ts index f306f71de7..8a1686fc03 100644 --- a/packages/react-native-playground/.storybook/main.ts +++ b/packages/react-native/.storybook/main.ts @@ -2,8 +2,8 @@ import { StorybookConfig } from "@storybook/react-native"; const main: StorybookConfig = { stories: [ - "../components/**/*.stories.?(ts|tsx)", - "../modules/**/*.stories.?(ts|tsx)", + "../src/components/**/*.stories.?(ts|tsx)", + "../src/experimental/**/*.stories.?(ts|tsx)", "./stories/**/*.stories.?(ts|tsx|js|jsx)", ], addons: [ diff --git a/packages/react-native-playground/.storybook/preview.tsx b/packages/react-native/.storybook/preview.tsx similarity index 92% rename from packages/react-native-playground/.storybook/preview.tsx rename to packages/react-native/.storybook/preview.tsx index e3c8d77dd5..a8920322f6 100644 --- a/packages/react-native-playground/.storybook/preview.tsx +++ b/packages/react-native/.storybook/preview.tsx @@ -1,6 +1,7 @@ import React from "react"; import type { Preview } from "@storybook/react"; import { View } from "react-native"; +import "../playground/global.css"; const preview: Preview = { decorators: [ diff --git a/packages/react-native-playground/.storybook/storybook.requires.ts b/packages/react-native/.storybook/storybook.requires.ts similarity index 93% rename from packages/react-native-playground/.storybook/storybook.requires.ts rename to packages/react-native/.storybook/storybook.requires.ts index e09a9f2422..cbdade90bf 100644 --- a/packages/react-native-playground/.storybook/storybook.requires.ts +++ b/packages/react-native/.storybook/storybook.requires.ts @@ -9,26 +9,26 @@ import "@storybook/addon-ondevice-actions/register"; const normalizedStories = [ { titlePrefix: "", - directory: "./components", + directory: "./src/components", files: "**/*.stories.?(ts|tsx)", importPathMatcher: /^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx)?)$/, // @ts-ignore req: require.context( - "../components", + "../src/components", true, /^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx)?)$/, ), }, { titlePrefix: "", - directory: "./modules", + directory: "./src/experimental", files: "**/*.stories.?(ts|tsx)", importPathMatcher: /^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx)?)$/, // @ts-ignore req: require.context( - "../modules", + "../src/experimental", true, /^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx)?)$/, ), diff --git a/packages/react-native-playground/app.json b/packages/react-native/app.json similarity index 51% rename from packages/react-native-playground/app.json rename to packages/react-native/app.json index 39cd38c81f..25ca4ea9db 100644 --- a/packages/react-native-playground/app.json +++ b/packages/react-native/app.json @@ -1,15 +1,13 @@ { "expo": { - "name": "react-native-playground", - "slug": "react-native-playground", + "name": "F0 React Native Storybook", + "slug": "f0-react-native-storybook", "version": "1.0.0", "orientation": "portrait", - "icon": "./assets/images/icon.png", - "scheme": "myapp", - "userInterfaceStyle": "automatic", - "newArchEnabled": true, + "icon": "./playground/assets/icon.svg", + "userInterfaceStyle": "light", "splash": { - "image": "./assets/images/splash.png", + "image": "./playground/assets/splash.png", "resizeMode": "contain", "backgroundColor": "#ffffff" }, @@ -19,14 +17,13 @@ }, "android": { "adaptiveIcon": { - "foregroundImage": "./assets/images/adaptive-icon.png", + "foregroundImage": "./playground/assets/adaptive-icon.png", "backgroundColor": "#ffffff" } }, "web": { - "bundler": "metro", - "output": "static", - "favicon": "./assets/images/favicon.png" + "favicon": "./playground/assets/favicon.png", + "bundler": "metro" }, "plugins": ["expo-router"], "experiments": { diff --git a/packages/react-native/babel.config.cjs b/packages/react-native/babel.config.cjs deleted file mode 100644 index 2138e6a917..0000000000 --- a/packages/react-native/babel.config.cjs +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - presets: ["babel-preset-expo", "@babel/preset-typescript"], -}; diff --git a/packages/react-native/babel.config.js b/packages/react-native/babel.config.js new file mode 100644 index 0000000000..565fb74b60 --- /dev/null +++ b/packages/react-native/babel.config.js @@ -0,0 +1,23 @@ +module.exports = function (api) { + api.cache(true); + return { + presets: [ + ["babel-preset-expo", { jsxImportSource: "nativewind" }], + "nativewind/babel", + ], + plugins: [ + [ + "module-resolver", + { + root: ["./src", "./playground"], + alias: { + "@": "./src", + lib: "./playground/lib", + ui: "./playground/ui", + }, + }, + ], + "react-native-reanimated/plugin", + ], + }; +}; diff --git a/packages/react-native/eslint.config.mjs b/packages/react-native/eslint.config.mjs index d205bda366..cf522c415a 100644 --- a/packages/react-native/eslint.config.mjs +++ b/packages/react-native/eslint.config.mjs @@ -23,7 +23,12 @@ const reactSettings = { export default [ // Ignore dist and other config files { - ignores: ["**/dist", "**/.eslintrc.cjs", "**/babel.config.cjs"], + ignores: [ + "**/dist", + "**/.eslintrc.cjs", + "**/babel.config.cjs", + ".storybook/storybook.requires.ts", + ], }, // Main React Native configuration diff --git a/packages/react-native-playground/index.js b/packages/react-native/index.js similarity index 77% rename from packages/react-native-playground/index.js rename to packages/react-native/index.js index b39b2bfa35..5f5c11de71 100644 --- a/packages/react-native-playground/index.js +++ b/packages/react-native/index.js @@ -1,7 +1,14 @@ +// Import side effects first and services + +// Initialize services + +// Register app entry through Expo Router +import "expo-router/entry"; + import { registerRootComponent } from "expo"; import { ExpoRoot } from "expo-router"; import StorybookUIRoot from "./.storybook"; -import "./global.css"; +import "./playground/global.css"; // Check for STORYBOOK_ENABLED environment variable // This allows using: @@ -14,7 +21,7 @@ const SHOW_STORYBOOK = process.env.STORYBOOK_ENABLED === "true" || true; // Must be exported or Fast Refresh won't update the context export function App() { - const ctx = require.context("./app"); + const ctx = require.context("./playground/app"); return ; } diff --git a/packages/react-native-playground/metro.config.js b/packages/react-native/metro.config.js similarity index 75% rename from packages/react-native-playground/metro.config.js rename to packages/react-native/metro.config.js index 0439e8eb03..00efdb38a0 100644 --- a/packages/react-native-playground/metro.config.js +++ b/packages/react-native/metro.config.js @@ -5,11 +5,12 @@ const withStorybook = require("@storybook/react-native/metro/withStorybook"); const config = getDefaultConfig(__dirname); -config.resolver.disableHierarchicalLookup = true; +// Since SDK 52+, Expo automatically configures Metro for monorepos +// No need for manual watchFolders, resolver.nodeModulesPath, or disableHierarchicalLookup // First apply NativeWind const nativeWindConfig = withNativeWind(config, { - input: "./global.css", + input: "./playground/global.css", inlineRem: 16, }); diff --git a/packages/react-native/package.json b/packages/react-native/package.json index a817c83f90..a6bdfdd823 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -1,49 +1,34 @@ { "name": "@factorialco/f0-react-native", "version": "0.20.1", - "type": "module", "description": "React Native components for F0 Design System", - "main": "src/index.ts", - "module": "src/index.ts", - "exports": { - ".": { - "import": "./src/index.ts", - "require": "./src/index.ts", - "react-native": "./src/index.ts", - "default": "./src/index.ts" - }, - "./icons": { - "import": "./src/icons/index.ts", - "require": "./src/icons/index.ts", - "react-native": "./src/icons/index.ts", - "default": "./src/icons/index.ts" - }, - "./icons/app": { - "import": "./src/icons/app/index.ts", - "require": "./src/icons/app/index.ts", - "react-native": "./src/icons/app/index.ts", - "default": "./src/icons/app/index.ts" - }, - "./icons/modules": { - "import": "./src/icons/modules/index.ts", - "require": "./src/icons/modules/index.ts", - "react-native": "./src/icons/modules/index.ts", - "default": "./src/icons/modules/index.ts" - }, - "./tailwind.config": "./tailwind.config.ts" - }, + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", "files": [ - "src", + "dist", "tailwind.config.ts" ], "scripts": { - "build": "echo 'we ship source code as is'; exit 0", + "build": "tsup", + "build:watch": "tsup --watch", + "dev": "tsup --watch", + "clean": "rm -rf dist", + "compile-css": "npx tailwindcss -i ./playground/global.css -o ./node_modules/.cache/nativewind/global.css", + "dev:storybook": "pnpm compile-css && STORYBOOK_ENABLED=true expo start -c", + "dev:app": "pnpm compile-css && STORYBOOK_ENABLED=false expo start -c", + "type-check": "tsc --noEmit", "lint": "eslint -c eslint.config.mjs . --report-unused-disable-directives --max-warnings 0", "lint-fix": "pnpm run lint -- --fix", "test": "jest", "generate-icons": "rm -fR src/icons/app/* src/icons/modules/* && run-p generate-app-icons generate-module-icons && find src/icons -name '*.tsx' -exec sed -i '' 's/SVGSVGElement/Svg/g; s/ xmlns=\"http:\\/\\/www.w3.org\\/2000\\/svg\"//g; s/ strokeWidth={[^}]*}//g; s/ strokeWidth=\"[^\"]*\"//g' {} +", "generate-app-icons": "npx @svgr/cli --out-dir src/icons/app --native --svg-props className={props.className} ../../packages/core/assets/icons/app", - "generate-module-icons": "npx @svgr/cli --out-dir src/icons/modules --native --svg-props className={props.className} ../../packages/core/assets/icons/modules" + "generate-module-icons": "npx @svgr/cli --out-dir src/icons/modules --native --svg-props className={props.className} ../../packages/core/assets/icons/modules", + "storybook-generate": "sb-rn-get-stories", + "start": "expo start", + "start:storybook": "expo start --dev-client", + "android": "expo start --android", + "ios": "expo start --ios", + "web": "expo start --web" }, "keywords": [ "factorial", @@ -56,42 +41,71 @@ "peerDependencies": { "date-fns": "^3.6.0", "nativewind": "^4.1.23", - "react": "^18.2.0", - "react-native": "^0.76.6", + "react": "19.0.0", + "react-native": "0.79.5", "react-native-svg": "^15.8.0", "tailwindcss": "^3.4.3", - "twemoji-parser": "^14.0.0", - "typescript": "^5.7.2" + "twemoji-parser": "^14.0.0" }, "dependencies": { "@factorialco/f0-core": "workspace:*", "clsx": "^2.1.1", "cva": "1.0.0-beta.3", "expo-clipboard": "^7.1.4", - "lodash": "^4.17.21", - "react-native-safe-area-context": "4.12.0", "tailwind-merge": "^2.6.0" }, "devDependencies": { + "@babel/preset-env": "^7.28.3", + "@babel/runtime": "^7.28.4", + "@react-native-async-storage/async-storage": "^2.1.2", + "@react-native-community/datetimepicker": "^8.4.1", + "@react-native-community/slider": "^4.5.6", + "@rn-primitives/avatar": "~1.1.0", + "@rn-primitives/portal": "~1.1.0", + "@rn-primitives/progress": "~1.1.0", + "@rn-primitives/slot": "~1.1.0", + "@rn-primitives/tooltip": "~1.1.0", + "@rn-primitives/types": "~1.1.0", + "@storybook/addon-ondevice-actions": "^8.6.4", + "@storybook/addon-ondevice-controls": "^8.6.4", + "@storybook/addon-ondevice-notes": "^8.6.4", + "@storybook/react-native": "^8.6.4", "@svgr/cli": "^8.1.0", "@testing-library/react-native": "^12.9.0", "@types/jest": "^29.5.14", "@types/lodash": "^4.17.16", - "@types/react": "^18.2.0", - "@types/react-native": "^0.73.0", + "@types/react": "^19.0.14", "@types/twemoji-parser": "^13.1.4", + "babel-jest": "^29.7.0", + "babel-plugin-module-resolver": "^5.0.0", + "date-fns": "^3.6.0", + "expo": "^53.0.23", + "expo-constants": "^17.1.7", + "expo-linking": "~7.1.7", + "expo-modules-core": "~2.5.0", + "expo-navigation-bar": "~4.2.8", + "expo-router": "^5.1.7", + "expo-status-bar": "~2.2.3", + "glob": "^11.0.0", "globals": "^16.0.0", "jest": "^29.7.0", + "lucide-react-native": "^0.378.0", "nativewind": "^4.1.23", "npm-run-all": "^4.1.5", "prettier": "^3.5.2", "prettier-plugin-tailwindcss": "^0.6.10", - "react": "^18.2.0", - "react-native": "^0.76.6", - "react-native-svg": "^15.8.0", + "react": "^19.0.0", + "react-native": "^0.79.5", + "react-native-gesture-handler": "^2.24.0", + "react-native-reanimated": "~3.17.5", + "react-native-safe-area-context": "5.4.0", + "react-native-screens": "^4.11.1", + "react-native-svg": "^15.11.2", + "react-native-worklets": "^0.6.1", "tailwindcss": "^3.4.3", "tailwindcss-animate": "^1.0.7", - "typescript": "^5.7.2", - "babel-jest": "^29.7.0" + "tsup": "^8.3.5", + "twemoji-parser": "^14.0.0", + "typescript": "^5.8.3" } } diff --git a/packages/react-native/playground/app/+not-found.tsx b/packages/react-native/playground/app/+not-found.tsx new file mode 100644 index 0000000000..dec4d7c927 --- /dev/null +++ b/packages/react-native/playground/app/+not-found.tsx @@ -0,0 +1,18 @@ +import { Link, Stack, type Href } from "expo-router"; +import { View } from "react-native"; +import { Text } from "../ui/text"; + +export default function NotFoundScreen() { + return ( + <> + + + This screen doesn't exist. + + + Go to home screen! + + + + ); +} diff --git a/packages/react-native-playground/app/_layout.tsx b/packages/react-native/playground/app/_layout.tsx similarity index 55% rename from packages/react-native-playground/app/_layout.tsx rename to packages/react-native/playground/app/_layout.tsx index c9f8d3c642..20b3efeb9f 100644 --- a/packages/react-native-playground/app/_layout.tsx +++ b/packages/react-native/playground/app/_layout.tsx @@ -1,15 +1,20 @@ -import '../global.css'; +import "../global.css"; -import { DarkTheme, DefaultTheme, Theme, ThemeProvider } from '@react-navigation/native'; -import { Stack } from 'expo-router'; -import { StatusBar } from 'expo-status-bar'; -import * as React from 'react'; -import { Platform } from 'react-native'; -import { NAV_THEME } from '~/lib/constants'; -import { useColorScheme } from '~/lib/useColorScheme'; -import { PortalHost } from '@rn-primitives/portal'; -import { ThemeToggle } from '~/components/ThemeToggle'; -import { setAndroidNavigationBar } from '~/lib/android-navigation-bar'; +import { + DarkTheme, + DefaultTheme, + Theme, + ThemeProvider, +} from "@react-navigation/native"; +import { Stack } from "expo-router"; +import { StatusBar } from "expo-status-bar"; +import * as React from "react"; +import { Platform } from "react-native"; +import { NAV_THEME } from "../lib/constants"; +import { useColorScheme } from "../lib/useColorScheme"; +import { setAndroidNavigationBar } from "../lib/android-navigation-bar"; +import { PortalHost } from "@rn-primitives/portal"; +import { ThemeToggle } from "../ui/ThemeToggle"; const LIGHT_THEME: Theme = { ...DefaultTheme, @@ -23,7 +28,7 @@ const DARK_THEME: Theme = { export { // Catch any errors thrown by the Layout component. ErrorBoundary, -} from 'expo-router'; +} from "expo-router"; export default function RootLayout() { const hasMounted = React.useRef(false); @@ -35,9 +40,9 @@ export default function RootLayout() { return; } - if (Platform.OS === 'web') { + if (Platform.OS === "web") { // Adds the background color to the html element to prevent white background on overscroll. - document.documentElement.classList.add('bg-background'); + document.documentElement.classList.add("bg-background"); } setAndroidNavigationBar(colorScheme); setIsColorSchemeLoaded(true); @@ -50,12 +55,12 @@ export default function RootLayout() { return ( - + , }} /> @@ -66,4 +71,6 @@ export default function RootLayout() { } const useIsomorphicLayoutEffect = - Platform.OS === 'web' && typeof window === 'undefined' ? React.useEffect : React.useLayoutEffect; + Platform.OS === "web" && typeof window === "undefined" + ? React.useEffect + : React.useLayoutEffect; diff --git a/packages/react-native-playground/app/index.tsx b/packages/react-native/playground/app/index.tsx similarity index 65% rename from packages/react-native-playground/app/index.tsx rename to packages/react-native/playground/app/index.tsx index c8faf106da..6cc466cb1e 100644 --- a/packages/react-native-playground/app/index.tsx +++ b/packages/react-native/playground/app/index.tsx @@ -5,9 +5,10 @@ import Animated, { FadeOutDown, LayoutAnimationConfig, } from "react-native-reanimated"; -import { Info } from "~/lib/icons/Info"; -import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar"; -import { Button } from "~/components/ui/button"; +import { Info } from "../lib/icons/Info"; + +import { Text } from "../ui/text"; +import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; import { Card, CardContent, @@ -15,15 +16,10 @@ import { CardFooter, CardHeader, CardTitle, -} from "~/components/ui/card"; -import { Progress } from "~/components/ui/progress"; -import { Text } from "~/components/ui/text"; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "~/components/ui/tooltip"; -import { ExampleComponent } from "@factorialco/f0-react-native"; +} from "../ui/card"; +import { Avatar, AvatarImage, AvatarFallback } from "../ui/avatar"; +import { Progress } from "../ui/progress"; +import { Button } from "../../src/components/Button"; const GITHUB_AVATAR_URI = "https://i.pinimg.com/originals/ef/a2/8d/efa28d18a04e7fa40ed49eeb0ab660db.jpg"; @@ -36,33 +32,28 @@ export default function Screen() { } return ( - - + + - + RS - + Rick Sanchez - Scientist - + - + Freelance @@ -71,22 +62,22 @@ export default function Screen() { - Hoooo + Hoooo C-137 - Age + Age 70 - Species + Species Human - Productivity: + Productivity: - + {progress}% @@ -108,11 +99,10 @@ export default function Screen() { + /> diff --git a/packages/react-native-playground/assets/images/adaptive-icon.png b/packages/react-native/playground/assets/adaptive-icon.png similarity index 100% rename from packages/react-native-playground/assets/images/adaptive-icon.png rename to packages/react-native/playground/assets/adaptive-icon.png diff --git a/packages/react-native-playground/assets/images/favicon.png b/packages/react-native/playground/assets/favicon.png similarity index 100% rename from packages/react-native-playground/assets/images/favicon.png rename to packages/react-native/playground/assets/favicon.png diff --git a/packages/react-native/playground/assets/icon.svg b/packages/react-native/playground/assets/icon.svg new file mode 100644 index 0000000000..181c2cc41c --- /dev/null +++ b/packages/react-native/playground/assets/icon.svg @@ -0,0 +1,4 @@ + + +F0 + diff --git a/packages/react-native-playground/assets/images/splash.png b/packages/react-native/playground/assets/splash.png similarity index 100% rename from packages/react-native-playground/assets/images/splash.png rename to packages/react-native/playground/assets/splash.png diff --git a/packages/react-native-playground/global.css b/packages/react-native/playground/global.css similarity index 100% rename from packages/react-native-playground/global.css rename to packages/react-native/playground/global.css diff --git a/packages/react-native/playground/lib/android-navigation-bar.ts b/packages/react-native/playground/lib/android-navigation-bar.ts new file mode 100644 index 0000000000..37d6aecd46 --- /dev/null +++ b/packages/react-native/playground/lib/android-navigation-bar.ts @@ -0,0 +1,11 @@ +import * as NavigationBar from "expo-navigation-bar"; +import { Platform } from "react-native"; +import { NAV_THEME } from "./constants"; + +export async function setAndroidNavigationBar(theme: "light" | "dark") { + if (Platform.OS !== "android") return; + await NavigationBar.setButtonStyleAsync(theme === "dark" ? "light" : "dark"); + await NavigationBar.setBackgroundColorAsync( + theme === "dark" ? NAV_THEME.dark.background : NAV_THEME.light.background, + ); +} diff --git a/packages/react-native/playground/lib/constants.ts b/packages/react-native/playground/lib/constants.ts new file mode 100644 index 0000000000..b2030fdf12 --- /dev/null +++ b/packages/react-native/playground/lib/constants.ts @@ -0,0 +1,18 @@ +export const NAV_THEME = { + light: { + background: "hsl(0 0% 100%)", // background + border: "hsl(240 5.9% 90%)", // border + card: "hsl(0 0% 100%)", // card + notification: "hsl(0 84.2% 60.2%)", // destructive + primary: "hsl(240 5.9% 10%)", // primary + text: "hsl(240 10% 3.9%)", // foreground + }, + dark: { + background: "hsl(240 10% 3.9%)", // background + border: "hsl(240 3.7% 15.9%)", // border + card: "hsl(240 10% 3.9%)", // card + notification: "hsl(0 72% 51%)", // destructive + primary: "hsl(0 0% 98%)", // primary + text: "hsl(0 0% 98%)", // foreground + }, +}; diff --git a/packages/react-native/playground/lib/icons/Info.tsx b/packages/react-native/playground/lib/icons/Info.tsx new file mode 100644 index 0000000000..68176e0546 --- /dev/null +++ b/packages/react-native/playground/lib/icons/Info.tsx @@ -0,0 +1,4 @@ +import { Info } from "lucide-react-native"; +import { iconWithClassName } from "./iconWithClassName"; +iconWithClassName(Info); +export { Info }; diff --git a/packages/react-native/playground/lib/icons/MoonStar.tsx b/packages/react-native/playground/lib/icons/MoonStar.tsx new file mode 100644 index 0000000000..29b65181f8 --- /dev/null +++ b/packages/react-native/playground/lib/icons/MoonStar.tsx @@ -0,0 +1,4 @@ +import { MoonStar } from "lucide-react-native"; +import { iconWithClassName } from "./iconWithClassName"; +iconWithClassName(MoonStar); +export { MoonStar }; diff --git a/packages/react-native/playground/lib/icons/Sun.tsx b/packages/react-native/playground/lib/icons/Sun.tsx new file mode 100644 index 0000000000..8b2ddfaa0f --- /dev/null +++ b/packages/react-native/playground/lib/icons/Sun.tsx @@ -0,0 +1,4 @@ +import { Sun } from "lucide-react-native"; +import { iconWithClassName } from "./iconWithClassName"; +iconWithClassName(Sun); +export { Sun }; diff --git a/packages/react-native-playground/lib/icons/iconWithClassName.ts b/packages/react-native/playground/lib/icons/iconWithClassName.ts similarity index 61% rename from packages/react-native-playground/lib/icons/iconWithClassName.ts rename to packages/react-native/playground/lib/icons/iconWithClassName.ts index c475ed1133..bf73d7eb47 100644 --- a/packages/react-native-playground/lib/icons/iconWithClassName.ts +++ b/packages/react-native/playground/lib/icons/iconWithClassName.ts @@ -1,10 +1,10 @@ -import type { LucideIcon } from 'lucide-react-native'; -import { cssInterop } from 'nativewind'; +import type { LucideIcon } from "lucide-react-native"; +import { cssInterop } from "nativewind"; export function iconWithClassName(icon: LucideIcon) { cssInterop(icon, { className: { - target: 'style', + target: "style", nativeStyleToProp: { color: true, opacity: true, diff --git a/packages/react-native/playground/lib/useColorScheme.tsx b/packages/react-native/playground/lib/useColorScheme.tsx new file mode 100644 index 0000000000..df75499f1e --- /dev/null +++ b/packages/react-native/playground/lib/useColorScheme.tsx @@ -0,0 +1,12 @@ +import { useColorScheme as useNativewindColorScheme } from "nativewind"; + +export function useColorScheme() { + const { colorScheme, setColorScheme, toggleColorScheme } = + useNativewindColorScheme(); + return { + colorScheme: colorScheme ?? "dark", + isDarkColorScheme: colorScheme === "dark", + setColorScheme, + toggleColorScheme, + }; +} diff --git a/packages/react-native/playground/lib/utils.ts b/packages/react-native/playground/lib/utils.ts new file mode 100644 index 0000000000..a5ef193506 --- /dev/null +++ b/packages/react-native/playground/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/packages/react-native/playground/ui/ThemeToggle.tsx b/packages/react-native/playground/ui/ThemeToggle.tsx new file mode 100644 index 0000000000..1fd635b204 --- /dev/null +++ b/packages/react-native/playground/ui/ThemeToggle.tsx @@ -0,0 +1,42 @@ +import { Pressable, View } from "react-native"; +import { setAndroidNavigationBar } from "../lib/android-navigation-bar"; +import { MoonStar } from "../lib/icons/MoonStar"; +import { Sun } from "../lib/icons/Sun"; +import { useColorScheme } from "../lib/useColorScheme"; +import { cn } from "../lib/utils"; + +export function ThemeToggle() { + const { isDarkColorScheme, setColorScheme } = useColorScheme(); + + function toggleColorScheme() { + const newTheme = isDarkColorScheme ? "light" : "dark"; + setColorScheme(newTheme); + setAndroidNavigationBar(newTheme); + } + + return ( + + {({ pressed }) => ( + + {isDarkColorScheme ? ( + + ) : ( + + )} + + )} + + ); +} diff --git a/packages/react-native/playground/ui/avatar.tsx b/packages/react-native/playground/ui/avatar.tsx new file mode 100644 index 0000000000..abb8814b71 --- /dev/null +++ b/packages/react-native/playground/ui/avatar.tsx @@ -0,0 +1,51 @@ +import * as AvatarPrimitive from "@rn-primitives/avatar"; +import * as React from "react"; +import { cn } from "../lib/utils"; + +const AvatarPrimitiveRoot = AvatarPrimitive.Root; +const AvatarPrimitiveImage = AvatarPrimitive.Image; +const AvatarPrimitiveFallback = AvatarPrimitive.Fallback; + +const Avatar = React.forwardRef< + AvatarPrimitive.RootRef, + AvatarPrimitive.RootProps +>(({ className, ...props }, ref) => ( + +)); +Avatar.displayName = AvatarPrimitiveRoot.displayName; + +const AvatarImage = React.forwardRef< + AvatarPrimitive.ImageRef, + AvatarPrimitive.ImageProps +>(({ className, ...props }, ref) => ( + +)); +AvatarImage.displayName = AvatarPrimitiveImage.displayName; + +const AvatarFallback = React.forwardRef< + AvatarPrimitive.FallbackRef, + AvatarPrimitive.FallbackProps +>(({ className, ...props }, ref) => ( + +)); +AvatarFallback.displayName = AvatarPrimitiveFallback.displayName; + +export { Avatar, AvatarFallback, AvatarImage }; diff --git a/packages/react-native/playground/ui/button.tsx b/packages/react-native/playground/ui/button.tsx new file mode 100644 index 0000000000..bca4baa806 --- /dev/null +++ b/packages/react-native/playground/ui/button.tsx @@ -0,0 +1,87 @@ +import { cva, type VariantProps } from "cva"; +import * as React from "react"; +import { Pressable } from "react-native"; +import { TextClassContext } from "./text"; +import { cn } from "../lib/utils"; + +const buttonVariants = cva({ + base: "group flex items-center justify-center rounded-md web:ring-offset-background web:transition-colors web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2", + variants: { + variant: { + default: "bg-primary web:hover:opacity-90 active:opacity-90", + destructive: "bg-destructive web:hover:opacity-90 active:opacity-90", + outline: + "border border-input bg-background web:hover:bg-accent web:hover:text-accent-foreground active:bg-accent", + secondary: "bg-secondary web:hover:opacity-80 active:opacity-80", + ghost: + "web:hover:bg-accent web:hover:text-accent-foreground active:bg-accent", + link: "web:underline-offset-4 web:hover:underline web:focus:underline ", + }, + size: { + default: "h-10 px-4 py-2 native:h-12 native:px-5 native:py-3", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8 native:h-14", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, +}); + +const buttonTextVariants = cva({ + base: "web:whitespace-nowrap text-sm native:text-base font-medium text-foreground web:transition-colors", + variants: { + variant: { + default: "text-primary-foreground", + destructive: "text-destructive-foreground", + outline: "group-active:text-accent-foreground", + secondary: + "text-secondary-foreground group-active:text-secondary-foreground", + ghost: "group-active:text-accent-foreground", + link: "text-primary group-active:underline", + }, + size: { + default: "", + sm: "", + lg: "native:text-lg", + icon: "", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, +}); + +type ButtonProps = React.ComponentPropsWithoutRef & + VariantProps; + +const Button = React.forwardRef< + React.ElementRef, + ButtonProps +>(({ className, variant, size, ...props }, ref) => { + return ( + + + + ); +}); +Button.displayName = "Button"; + +export { Button, buttonTextVariants, buttonVariants }; +export type { ButtonProps }; diff --git a/packages/react-native/playground/ui/card.tsx b/packages/react-native/playground/ui/card.tsx new file mode 100644 index 0000000000..dcead0f0cf --- /dev/null +++ b/packages/react-native/playground/ui/card.tsx @@ -0,0 +1,87 @@ +import type { TextRef, ViewRef } from "@rn-primitives/types"; +import * as React from "react"; +import { Text, TextProps, View, ViewProps } from "react-native"; +import { TextClassContext } from "./text"; +import { cn } from "../lib/utils"; + +const Card = React.forwardRef( + ({ className, ...props }, ref) => ( + + ), +); +Card.displayName = "Card"; + +const CardHeader = React.forwardRef( + ({ className, ...props }, ref) => ( + + ), +); +CardHeader.displayName = "CardHeader"; + +const CardTitle = React.forwardRef< + TextRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CardTitle.displayName = "CardTitle"; + +const CardDescription = React.forwardRef( + ({ className, ...props }, ref) => ( + + ), +); +CardDescription.displayName = "CardDescription"; + +const CardContent = React.forwardRef( + ({ className, ...props }, ref) => ( + + + + ), +); +CardContent.displayName = "CardContent"; + +const CardFooter = React.forwardRef( + ({ className, ...props }, ref) => ( + + ), +); +CardFooter.displayName = "CardFooter"; + +export { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +}; diff --git a/packages/react-native-playground/components/ui/progress.tsx b/packages/react-native/playground/ui/progress.tsx similarity index 55% rename from packages/react-native-playground/components/ui/progress.tsx rename to packages/react-native/playground/ui/progress.tsx index f73bf40bd6..7fb286ab2f 100644 --- a/packages/react-native-playground/components/ui/progress.tsx +++ b/packages/react-native/playground/ui/progress.tsx @@ -1,14 +1,14 @@ -import * as ProgressPrimitive from '@rn-primitives/progress'; -import * as React from 'react'; -import { Platform, View } from 'react-native'; +import * as ProgressPrimitive from "@rn-primitives/progress"; +import * as React from "react"; +import { Platform, View } from "react-native"; import Animated, { Extrapolation, interpolate, useAnimatedStyle, useDerivedValue, withSpring, -} from 'react-native-reanimated'; -import { cn } from '~/lib/utils'; +} from "react-native-reanimated"; +import { cn } from "../lib/utils"; const Progress = React.forwardRef< ProgressPrimitive.RootRef, @@ -19,7 +19,10 @@ const Progress = React.forwardRef< return ( @@ -30,32 +33,46 @@ Progress.displayName = ProgressPrimitive.Root.displayName; export { Progress }; -function Indicator({ value, className }: { value: number | undefined | null; className?: string }) { +function Indicator({ + value, + className, +}: { + value: number | undefined | null; + className?: string; +}) { const progress = useDerivedValue(() => value ?? 0); const indicator = useAnimatedStyle(() => { return { width: withSpring( `${interpolate(progress.value, [0, 100], [1, 100], Extrapolation.CLAMP)}%`, - { overshootClamping: true } + { overshootClamping: true }, ), }; }); - if (Platform.OS === 'web') { + if (Platform.OS === "web") { return ( - + ); } return ( - + ); } diff --git a/packages/react-native-playground/components/ui/text.tsx b/packages/react-native/playground/ui/text.tsx similarity index 51% rename from packages/react-native-playground/components/ui/text.tsx rename to packages/react-native/playground/ui/text.tsx index 0d7af0f9ee..2b430f91ac 100644 --- a/packages/react-native-playground/components/ui/text.tsx +++ b/packages/react-native/playground/ui/text.tsx @@ -1,8 +1,8 @@ -import * as Slot from '@rn-primitives/slot'; -import type { SlottableTextProps, TextRef } from '@rn-primitives/types'; -import * as React from 'react'; -import { Text as RNText } from 'react-native'; -import { cn } from '~/lib/utils'; +import * as Slot from "@rn-primitives/slot"; +import type { SlottableTextProps, TextRef } from "@rn-primitives/types"; +import * as React from "react"; +import { Text as RNText } from "react-native"; +import { cn } from "../lib/utils"; const TextClassContext = React.createContext(undefined); @@ -12,13 +12,17 @@ const Text = React.forwardRef( const Component = asChild ? Slot.Text : RNText; return ( ); - } + }, ); -Text.displayName = 'Text'; +Text.displayName = "Text"; export { Text, TextClassContext }; diff --git a/packages/react-native-playground/components/ui/tooltip.tsx b/packages/react-native/playground/ui/tooltip.tsx similarity index 52% rename from packages/react-native-playground/components/ui/tooltip.tsx rename to packages/react-native/playground/ui/tooltip.tsx index 82b65a985e..396eaf9906 100644 --- a/packages/react-native-playground/components/ui/tooltip.tsx +++ b/packages/react-native/playground/ui/tooltip.tsx @@ -1,9 +1,9 @@ -import * as TooltipPrimitive from '@rn-primitives/tooltip'; -import * as React from 'react'; -import { Platform, StyleSheet } from 'react-native'; -import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'; -import { TextClassContext } from '~/components/ui/text'; -import { cn } from '~/lib/utils'; +import * as TooltipPrimitive from "@rn-primitives/tooltip"; +import * as React from "react"; +import { Platform, StyleSheet } from "react-native"; +import Animated, { FadeIn, FadeOut } from "react-native-reanimated"; +import { TextClassContext } from "./text"; +import { cn } from "../lib/utils"; const Tooltip = TooltipPrimitive.Root; @@ -14,18 +14,20 @@ const TooltipContent = React.forwardRef< TooltipPrimitive.ContentProps & { portalHost?: string } >(({ className, sideOffset = 4, portalHost, ...props }, ref) => ( - + - + diff --git a/packages/react-native-playground/components/Activity/ActivityItem/index.stories.tsx b/packages/react-native/src/components/Activity/ActivityItem/index.stories.tsx similarity index 94% rename from packages/react-native-playground/components/Activity/ActivityItem/index.stories.tsx rename to packages/react-native/src/components/Activity/ActivityItem/index.stories.tsx index 2acf496e19..1b9b642549 100644 --- a/packages/react-native-playground/components/Activity/ActivityItem/index.stories.tsx +++ b/packages/react-native/src/components/Activity/ActivityItem/index.stories.tsx @@ -1,13 +1,10 @@ import React from "react"; import { ScrollView, View, Text } from "react-native"; import type { Meta, StoryObj } from "@storybook/react"; -import { - ActivityItem, - ActivityItemSkeleton, -} from "@factorialco/f0-react-native"; -import { AppIcons } from "@factorialco/f0-react-native"; +import { ActivityItem, ActivityItemSkeleton } from "./index"; +import { AppIcons } from "../../../icons"; -const meta = { +const meta: Meta = { title: "Components/ActivityItem", component: ActivityItem, argTypes: { @@ -126,7 +123,7 @@ export const ActivityItemList: Story = { return ( - + Activity Feed diff --git a/packages/react-native/src/components/Avatars/BaseAvatar/utils.ts b/packages/react-native/src/components/Avatars/BaseAvatar/utils.ts index 2c30376d57..fbd91059f8 100644 --- a/packages/react-native/src/components/Avatars/BaseAvatar/utils.ts +++ b/packages/react-native/src/components/Avatars/BaseAvatar/utils.ts @@ -17,7 +17,7 @@ export function getInitials( const isSmall = size === "xsmall" || size === "small"; const minChar = isFile ? 3 : 2; - if (isSmall) return (nameArray[0][0] ?? "").toUpperCase(); + if (isSmall) return (nameArray[0]?.[0] ?? "").toUpperCase(); if (!Array.isArray(name)) return name.slice(0, minChar).toUpperCase(); return nameArray diff --git a/packages/react-native-playground/components/Avatars/CompanyAvatar/index.stories.tsx b/packages/react-native/src/components/Avatars/CompanyAvatar/index.stories.tsx similarity index 88% rename from packages/react-native-playground/components/Avatars/CompanyAvatar/index.stories.tsx rename to packages/react-native/src/components/Avatars/CompanyAvatar/index.stories.tsx index 47b75cba31..2ef477b96b 100644 --- a/packages/react-native-playground/components/Avatars/CompanyAvatar/index.stories.tsx +++ b/packages/react-native/src/components/Avatars/CompanyAvatar/index.stories.tsx @@ -1,8 +1,8 @@ import React from "react"; import { View } from "react-native"; import type { Meta, StoryObj } from "@storybook/react"; -import { CompanyAvatar } from "@factorialco/f0-react-native"; -import { Check } from "@factorialco/f0-react-native/src/icons/app"; +import { CompanyAvatar } from "./index"; +import { Check } from "../../../icons/app"; export const sizes = ["xsmall", "small", "medium", "large", "xlarge"] as const; diff --git a/packages/react-native-playground/components/Avatars/DateAvatar/index.stories.tsx b/packages/react-native/src/components/Avatars/DateAvatar/index.stories.tsx similarity index 90% rename from packages/react-native-playground/components/Avatars/DateAvatar/index.stories.tsx rename to packages/react-native/src/components/Avatars/DateAvatar/index.stories.tsx index b1a44815a0..6338dec5a2 100644 --- a/packages/react-native-playground/components/Avatars/DateAvatar/index.stories.tsx +++ b/packages/react-native/src/components/Avatars/DateAvatar/index.stories.tsx @@ -1,4 +1,4 @@ -import { DateAvatar } from "@factorialco/f0-react-native"; +import { DateAvatar } from "./index"; import type { Meta, StoryObj } from "@storybook/react"; import { View } from "react-native"; diff --git a/packages/react-native-playground/components/Avatars/EmojiAvatar/index.stories.tsx b/packages/react-native/src/components/Avatars/EmojiAvatar/index.stories.tsx similarity index 81% rename from packages/react-native-playground/components/Avatars/EmojiAvatar/index.stories.tsx rename to packages/react-native/src/components/Avatars/EmojiAvatar/index.stories.tsx index ad2382a85b..a2e071d621 100644 --- a/packages/react-native-playground/components/Avatars/EmojiAvatar/index.stories.tsx +++ b/packages/react-native/src/components/Avatars/EmojiAvatar/index.stories.tsx @@ -1,9 +1,9 @@ import React from "react"; import { ScrollView, View, Text } from "react-native"; import type { Meta, StoryObj } from "@storybook/react"; -import { EmojiAvatar } from "@factorialco/f0-react-native"; +import { EmojiAvatar } from "./index"; -const meta = { +const meta: Meta = { title: "Components/Avatars/EmojiAvatar", component: EmojiAvatar, argTypes: { @@ -37,7 +37,7 @@ type EmojiAvatarProps = { }; const EmojiAvatarDisplay = ({ emoji, size, className }: EmojiAvatarProps) => ( - + ); @@ -52,10 +52,10 @@ export const Basic: Story = { export const SizeVariants: Story = { render: () => ( - + Size Variants - + diff --git a/packages/react-native-playground/components/Avatars/FileAvatar/index.stories.tsx b/packages/react-native/src/components/Avatars/FileAvatar/index.stories.tsx similarity index 91% rename from packages/react-native-playground/components/Avatars/FileAvatar/index.stories.tsx rename to packages/react-native/src/components/Avatars/FileAvatar/index.stories.tsx index a2459e3139..391b2544b7 100644 --- a/packages/react-native-playground/components/Avatars/FileAvatar/index.stories.tsx +++ b/packages/react-native/src/components/Avatars/FileAvatar/index.stories.tsx @@ -1,9 +1,9 @@ -import { FileAvatar } from "@factorialco/f0-react-native"; -import { Check } from "@factorialco/f0-react-native/src/icons/app"; +import { FileAvatar } from "./index"; +import { Check } from "../../../icons/app"; import type { Meta, StoryObj } from "@storybook/react"; import { View } from "react-native"; -const meta = { +const meta: Meta = { component: FileAvatar, decorators: [ (Story) => ( @@ -45,7 +45,7 @@ export const AllFileTypesGrid = (): React.ReactElement => { ]; return ( - + {fileTypes.map((fileType, index) => ( { ); if (matchedMimeKey) { - return FILE_TYPE_MAP[MIME_MATCH_MAP[matchedMimeKey]]; + const fileTypeKey = MIME_MATCH_MAP[matchedMimeKey]; + if (fileTypeKey && FILE_TYPE_MAP[fileTypeKey]) { + return FILE_TYPE_MAP[fileTypeKey]; + } } const extension = file.name.toLowerCase().split(".").pop(); if (extension && EXTENSION_MAP[extension]) { - return FILE_TYPE_MAP[EXTENSION_MAP[extension]]; + const fileTypeKey = EXTENSION_MAP[extension]; + if (fileTypeKey && FILE_TYPE_MAP[fileTypeKey]) { + return FILE_TYPE_MAP[fileTypeKey]; + } } - return FILE_TYPE_MAP.default; + return FILE_TYPE_MAP.default!; }; export { getFileTypeInfo }; diff --git a/packages/react-native/src/components/Avatars/IconAvatar/__snapshots__/index.spec.tsx.snap b/packages/react-native/src/components/Avatars/IconAvatar/__snapshots__/index.spec.tsx.snap index 9241a5a97b..dae114d895 100644 --- a/packages/react-native/src/components/Avatars/IconAvatar/__snapshots__/index.spec.tsx.snap +++ b/packages/react-native/src/components/Avatars/IconAvatar/__snapshots__/index.spec.tsx.snap @@ -8,7 +8,6 @@ exports[`IconAvatar Snapshot - different sizes 1`] = ` align="xMidYMid" bbHeight="100%" bbWidth="100%" - className="shrink-0 w-4 h-4 stroke-sm text-f1-foreground-secondary" fill="none" focusable={false} meetOrSlice={0} @@ -93,7 +92,6 @@ exports[`IconAvatar Snapshot - different sizes 2`] = ` align="xMidYMid" bbHeight="100%" bbWidth="100%" - className="shrink-0 w-5 h-5 stroke-md text-f1-foreground-secondary" fill="none" focusable={false} meetOrSlice={0} @@ -178,7 +176,6 @@ exports[`IconAvatar Snapshot - different sizes 3`] = ` align="xMidYMid" bbHeight="100%" bbWidth="100%" - className="shrink-0 w-6 h-6 stroke-lg text-f1-foreground-secondary" fill="none" focusable={false} meetOrSlice={0} diff --git a/packages/react-native-playground/components/Avatars/IconAvatar/index.stories.tsx b/packages/react-native/src/components/Avatars/IconAvatar/index.stories.tsx similarity index 78% rename from packages/react-native-playground/components/Avatars/IconAvatar/index.stories.tsx rename to packages/react-native/src/components/Avatars/IconAvatar/index.stories.tsx index e183cd46d8..2877887aef 100644 --- a/packages/react-native-playground/components/Avatars/IconAvatar/index.stories.tsx +++ b/packages/react-native/src/components/Avatars/IconAvatar/index.stories.tsx @@ -1,9 +1,11 @@ import React from "react"; import { ScrollView, View, Text } from "react-native"; import type { Meta, StoryObj } from "@storybook/react"; -import { IconAvatar, AppIcons } from "@factorialco/f0-react-native"; +import { IconAvatar } from "./index"; +import { AppIcons } from "../../../icons"; +import { IconType } from "../../Icon"; -const meta = { +const meta: Meta = { title: "Components/Avatars/IconAvatar", component: IconAvatar, argTypes: { @@ -31,7 +33,7 @@ export default meta; type Story = StoryObj; interface AvatarDisplayProps { - icon: any; + icon: IconType; size: "sm" | "md" | "lg"; className?: string; label?: string; @@ -43,10 +45,10 @@ const AvatarDisplay = ({ className, label, }: AvatarDisplayProps) => ( - + {label && ( - + {label} )} @@ -63,10 +65,10 @@ export const Basic: Story = { export const SizeVariants: Story = { render: () => ( - + Size Variants - + @@ -78,10 +80,10 @@ export const SizeVariants: Story = { export const Styling: Story = { render: () => ( - + Custom Styling - + ( - + Different Icons - + = { title: "Components/Avatars/ModuleAvatar", component: ModuleAvatar, argTypes: { @@ -32,59 +34,59 @@ export default meta; type Story = StoryObj; interface SizeVariantProps { - icon: any; + icon: IconType; name: string; size: "sm" | "md" | "lg" | "xl"; } interface IconDisplayProps { - icon: any; + icon: IconType; name: string; } const SizeVariant = ({ icon, name, size }: SizeVariantProps) => ( - {name} + {name} ); const IconDisplay = ({ icon, name }: IconDisplayProps) => ( - + - {name} + {name} ); export const Default: Story = { args: { - icon: ModuleIcons.Home, + module: "home", size: "md", }, }; export const SizeVariants: Story = { args: { - icon: ModuleIcons.Home, + module: "home", }, render: () => ( - Size Variants - + Size Variants + - + - + @@ -95,12 +97,15 @@ export const SizeVariants: Story = { }; export const AllModules: Story = { + args: { + module: "home", + }, render: () => { const moduleEntries = Object.entries(ModuleIcons); return ( - + All Module Avatar Icons diff --git a/packages/react-native-playground/components/Avatars/PersonAvatar/index.stories.tsx b/packages/react-native/src/components/Avatars/PersonAvatar/index.stories.tsx similarity index 89% rename from packages/react-native-playground/components/Avatars/PersonAvatar/index.stories.tsx rename to packages/react-native/src/components/Avatars/PersonAvatar/index.stories.tsx index ed5ee14bb0..25116dffb9 100644 --- a/packages/react-native-playground/components/Avatars/PersonAvatar/index.stories.tsx +++ b/packages/react-native/src/components/Avatars/PersonAvatar/index.stories.tsx @@ -1,8 +1,8 @@ import React from "react"; import { View } from "react-native"; import type { Meta, StoryObj } from "@storybook/react"; -import { PersonAvatar, PersonAvatarProps } from "@factorialco/f0-react-native"; -import { Check } from "@factorialco/f0-react-native/src/icons/app"; +import { PersonAvatar, PersonAvatarProps } from "./index"; +import { Check } from "../../../icons/app"; export const sizes = ["xsmall", "small", "medium", "large", "xlarge"] as const; @@ -11,7 +11,7 @@ const PersonAvatarExample = ( ) => { return ; }; -const meta = { +const meta: Meta = { title: "Components/Avatars/PersonAvatar", component: PersonAvatarExample, argTypes: { diff --git a/packages/react-native-playground/components/Avatars/TeamAvatar/index.stories.tsx b/packages/react-native/src/components/Avatars/TeamAvatar/index.stories.tsx similarity index 88% rename from packages/react-native-playground/components/Avatars/TeamAvatar/index.stories.tsx rename to packages/react-native/src/components/Avatars/TeamAvatar/index.stories.tsx index a9c351443b..f425a032e2 100644 --- a/packages/react-native-playground/components/Avatars/TeamAvatar/index.stories.tsx +++ b/packages/react-native/src/components/Avatars/TeamAvatar/index.stories.tsx @@ -1,8 +1,9 @@ import React from "react"; import { View } from "react-native"; import type { Meta, StoryObj } from "@storybook/react"; -import { TeamAvatar } from "@factorialco/f0-react-native"; -import { Check } from "@factorialco/f0-react-native/src/icons/app"; + +import { Check } from "../../../icons/app"; +import { TeamAvatar } from "."; export const sizes = ["xsmall", "small", "medium", "large", "xlarge"] as const; diff --git a/packages/react-native-playground/components/Avatars/index.stories.tsx b/packages/react-native/src/components/Avatars/index.stories.tsx similarity index 75% rename from packages/react-native-playground/components/Avatars/index.stories.tsx rename to packages/react-native/src/components/Avatars/index.stories.tsx index de42e852ec..f2c178ab08 100644 --- a/packages/react-native-playground/components/Avatars/index.stories.tsx +++ b/packages/react-native/src/components/Avatars/index.stories.tsx @@ -1,17 +1,13 @@ -import { - AppIcons, - CompanyAvatar, - DateAvatar, - FileAvatar, - IconAvatar, - ModuleAvatar, - ModuleIcons, - PersonAvatar, -} from "@factorialco/f0-react-native"; -import { Check } from "@factorialco/f0-react-native/src/icons/app"; +import { Check } from "../../icons/app"; import type { Meta, StoryFn } from "@storybook/react"; -import { icons } from "lucide-react-native"; import { ScrollView, View, Text } from "react-native"; +import { PersonAvatar } from "./PersonAvatar"; +import { DateAvatar } from "./DateAvatar"; +import { FileAvatar } from "./FileAvatar"; +import { ModuleAvatar } from "./ModuleAvatar"; +import { IconAvatar } from "./IconAvatar"; +import { AppIcons, ModuleIcons } from "../../icons"; +import { CompanyAvatar } from "./CompanyAvatar"; const meta: Meta = { title: "Components/Avatars", @@ -21,6 +17,7 @@ const meta: Meta = { decorators: [ (Story: StoryFn) => ( + {/* @ts-expect-error - Storybook type issue with Story component */} ), @@ -59,10 +56,10 @@ export const AvatarsShowCase = () => { ]; return ( - + PersonAvatar - + { badge={{ icon: Check, type: "positive" }} /> - + DateAvatar - + - + FileAvatar - + {fileTypes.map((fileType, index) => ( { /> ))} - + ModuleAvatar - + {moduleEntries.map(([name, icon]) => ( ))} - + IconAvatar - + @@ -123,10 +120,10 @@ export const AvatarsShowCase = () => { - + CompanyAvatar - + ( + {/* @ts-expect-error - Storybook type issue with Story component */} ), @@ -37,10 +38,10 @@ export const ButtonShowcase = () => { return ( {/* Basic Variants */} - + Default Variants - +