├── .nvmrc
├── src
├── loading
│ ├── loading.type.ts
│ └── loading.tsx
├── skeleton
│ ├── skeleton.type.ts
│ └── skeleton.tsx
├── types
│ ├── common.js
│ ├── common.d.ts
│ └── common.ts
├── collapse
│ ├── collapse.type.ts
│ └── collapse.tsx
├── utils
│ ├── index.d.ts
│ ├── index.ts
│ └── index.js
├── Navtabs
│ ├── Navtabs.type.ts
│ └── Navtabs.tsx
├── tabs
│ ├── tabs.type.ts
│ └── tabs.tsx
├── navigationbar
│ ├── navigationbar.type.ts
│ └── navigationbar.tsx
├── segmentedswitchios
│ ├── segmentedswitchios.type.ts
│ └── segmentedswitchios.tsx
├── dialog
│ ├── dialog.type.ts
│ └── dialog.tsx
├── Tile
│ └── Tile.type.ts
├── emptystates
│ ├── emptystates.type.ts
│ └── emptystates.tsx
├── card
│ ├── card.type.ts
│ ├── card.tsx
│ └── section.tsx
├── iosmodalsheets
│ ├── iosmodalsheets.type.ts
│ └── iosmodalsheets.tsx
├── statusbutton
│ ├── statusbutton.type.ts
│ └── statusbutton.tsx
├── bottomnav
│ ├── bottomnav.type.ts
│ └── bottomnav.tsx
├── androidmodalsheets
│ ├── androidmodalsheets.type.ts
│ └── androidmodalsheets.tsx
├── select
│ ├── select.type.ts
│ └── select.tsx
├── socialmediastack
│ ├── socialmediastack.tsx
│ ├── socialmediastack.type.ts
│ ├── storyprogressbar.tsx
│ ├── socialmediaengagement.tsx
│ └── socialmediacontainer.tsx
├── Input
│ ├── Input.type.ts
│ ├── NormalInput.tsx
│ ├── Input.tsx
│ └── Autocomplete.tsx
├── bottomsheet
│ ├── bottomsheet.type.ts
│ └── bottomsheet.tsx
├── Button
│ ├── Button.type.ts
│ ├── rect.tsx
│ ├── icon.tsx
│ ├── Button.tsx
│ └── typeDoc.tsx
├── index.tsx
├── grid
│ ├── Grid.type.ts
│ └── grid.tsx
├── alert
│ ├── alert.type.ts
│ └── alert.tsx
├── socialbuttons
│ ├── socialbuttons.type.ts
│ ├── socialbuttons.tsx
│ └── sociallogin.tsx
├── iconography
│ ├── iconography.type.ts
│ └── iconography.tsx
├── sociallogin.tsx
├── index.ts
├── logo.svg
└── styles
│ └── global.css
├── postcss.config.cjs
├── nativewind-env.d.ts
├── babel.config.cjs
├── lefthook.yml
├── .github
├── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── feature-request.yml
│ └── bug-report.yml
└── PULL_REQUEST_TEMPLATE.md
├── .editorconfig
├── .gitignore
├── tsconfig.json
├── LICENSE
├── SECURITY.md
├── tsconfig.build.json
├── CONTRIBUTING.md
├── package.json
├── CODE_OF_CONDUCT.md
└── allprops.ts
/.nvmrc:
--------------------------------------------------------------------------------
1 | v20
2 |
--------------------------------------------------------------------------------
/src/loading/loading.type.ts:
--------------------------------------------------------------------------------
1 | export interface LoadingProps {
2 | description?: string;
3 | }
4 |
--------------------------------------------------------------------------------
/src/skeleton/skeleton.type.ts:
--------------------------------------------------------------------------------
1 | export interface SkeletonProps {
2 | className?: string;
3 | }
4 |
--------------------------------------------------------------------------------
/src/types/common.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 |
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/nativewind-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // NOTE: This file should not be edited and should be committed with your source code. It is generated by NativeWind.
--------------------------------------------------------------------------------
/src/collapse/collapse.type.ts:
--------------------------------------------------------------------------------
1 | export interface CollapsibleProps {
2 | title: string;
3 | text?: string;
4 | initialCollapsed?: boolean;
5 | children?: React.ReactNode;
6 | }
7 |
--------------------------------------------------------------------------------
/src/utils/index.d.ts:
--------------------------------------------------------------------------------
1 | export { default as createStyle, useStyle, mergeStyles, isNativeWindAvailable } from './styleCompat';
2 | export type { StyleCompatProps, RNStyle } from './styleCompat';
3 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | // Main compatibility utilities
2 | export { default as createStyle, useStyle, mergeStyles, isNativeWindAvailable } from './styleCompat';
3 | export type { StyleCompatProps, RNStyle } from './styleCompat';
4 |
--------------------------------------------------------------------------------
/babel.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = function (api) {
2 | api.cache(true);
3 | return {
4 | presets: [
5 | ["babel-preset-expo", { jsxImportSource: "nativewind" }],
6 | "nativewind/babel",
7 | ],
8 | };
9 | };
--------------------------------------------------------------------------------
/src/Navtabs/Navtabs.type.ts:
--------------------------------------------------------------------------------
1 | import { ContainerComponentProps } from '../types/common';
2 |
3 | export interface NavTabProps extends ContainerComponentProps {
4 | tabs: string[];
5 | initialActiveIndex?: number;
6 | onTabChange?: (index: number) => void;
7 | }
8 |
--------------------------------------------------------------------------------
/src/tabs/tabs.type.ts:
--------------------------------------------------------------------------------
1 | type MessageType = 'none' | 'help' | 'error';
2 |
3 | export interface TabsProps {
4 | label: string;
5 | placeholder?: string;
6 | value?: string;
7 | messageType?: MessageType;
8 | message?: string;
9 | onPress?: () => void;
10 | }
--------------------------------------------------------------------------------
/src/navigationbar/navigationbar.type.ts:
--------------------------------------------------------------------------------
1 | export interface NavigationBarProps {
2 | title: string;
3 | device?: 'ios' | 'android';
4 | action?: 1 | 2 | 3;
5 | size?: 'compact' | 'expandable';
6 | onClose?: () => void;
7 | onClipboard?: () => void;
8 | onForward?: () => void;
9 | }
--------------------------------------------------------------------------------
/src/segmentedswitchios/segmentedswitchios.type.ts:
--------------------------------------------------------------------------------
1 | export interface SegmentedSwitchIOSProps {
2 | title: string;
3 | items: string[];
4 | selectedIndex?: number | null;
5 | messageType?: 'none' | 'help' | 'error';
6 | message?: string;
7 | onChange?: (index: number) => void;
8 | }
--------------------------------------------------------------------------------
/src/dialog/dialog.type.ts:
--------------------------------------------------------------------------------
1 | import { ContainerComponentProps } from '../types/common';
2 |
3 | export interface DialogProps extends ContainerComponentProps {
4 | title: string;
5 | description: string;
6 | buttonLabel: string;
7 | onButtonPress: () => void;
8 | children: React.ReactNode;
9 | }
10 |
--------------------------------------------------------------------------------
/src/Tile/Tile.type.ts:
--------------------------------------------------------------------------------
1 | export type TileProps = {
2 | w?: number;
3 | h?: number;
4 | label: string;
5 | paragraph?: string;
6 | variant?: "variant-1" | "variant-2" | "variant-3" | "variant-4" | "variant-5" | "variant-6";
7 | icon?: "sheart" | "heart" | "box" | "label";
8 | enabled?: boolean;
9 | selected?: boolean;
10 | };
--------------------------------------------------------------------------------
/src/emptystates/emptystates.type.ts:
--------------------------------------------------------------------------------
1 | import { ReactNode } from "react";
2 |
3 | type ActionType = "none" | "primary" | "secondary";
4 |
5 | export interface EmptyStateProps {
6 | message: string;
7 | description?: string;
8 | label?: string;
9 | onPress?: () => void;
10 | action?: ActionType;
11 | children?: ReactNode;
12 | }
--------------------------------------------------------------------------------
/lefthook.yml:
--------------------------------------------------------------------------------
1 | pre-commit:
2 | parallel: true
3 | commands:
4 | lint:
5 | glob: "*.{js,ts,jsx,tsx}"
6 | run: npx eslint {staged_files}
7 | types:
8 | glob: "*.{js,ts, jsx, tsx}"
9 | run: npx tsc
10 | commit-msg:
11 | parallel: true
12 | commands:
13 | commitlint:
14 | run: npx commitlint --edit
15 |
--------------------------------------------------------------------------------
/src/card/card.type.ts:
--------------------------------------------------------------------------------
1 | import { ReactNode } from "react";
2 |
3 | export interface CardProps {
4 | title?: string;
5 | label?: string;
6 | cardTitle?: boolean;
7 | button?: boolean;
8 | children?: ReactNode;
9 | }
10 | export interface CardSectionProps {
11 | title?: string;
12 | icon?: boolean;
13 | onPress?: () => void;
14 | }
15 |
--------------------------------------------------------------------------------
/src/iosmodalsheets/iosmodalsheets.type.ts:
--------------------------------------------------------------------------------
1 | import { ReactNode } from 'react';
2 |
3 | export interface IOSModalSheetProps {
4 | title: string;
5 | label: string;
6 | action?: 1 | 2 | 3;
7 | compact?: boolean;
8 | onClose?: () => void;
9 | onButtonPress?: () => void;
10 | onClipboard?: () => void;
11 | onForward?: () => void;
12 | children?: ReactNode;
13 | }
--------------------------------------------------------------------------------
/src/statusbutton/statusbutton.type.ts:
--------------------------------------------------------------------------------
1 | type StatusType = 'information' | 'success' | 'warning' | 'error';
2 | type ButtonType = 'primary' | 'subtle';
3 |
4 | export interface StatusButtonProps {
5 | label?: string;
6 | type?: StatusType;
7 | buttonType?: ButtonType;
8 | iconLeft?: boolean;
9 | iconRight?: boolean;
10 | iconOnly?: boolean;
11 | onPress?: () => void;
12 | }
--------------------------------------------------------------------------------
/src/bottomnav/bottomnav.type.ts:
--------------------------------------------------------------------------------
1 | export interface NavItemProps {
2 | icon: string;
3 | label: string;
4 | active: boolean;
5 | theme?: "light" | "dark";
6 | onPress: () => void;
7 | }
8 |
9 | export interface BottomNavProps {
10 | items: Array<{
11 | icon: string;
12 | label: string;
13 | }>;
14 | theme?: "light" | "dark";
15 | initialActiveIndex?: number;
16 | onItemPress?: (index: number) => void;
17 | }
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 | contact_links:
3 | - name: ✨ Feature Request
4 | url: https://github.com/gaureshpai/reactnativeepictrailsds/discussions/new?category=ideas
5 | about: Suggest new features in the Ideas discussion category.
6 |
7 | - name: 💬 General Discussion & Help
8 | url: https://github.com/gaureshpai/reactnativeepictrailsds/discussions
9 | about: Ask questions or start general discussions here.
--------------------------------------------------------------------------------
/src/androidmodalsheets/androidmodalsheets.type.ts:
--------------------------------------------------------------------------------
1 | import { ReactNode } from 'react';
2 | import { ContainerComponentProps } from '../types/common';
3 |
4 | export interface AndroidModalSheetProps extends ContainerComponentProps {
5 | title: string;
6 | label: string;
7 | action?: 1 | 2 | 3;
8 | compact?: boolean;
9 | onClose?: () => void;
10 | onButtonPress?: () => void;
11 | onClipboard?: () => void;
12 | onForward?: () => void;
13 | children?: ReactNode;
14 | }
15 |
--------------------------------------------------------------------------------
/src/select/select.type.ts:
--------------------------------------------------------------------------------
1 | export type SelectValue = {
2 | value: string;
3 | label: string;
4 | flagCode?: string;
5 | };
6 |
7 | type MessageType = 'none' | 'help' | 'error';
8 |
9 | export interface SelectProps {
10 | label: string;
11 | values: SelectValue[];
12 | placeholder?: string;
13 | prefix?: boolean;
14 | messageType?: MessageType;
15 | message?: string;
16 | onChange?: (value: SelectValue) => void;
17 | disabled?: boolean;
18 | containerStyle?: any;
19 | }
--------------------------------------------------------------------------------
/src/socialmediastack/socialmediastack.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, StyleSheet } from 'react-native';
3 | import { SocialMediaStackProps } from './socialmediastack.type';
4 |
5 | const SocialMediaStack: React.FC = ({ children }) => {
6 | return (
7 |
8 | {children}
9 |
10 | );
11 | };
12 |
13 | const styles = StyleSheet.create({
14 | container: {
15 | flex: 1,
16 | width: '100%',
17 | height: '100%',
18 | position: 'relative',
19 | },
20 | });
21 |
22 | export default SocialMediaStack;
--------------------------------------------------------------------------------
/src/Input/Input.type.ts:
--------------------------------------------------------------------------------
1 | import { TextInputProps } from "react-native";
2 |
3 | export type NInputProp = TextInputProps & {
4 | Size?: "small" | "medium" | "large" | "full" | "fit";
5 | Label?: string;
6 | Hint?: string;
7 | State?: "Default" | "Active" | "Correct" | "ViewOnly" | "Loading" | "Disabled" | "Incorrect";
8 | curved?: boolean
9 | };
10 |
11 | export type AInputProp = TextInputProps & {
12 | Size?: "small" | "medium" | "large" | "full" | "fit";
13 | Label?: string;
14 | Hint?: string;
15 | State?: "Default" | "Error" | "Success" | "Loading";
16 | input?: string[];
17 | curved?: boolean
18 | };
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent coding styles
2 | # across different editors and IDEs: editorconfig.org
3 |
4 | root = true
5 |
6 | [*]
7 | indent_style = space
8 | indent_size = 2
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 |
14 | [*.{js,jsx,ts,tsx}]
15 | indent_style = space
16 | indent_size = 2
17 |
18 | [*.json]
19 | indent_style = space
20 | indent_size = 2
21 | quote_type = double
22 |
23 | [*.md]
24 | trim_trailing_whitespace = false
25 |
26 | [*.{yaml,yml}]
27 | indent_style = space
28 | indent_size = 2
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2 |
3 | # dependencies
4 | node_modules/
5 | example/node_modules/
6 | *.tgz
7 | .npmrc
8 |
9 | # Expo
10 | example/.expo/
11 | example
12 | dist/
13 | web-build/
14 | expo-env.d.ts
15 |
16 | # Native
17 | *.orig.*
18 | *.jks
19 | *.p8
20 | *.p12
21 | *.key
22 | *.mobileprovision
23 |
24 | # Metro
25 | .metro-health-check*
26 |
27 | # debug
28 | npm-debug.*
29 | yarn-debug.*
30 | yarn-error.*
31 |
32 | # macOS
33 | .DS_Store
34 | *.pem
35 |
36 | # local env files
37 | .env*.local
38 |
39 | # TypeScript
40 | *.tsbuildinfo
41 |
--------------------------------------------------------------------------------
/src/bottomsheet/bottomsheet.type.ts:
--------------------------------------------------------------------------------
1 | import { ReactNode } from "react";
2 |
3 | export interface Comment {
4 | id?: string | number;
5 | userInitials?: string;
6 | userName?: string;
7 | likes?: number;
8 | time?: string;
9 | text?: string;
10 | }
11 |
12 | export interface BottomSheetProps {
13 | showKnob?: boolean;
14 | showButton?: boolean;
15 | buttonLabel?: string;
16 | onButtonPress?: () => void;
17 | showComments?: boolean;
18 | comments?: Comment[];
19 | onAddComment?: (text: string) => void;
20 | onLikeComment?: (index: number) => void;
21 | onReplyComment?: (index: number) => void;
22 | children?: ReactNode;
23 | containerStyle?: object;
24 | }
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🚀 Pull Request
3 | about: Submit a code change or improvement.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | ## 📌 Description
10 |
11 |
12 |
13 | Fixes: #
14 |
15 | ## 🔧 Changes
16 |
17 |
18 | -
19 | -
20 | -
21 |
22 | ## ✅ Checklist
23 |
24 | - [ ] Code follows project style
25 | - [ ] Tested and working
26 | - [ ] Docs updated (if needed)
27 |
28 | ## 🧪 Testing Steps
29 |
30 |
31 | 1.
32 | 2.
33 |
34 | ## 📝 Notes
35 |
36 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo/tsconfig.base",
3 | "compilerOptions": {
4 | "jsx": "react-native",
5 | "esModuleInterop": true,
6 | "allowSyntheticDefaultImports": true,
7 | "moduleResolution": "node",
8 | "resolveJsonModule": true,
9 | "strict": false,
10 | "skipLibCheck": true,
11 | "noEmitOnError": false,
12 | "ignoreDeprecations": "6.0",
13 | "target": "es2019",
14 | "lib": ["es2019", "dom"],
15 | "paths": {
16 | "@/*": [
17 | "./*"
18 | ]
19 | }
20 | },
21 | "include": [
22 | "nativewind-env.d.ts",
23 | "./src/**/*"
24 | ],
25 | "exclude": [
26 | "node_modules",
27 | "dist",
28 | "example"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/src/Button/Button.type.ts:
--------------------------------------------------------------------------------
1 | import { PressableProps } from "react-native";
2 | import { PressableComponentProps, ComponentSize, ComponentState } from '../types/common';
3 |
4 | export type ButtonProps = PressableProps & PressableComponentProps & {
5 | label: string;
6 | variant?: "primary" | "secondary";
7 | size?: ComponentSize;
8 | icon?: "right" | "left";
9 | state?: ComponentState;
10 | background?: string | null;
11 | };
12 |
13 | export type TypeDocsProps = PressableProps & PressableComponentProps & {
14 | icon?: "left" | "right";
15 | buttonLabel?: string;
16 | label?: string;
17 | size?: ComponentSize;
18 | state?: ComponentState;
19 | background?: string | null;
20 | onClick?: () => void;
21 | };
22 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import "./styles/global.css";
2 |
3 | export * from './alert';
4 | export * from './androidmodalsheets';
5 | export * from './bottomnav';
6 | export * from './bottomsheet';
7 | export * from './Button';
8 | export * from './card';
9 | export * from './collapse';
10 | export * from './dialog';
11 | export * from './emptystates';
12 | export * from './grid';
13 | export * from './iconography';
14 | export * from './Input';
15 | export * from './iosmodalsheets';
16 | export * from './loading';
17 | export * from './navigationbar';
18 | export * from './Navtabs';
19 | export * from './segmentedswitchios';
20 | export * from './select';
21 | export * from './skeleton';
22 | export * from './socialbuttons';
23 | export * from './socialmediastack';
24 | export * from './statusbutton';
25 | export * from './tabs';
26 | export * from './Tile';
--------------------------------------------------------------------------------
/src/grid/Grid.type.ts:
--------------------------------------------------------------------------------
1 | import { ReactNode } from "react";
2 | import { ContainerComponentProps } from '../types/common';
3 |
4 | export type Breakpoint = "xs" | "sm" | "md" | "lg" | "xl";
5 | export type ResponsiveValue = T | Partial>;
6 |
7 | export interface GridProps extends ContainerComponentProps {
8 | children: ReactNode;
9 | container?: boolean;
10 | spacing?: ResponsiveValue;
11 | rowSpacing?: ResponsiveValue;
12 | columnSpacing?: ResponsiveValue;
13 | columns?: ResponsiveValue;
14 | rows?: ResponsiveValue;
15 | direction?: ResponsiveValue<"row" | "column">;
16 | size?: ResponsiveValue;
17 | }
18 |
19 | export interface ItemProps extends ContainerComponentProps {
20 | children: ReactNode;
21 | xs?: number;
22 | sm?: number;
23 | md?: number;
24 | lg?: number;
25 | xl?: number;
26 | size?: number;
27 | }
28 |
--------------------------------------------------------------------------------
/src/socialmediastack/socialmediastack.type.ts:
--------------------------------------------------------------------------------
1 | // Improved type for image content to handle both remote and local images
2 | export type ImageContent = {
3 | type: 'image';
4 | imageUri: string;
5 | isLocal?: boolean;
6 | };
7 |
8 | export type VideoContent = {
9 | type: 'video';
10 | uri: string;
11 | };
12 |
13 | export type TextContent = {
14 | type: 'text';
15 | text: string;
16 | backgroundColor?: string;
17 | };
18 |
19 | export type ContentItem = VideoContent | ImageContent | TextContent;
20 |
21 | export interface SocialMediaContainerProps {
22 | contentItems: ContentItem[];
23 | duration?: number;
24 | onActiveIndexChange?: (index: number) => void;
25 | index?: number;
26 | }
27 |
28 |
29 | export interface SocialMediaStackProps {
30 | children: React.ReactNode;
31 | }
32 | export interface StoryProgressBarsProps {
33 | totalStories: number;
34 | activeStoryIndex: number;
35 | }
36 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | exports.isNativeWindAvailable = exports.mergeStyles = exports.useStyle = exports.createStyle = void 0;
7 | // Main compatibility utilities
8 | var styleCompat_1 = require("./styleCompat");
9 | Object.defineProperty(exports, "createStyle", { enumerable: true, get: function () { return __importDefault(styleCompat_1).default; } });
10 | Object.defineProperty(exports, "useStyle", { enumerable: true, get: function () { return styleCompat_1.useStyle; } });
11 | Object.defineProperty(exports, "mergeStyles", { enumerable: true, get: function () { return styleCompat_1.mergeStyles; } });
12 | Object.defineProperty(exports, "isNativeWindAvailable", { enumerable: true, get: function () { return styleCompat_1.isNativeWindAvailable; } });
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 paigauresh
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | SOFTWARE.
21 |
--------------------------------------------------------------------------------
/src/alert/alert.type.ts:
--------------------------------------------------------------------------------
1 | const alertStyles = {
2 | information: {
3 | backgroundColor: "#E8E8E8",
4 | color: "#1F1F1F",
5 | secondaryColor: "#DDDDDD",
6 | secondaryTextColor: "#1F1F1F",
7 | icon: "information-circle",
8 | },
9 | success: {
10 | backgroundColor: "#D3EFDA",
11 | color: "#166C3B",
12 | secondaryColor: "#B1EAC2",
13 | secondaryTextColor: "#166C3B",
14 | icon: "checkmark-circle",
15 | },
16 | warning: {
17 | backgroundColor: "#FEE2D4",
18 | color: "#C54600",
19 | secondaryColor: "#FFD3BC",
20 | secondaryTextColor: "#C54600",
21 | icon: "warning",
22 | },
23 | error: {
24 | backgroundColor: "#FFE1DE",
25 | color: "#950F22",
26 | secondaryColor: "#FFD2CD",
27 | secondaryTextColor: "#950F22",
28 | icon: "alert-circle",
29 | },
30 | };
31 |
32 | type AlertType = "information" | "success" | "warning" | "error";
33 |
34 | interface AlertProps {
35 | message: string;
36 | label?: string;
37 | description?: string;
38 | icon?: boolean;
39 | inline?: boolean;
40 | suppressed?: boolean;
41 | type?: AlertType;
42 | onPrimaryPress?: () => void;
43 | onSecondaryPress?: () => void;
44 | }
45 |
46 | export { alertStyles, AlertProps };
--------------------------------------------------------------------------------
/src/socialmediastack/storyprogressbar.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, StyleSheet } from 'react-native';
3 | import { StoryProgressBarsProps } from './socialmediastack.type';
4 |
5 | const StoryProgressBar: React.FC = ({ totalStories, activeStoryIndex }) => {
6 | return (
7 |
8 | {Array.from({ length: totalStories }).map((_, index) => (
9 |
16 | ))}
17 |
18 | );
19 | };
20 |
21 | const styles = StyleSheet.create({
22 | progressBarsContainer: {
23 | position: 'absolute',
24 | top: 10,
25 | left: 0,
26 | right: 0,
27 | flexDirection: 'row',
28 | paddingHorizontal: 20,
29 | zIndex: 10,
30 | },
31 | progressBar: {
32 | flex: 1,
33 | height: 5,
34 | marginHorizontal: 2,
35 | },
36 | activeProgressBar: {
37 | backgroundColor: '#FFFFFF',
38 | },
39 | inactiveProgressBar: {
40 | backgroundColor: '#ffffff75',
41 | },
42 | });
43 |
44 | export default StoryProgressBar;
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.yml:
--------------------------------------------------------------------------------
1 | name: "🚀 Feature Request"
2 | description: Suggest an idea or enhancement for this project.
3 | labels: [enhancement]
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | # 🚀 Feature Request
9 |
10 | Thanks for suggesting a feature!
11 |
12 | - type: textarea
13 | id: problem-statement
14 | attributes:
15 | label: Problem
16 | description: What problem are you trying to solve?
17 | placeholder: I'm always frustrated when...
18 | validations:
19 | required: true
20 |
21 | - type: textarea
22 | id: solution
23 | attributes:
24 | label: Proposed Solution
25 | description: Describe the solution you’d like.
26 | placeholder: A possible solution would be...
27 | validations:
28 | required: true
29 |
30 | - type: textarea
31 | id: alternatives
32 | attributes:
33 | label: Alternatives Considered
34 | description: Have you considered other solutions?
35 | placeholder: Another approach could be...
36 | validations:
37 | required: false
38 |
39 | - type: textarea
40 | id: context
41 | attributes:
42 | label: Additional Context
43 | description: Add any extra context or links.
--------------------------------------------------------------------------------
/src/socialbuttons/socialbuttons.type.ts:
--------------------------------------------------------------------------------
1 | interface SocialButtonProps {
2 | provider: "email" | "google" | "facebook" | "apple";
3 | onPress: () => void;
4 | }
5 |
6 | const providerConfig = {
7 | email: {
8 | text: "Sign in with E-mail",
9 | iconName: "mail",
10 | backgroundColor: "bg-gray-200",
11 | textColor: "text-black",
12 | iconColor: "black",
13 | },
14 | google: {
15 | text: "Sign in with Google",
16 | iconName: "logo-google",
17 | backgroundColor: "bg-gray-200",
18 | textColor: "text-black",
19 | iconColor: "#4285F4",
20 | },
21 | facebook: {
22 | text: "Sign in with Facebook",
23 | iconName: "logo-facebook",
24 | backgroundColor: "bg-gray-200",
25 | textColor: "text-black",
26 | iconColor: "#1877F2",
27 | },
28 | apple: {
29 | text: "Sign in with Apple",
30 | iconName: "logo-apple",
31 | backgroundColor: "bg-black",
32 | textColor: "text-white",
33 | iconColor: "white",
34 | },
35 | };
36 |
37 | interface SocialLoginProps {
38 | email?: boolean;
39 | google?: boolean;
40 | facebook?: boolean;
41 | apple?: boolean;
42 | onEmailSignIn?: () => void;
43 | onGoogleSignIn?: () => void;
44 | onFacebookSignIn?: () => void;
45 | onAppleSignIn?: () => void;
46 | }
47 |
48 | export { SocialButtonProps, providerConfig, SocialLoginProps };
--------------------------------------------------------------------------------
/src/iconography/iconography.type.ts:
--------------------------------------------------------------------------------
1 | export const iconMap: Record = {
2 | bell: "notifications-outline",
3 | calendar: "calendar-outline",
4 | comment: "chatbubble-outline",
5 | email: "mail-outline",
6 | grid: "grid-outline",
7 | phone: "call-outline",
8 | plus: "add-outline",
9 | "chevron-left": "chevron-back-outline",
10 | check: "checkmark-outline",
11 | envelope: "mail-outline",
12 | link: "link-outline",
13 | "chevron-right": "chevron-forward-outline",
14 | x: "close-outline",
15 | "info-circle": "information-circle-outline",
16 | at: "at-outline",
17 | ellipsis: "ellipsis-horizontal-outline",
18 | search: "search-outline",
19 | "phone-handset": "call-outline",
20 | "check-small": "checkmark-outline",
21 | };
22 |
23 | export type IconName = keyof typeof iconMap;
24 |
25 | export type PlaceholderSize =
26 | | "xl"
27 | | "large"
28 | | "medium"
29 | | "regular"
30 | | "small"
31 | | "xs"
32 | | "xxs"
33 | | "tiny";
34 |
35 | export type AvatarSize = "xl" | "large" | "medium" | "small" | "xs" | "xxs";
36 |
37 | export interface IconComponentProps {
38 | type: "icon" | "placeholder" | "avatar";
39 | className?: string;
40 | iconName?: IconName;
41 | size?: number;
42 | theme?: "light" | "dark";
43 | placeholderSize?: PlaceholderSize;
44 | filled?: boolean;
45 | avatarSize?: AvatarSize;
46 | initials?: string;
47 | }
48 |
49 | export const sizeMapping: Record = {
50 | xl: 48,
51 | large: 40,
52 | medium: 32,
53 | regular: 24,
54 | small: 20,
55 | xs: 16,
56 | xxs: 12,
57 | tiny: 8,
58 | };
--------------------------------------------------------------------------------
/src/socialbuttons/socialbuttons.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text, TouchableOpacity } from 'react-native';
3 | import Ionicons from '@expo/vector-icons/Ionicons';
4 | import { SocialButtonProps, providerConfig, SocialLoginProps } from './socialbuttons.type';
5 |
6 | const SocialButton: React.FC = ({ provider, onPress }) => {
7 | const config = providerConfig[provider];
8 |
9 | return (
10 |
14 |
15 |
16 | {config.text}
17 |
18 |
19 |
20 | );
21 | };
22 |
23 | const SocialLogin: React.FC = ({
24 | email = true,
25 | google = false,
26 | facebook = false,
27 | apple = false,
28 | onEmailSignIn = () => { },
29 | onGoogleSignIn = () => { },
30 | onFacebookSignIn = () => { },
31 | onAppleSignIn = () => { },
32 | }) => {
33 | return (
34 |
35 | {(email && )}
36 | {(google && )}
37 | {(facebook && )}
38 | {(apple && )}
39 |
40 | );
41 | };
42 |
43 | export default SocialLogin;
--------------------------------------------------------------------------------
/src/socialmediastack/socialmediaengagement.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, TouchableOpacity, StyleSheet } from 'react-native';
3 | import { Ionicons } from "@expo/vector-icons";
4 |
5 | const SocialMediaEngagement = ({
6 | icons = [
7 | { name: 'heart-outline', onPress: () => console.log('Like pressed') },
8 | { name: 'chatbox-outline', onPress: () => console.log('Comment pressed') },
9 | { name: 'paper-plane-outline', onPress: () => console.log('Share pressed') },
10 | ],
11 | iconColor = '#ffffff',
12 | }) => {
13 | return (
14 |
15 |
16 | {icons.map((icon, index) => (
17 |
22 |
23 |
24 | ))}
25 |
26 |
27 | );
28 | };
29 |
30 | const styles = StyleSheet.create({
31 | container: {
32 | position: 'absolute',
33 | right: 15,
34 | bottom: 120,
35 | borderRadius: 8,
36 | padding: 8,
37 | width: 60,
38 | alignItems: 'center',
39 | zIndex: 999,
40 | },
41 | title: {
42 | fontSize: 12,
43 | fontWeight: 'bold',
44 | textAlign: 'center',
45 | marginBottom: 10,
46 | width: '100%',
47 | },
48 | iconsContainer: {
49 | width: '100%',
50 | },
51 | iconButton: {
52 | width: '100%',
53 | backgroundColor: '#ffffff75',
54 | marginBottom: 18,
55 | alignItems: 'center',
56 | paddingVertical: 12,
57 | marginVertical: 2,
58 | }
59 | });
60 |
61 | export default SocialMediaEngagement;
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # 🔒 Security Policy
2 |
3 | ## 🛠 Supported Versions
4 |
5 | We currently **maintain active support only for the latest stable release** of the design system. Versions prior to `1.0.0` have been **deprecated** and will not receive updates, including security patches.
6 |
7 | | Version | Status |
8 | | ---------- | ------------------------- |
9 | | `>= 1.0.0` | ✅ Supported (Active) |
10 | | `< 1.0.0` | ❌ Deprecated (No Support) |
11 |
12 | > The `1.x` release line marks the foundation for long-term support, stability, and forward compatibility. All critical fixes, patches, and features will be based on `v1.0.0` and above.
13 |
14 | ## 🛡 Reporting a Vulnerability
15 |
16 | If you discover a security vulnerability in **`reactnativeepictrailsds`**, please report it responsibly and privately so that we can address the issue promptly.
17 |
18 | ### 📩 How to Report
19 |
20 | Please send a detailed email to:
21 |
22 | 📧 **[paigauresh@gmail.com](mailto:paigauresh@gmail.com)**
23 | Subject: **Security Issue — reactnativeepictrailsds**
24 |
25 | Include the following information:
26 |
27 | * Clear steps to reproduce the issue
28 | * Affected package versions
29 | * Impact assessment (data leak, crash, etc.)
30 | * Optional: Any suggestions for remediation
31 |
32 | ### 🔄 What to Expect
33 |
34 | * **Acknowledgment**: You will receive a response within **2 business days**
35 | * **Assessment & Patch**: We aim to investigate and patch issues swiftly
36 | * **Disclosure**: If necessary, we will publish a public advisory in a responsible and transparent manner (with credit to reporters if desired)
37 |
38 | ## ⚠️ Responsible Disclosure Guidelines
39 |
40 | We **strongly encourage responsible disclosure**:
41 |
42 | * Please **do not** publicly disclose or discuss the vulnerability until a patch is released.
43 | * Avoid posting issues directly on GitHub if they contain sensitive security details.
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2019",
4 | "module": "commonjs",
5 | "lib": ["es2019"],
6 | "moduleResolution": "node",
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "resolveJsonModule": true,
10 | "strict": false,
11 | "skipLibCheck": true,
12 | "noEmitOnError": false,
13 | "declaration": true,
14 | "declarationMap": false,
15 | "jsx": "react-native",
16 | "outDir": "dist/commonjs"
17 | },
18 | "include": [
19 | "src/utils/styleCompat.ts",
20 | "src/utils/index.ts",
21 | "src/types/common.ts",
22 | "src/alert/alert.tsx",
23 | "src/alert/alert.type.ts",
24 | "src/androidmodalsheets/androidmodalsheets.tsx",
25 | "src/androidmodalsheets/androidmodalsheets.type.ts",
26 | "src/bottomnav/bottomnav.tsx",
27 | "src/bottomnav/bottomnav.type.ts",
28 | "src/Button/Button.tsx",
29 | "src/Button/Button.type.ts",
30 | "src/Button/icon.tsx",
31 | "src/Button/rect.tsx",
32 | "src/Button/typeDoc.tsx",
33 | "src/card/card.tsx",
34 | "src/card/card.type.ts",
35 | "src/card/section.tsx",
36 | "src/collapse/collapse.tsx",
37 | "src/collapse/collapse.type.ts",
38 | "src/dialog/dialog.tsx",
39 | "src/dialog/dialog.type.ts",
40 | "src/emptystates/emptystates.tsx",
41 | "src/emptystates/emptystates.type.ts",
42 | "src/grid/grid.tsx",
43 | "src/grid/Grid.type.ts",
44 | "src/Input/Input.tsx",
45 | "src/Input/Input.type.ts",
46 | "src/loading/loading.tsx",
47 | "src/loading/loading.type.ts",
48 | "src/Navtabs/Navtabs.tsx",
49 | "src/Navtabs/Navtabs.type.ts",
50 | "src/select/select.tsx",
51 | "src/select/select.type.ts",
52 | "src/skeleton/skeleton.tsx",
53 | "src/skeleton/skeleton.type.ts",
54 | "src/tabs/tabs.tsx",
55 | "src/tabs/tabs.type.ts",
56 | "src/sociallogin.tsx",
57 | "src/Tile/tile.tsx",
58 | "src/Tile/Tile.type.ts",
59 | "src/index.ts"
60 | ],
61 | "exclude": [
62 | "node_modules",
63 | "dist"
64 | ]
65 | }
66 |
--------------------------------------------------------------------------------
/src/loading/loading.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text, ActivityIndicator } from 'react-native';
3 | import { createStyle } from '../utils/styleCompat';
4 | import { ContainerComponentProps } from '../types/common';
5 |
6 | export interface LoadingProps extends ContainerComponentProps {
7 | description?: string;
8 | }
9 |
10 | const Loading: React.FC = ({
11 | description = "Please wait, content of the page is loading...",
12 | className,
13 | style,
14 | ...props
15 | }) => {
16 | const containerStyle = createStyle({
17 | className: `${className || ''} flex-1 items-center justify-center w-full h-full bg-white`,
18 | style: [
19 | {
20 | flex: 1,
21 | alignItems: 'center',
22 | justifyContent: 'center',
23 | width: '100%',
24 | height: '100%',
25 | backgroundColor: 'white'
26 | },
27 | style
28 | ].filter(Boolean)
29 | });
30 |
31 | const indicatorContainerStyle = createStyle({
32 | className: "w-20 h-20",
33 | style: {
34 | width: 80,
35 | height: 80
36 | }
37 | });
38 |
39 | const indicatorWrapperStyle = createStyle({
40 | className: "w-full h-full items-center justify-center",
41 | style: {
42 | width: '100%',
43 | height: '100%',
44 | alignItems: 'center',
45 | justifyContent: 'center'
46 | }
47 | });
48 |
49 | const textStyle = createStyle({
50 | className: "text-gray-500 text-center px-4",
51 | style: {
52 | color: '#6b7280',
53 | textAlign: 'center',
54 | paddingHorizontal: 16
55 | }
56 | });
57 |
58 | return (
59 |
60 |
61 |
62 |
66 |
67 |
68 |
69 |
70 | {description}
71 |
72 |
73 | );
74 | };
75 |
76 | export default Loading;
77 |
78 |
--------------------------------------------------------------------------------
/src/card/card.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { View, Text } from "react-native";
3 | import { createStyle } from '../utils/styleCompat';
4 | import { ContainerComponentProps } from '../types/common';
5 |
6 | export interface CardProps extends ContainerComponentProps {
7 | title?: string;
8 | subtitle?: string;
9 | cardTitle?: boolean;
10 | content?: React.ReactNode;
11 | backgroundColor?: string;
12 | }
13 |
14 | const Card: React.FC = ({
15 | title,
16 | subtitle,
17 | cardTitle = true,
18 | children,
19 | content,
20 | backgroundColor = 'white',
21 | className,
22 | style,
23 | ...props
24 | }) => {
25 | const containerStyle = createStyle({
26 | className: "px-4 bg-white overflow-hidden border border-gray-200 shadow-sm mb-4 w-[80vw]",
27 | style: [{ backgroundColor }, style].filter(Boolean)
28 | });
29 |
30 | const headerStyle = createStyle({
31 | className: `flex ${cardTitle ? "flex-row" : "flex-row-reverse"} justify-between px-0 py-2`
32 | });
33 |
34 | const titleContainerStyle = createStyle({
35 | className: "px-0 py-2"
36 | });
37 |
38 | const titleTextStyle = createStyle({
39 | className: "font-medium text-gray-800"
40 | });
41 |
42 | const contentContainerStyle = createStyle({
43 | className: "relative mb-4 p-4 bg-green-50 min-h-32 flex-grow"
44 | });
45 |
46 | return (
47 |
48 |
49 | {title && (
50 |
51 | {title}
52 |
53 | )}
54 |
55 | {subtitle && (
56 |
57 |
58 | {subtitle}
59 |
60 |
61 | )}
62 |
63 |
64 |
65 | {children}
66 | {content}
67 |
68 |
69 | );
70 | };
71 |
72 | export default Card;
73 |
--------------------------------------------------------------------------------
/src/card/section.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text, TouchableOpacity } from 'react-native';
3 | import { Ionicons } from '@expo/vector-icons';
4 | import { createStyle } from '../utils/styleCompat';
5 | import { ContainerComponentProps } from '../types/common';
6 |
7 | export interface CardSectionProps extends ContainerComponentProps {
8 | title?: string;
9 | icon?: boolean;
10 | onPress?: () => void;
11 | }
12 |
13 | const CardSection: React.FC = ({
14 | title = "Section Title",
15 | icon = false,
16 | onPress = () => console.log('Section pressed'),
17 | className,
18 | style,
19 | ...props
20 | }) => {
21 | const containerStyle = createStyle({
22 | className: `${className || ''} w-full bg-white`,
23 | style
24 | });
25 |
26 | const touchableStyle = createStyle({
27 | className: "flex flex-row items-center justify-between py-3 px-2 border-b border-gray-200"
28 | });
29 |
30 | const leftContentStyle = createStyle({
31 | className: "flex flex-row items-center"
32 | });
33 |
34 | const iconContainerStyle = createStyle({
35 | className: "mr-2 flex items-center justify-center"
36 | });
37 |
38 | const titleTextStyle = createStyle({
39 | className: "text-base font-medium text-gray-800",
40 | style: {
41 | fontSize: 16,
42 | fontWeight: "500",
43 | color: "#1f2937"
44 | }
45 | });
46 |
47 | const rightIconStyle = createStyle({
48 | className: "flex items-center justify-center"
49 | });
50 |
51 | return (
52 |
53 |
58 |
59 | {icon && (
60 |
61 |
62 |
63 | )}
64 | {title}
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | );
73 | };
74 |
75 | export default CardSection;
--------------------------------------------------------------------------------
/src/socialbuttons/sociallogin.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text, TouchableOpacity } from 'react-native';
3 | import { createStyle } from '../utils/styleCompat';
4 | import { ContainerComponentProps } from '../types/common';
5 |
6 | export interface SocialLoginProps extends ContainerComponentProps {
7 | email?: boolean;
8 | google?: boolean;
9 | facebook?: boolean;
10 | apple?: boolean;
11 | onEmailSignIn?: () => void;
12 | onGoogleSignIn?: () => void;
13 | onFacebookSignIn?: () => void;
14 | onAppleSignIn?: () => void;
15 | }
16 |
17 | const SocialLogin: React.FC = ({
18 | email = true,
19 | google = true,
20 | facebook = true,
21 | apple = true,
22 | onEmailSignIn,
23 | onGoogleSignIn,
24 | onFacebookSignIn,
25 | onAppleSignIn,
26 | className,
27 | style,
28 | ...props
29 | }) => {
30 | const containerStyle = createStyle({
31 | className: `${className || ''} w-full`,
32 | style
33 | });
34 |
35 | const buttonStyle = createStyle({
36 | className: 'flex-row items-center justify-center p-3 m-1 rounded-lg bg-blue-500'
37 | });
38 |
39 | const buttonTextStyle = createStyle({
40 | className: 'text-white font-medium',
41 | style: { color: 'white', fontWeight: '500' }
42 | });
43 |
44 | return (
45 |
46 | {google && (
47 |
48 | Sign in with Google
49 |
50 | )}
51 | {apple && (
52 |
53 | Sign in with Apple
54 |
55 | )}
56 | {facebook && (
57 |
58 | Sign in with Facebook
59 |
60 | )}
61 | {email && (
62 |
63 | Sign in with Email
64 |
65 | )}
66 |
67 | );
68 | };
69 |
70 | export default SocialLogin;
71 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.yml:
--------------------------------------------------------------------------------
1 | name: "🐛 Bug Report"
2 | description: Report a reproducible bug or regression in the library.
3 | labels: [bug]
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | # 🐛 Bug Report
9 |
10 | Please fill out all required sections to help us fix the issue faster.
11 |
12 | - type: checkboxes
13 | attributes:
14 | label: Checklist
15 | description: Confirm the following before submitting:
16 | options:
17 | - label: I tested the latest version.
18 | required: true
19 | - label: I’m using a supported React Native version.
20 | required: true
21 | - label: I searched existing issues.
22 | required: true
23 |
24 | - type: textarea
25 | id: summary
26 | attributes:
27 | label: Bug Summary
28 | description: A clear and concise description of the bug.
29 | validations:
30 | required: true
31 |
32 | - type: input
33 | id: version
34 | attributes:
35 | label: Package Version
36 | placeholder: e.g., 1.0.2
37 | validations:
38 | required: true
39 |
40 | - type: textarea
41 | id: environment
42 | attributes:
43 | label: Environment Info
44 | description: Paste output from `npx react-native info`
45 | render: shell
46 | value: |
47 | ```sh
48 | npx react-native info
49 | ```
50 | validations:
51 | required: true
52 |
53 | - type: textarea
54 | id: steps
55 | attributes:
56 | label: Steps to Reproduce
57 | description: Provide steps to reproduce the issue.
58 | placeholder: |
59 | 1. Install the package
60 | 2. Import a component
61 | 3. Run the app
62 | 4. Observe the issue
63 | validations:
64 | required: true
65 |
66 | - type: textarea
67 | id: expected
68 | attributes:
69 | label: Expected Behavior
70 | description: What should happen instead?
71 | validations:
72 | required: true
73 |
74 | - type: input
75 | id: repro
76 | attributes:
77 | label: Reproducible Example
78 | placeholder: e.g., https://github.com/user/repro-app
79 | validations:
80 | required: true
81 |
82 | - type: textarea
83 | id: context
84 | attributes:
85 | label: Additional Context
86 | description: Logs, device info, screenshots, or anything relevant.
87 |
--------------------------------------------------------------------------------
/src/Input/NormalInput.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useRef, useEffect } from "react";
2 | import { TextInput, View, ActivityIndicator } from "react-native";
3 | import { Ionicons } from "@expo/vector-icons";
4 | import { NInputProp } from "./Input.type";
5 | const Sizes = {
6 | small: "w-[375px] h-[36px]",
7 | medium: "w-[375px] h-[48px]",
8 | large: "w-[375px] h-[56px]",
9 | full: "w-full h-full",
10 | fit : "w-fit h-full",
11 | };
12 |
13 | export default function NormalText({
14 | Size = "small",
15 | Label = "",
16 | Hint = "",
17 | State = "Default",
18 | value = "",
19 | curved = false,
20 | ...props
21 | }: NInputProp) {
22 | const inputRef = useRef(null);
23 |
24 |
25 | useEffect(() => {
26 | if (State === "Active" && inputRef.current) {
27 | inputRef.current.focus();
28 | }
29 | }, [State]);
30 |
31 |
32 | const getOutlineClass = () => {
33 | if (["Loading", "Disabled", "ViewOnly"].includes(State)) {
34 | return "outline-none";
35 | }
36 | };
37 |
38 |
39 | const getIcon = () => {
40 | switch (State) {
41 | case "Correct":
42 | return ;
43 | case "Incorrect":
44 | return ;
45 | case "Loading":
46 | return ;
47 | default:
48 | return null;
49 | }
50 | };
51 |
52 | return (
53 |
54 | {Label ? (
55 |
56 | {Label}
57 |
58 | ) : null}
59 |
60 |
66 |
67 | {["Correct", "Incorrect", "Loading"].includes(State) && (
68 |
69 | {getIcon()}
70 |
71 | )}
72 |
73 | {Hint ? (
74 |
75 | {Hint}
76 |
77 | ) : null}
78 |
79 | );
80 | }
81 |
--------------------------------------------------------------------------------
/src/iosmodalsheets/iosmodalsheets.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text, TouchableOpacity, SafeAreaView } from 'react-native';
3 | import { Ionicons } from '@expo/vector-icons';
4 | import { RectButton } from "../Button";
5 | import { IOSModalSheetProps } from './iosmodalsheets.type';
6 |
7 | const IOSModalSheet: React.FC = ({
8 | title,
9 | label,
10 | action = 1,
11 | compact = false,
12 | onClose,
13 | onButtonPress,
14 | onClipboard,
15 | onForward,
16 | children,
17 | }) => {
18 |
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {title}
29 |
30 |
31 | {action >= 3 && (
32 |
33 |
34 |
35 | )}
36 | {action >= 2 && (
37 |
38 |
39 |
40 | )}
41 | {action === 1 && }
42 |
43 |
44 |
45 |
46 | {!compact && (
47 | {title}
48 | )}
49 |
50 | {children}
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | );
65 | };
66 |
67 | export default IOSModalSheet;
--------------------------------------------------------------------------------
/src/navigationbar/navigationbar.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text, TouchableOpacity, Platform } from 'react-native';
3 | import { Ionicons } from '@expo/vector-icons';
4 | import { NavigationBarProps } from './navigationbar.type';
5 |
6 | const NavigationBar: React.FC = ({
7 | title,
8 | device = Platform.OS as 'ios' | 'android',
9 | action = 1,
10 | size = 'compact',
11 | onClose,
12 | onClipboard,
13 | onForward,
14 | }) => {
15 | const isIOS = device === 'ios';
16 |
17 | const renderActions = () => {
18 | if (action === 1) {
19 | return null;
20 | } else if (action === 2) {
21 | return (
22 |
23 |
24 |
25 | );
26 | } else if (action === 3) {
27 | return (
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | );
37 | }
38 | return null;
39 | };
40 |
41 | return (
42 |
43 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | {title}
53 |
54 |
55 |
56 | {renderActions()}
57 |
58 |
59 |
60 |
61 | {size === 'expandable' && (
62 |
63 |
64 | {title}
65 |
66 |
67 | )}
68 |
69 | );
70 | };
71 |
72 | export default NavigationBar;
--------------------------------------------------------------------------------
/src/sociallogin.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text, TouchableOpacity } from 'react-native';
3 | import { createStyle } from './utils/styleCompat';
4 | import { ContainerComponentProps } from './types/common';
5 |
6 | export interface SocialLoginProps extends ContainerComponentProps {
7 | email?: boolean;
8 | google?: boolean;
9 | facebook?: boolean;
10 | apple?: boolean;
11 | onEmailSignIn?: () => void;
12 | onGoogleSignIn?: () => void;
13 | onFacebookSignIn?: () => void;
14 | onAppleSignIn?: () => void;
15 | }
16 |
17 | const SocialLogin: React.FC = ({
18 | email = true,
19 | google = true,
20 | facebook = true,
21 | apple = true,
22 | onEmailSignIn,
23 | onGoogleSignIn,
24 | onFacebookSignIn,
25 | onAppleSignIn,
26 | className,
27 | style,
28 | ...props
29 | }) => {
30 | const containerStyle = createStyle({
31 | className: `${className || ''} w-full`,
32 | style
33 | });
34 |
35 | const buttonStyle = createStyle({
36 | className: 'flex-row items-center justify-center p-3 m-1 rounded-lg',
37 | style: {
38 | backgroundColor: '#3b82f6',
39 | padding: 12,
40 | margin: 4,
41 | borderRadius: 8,
42 | flexDirection: 'row',
43 | alignItems: 'center',
44 | justifyContent: 'center'
45 | }
46 | });
47 |
48 | const buttonTextStyle = createStyle({
49 | className: 'text-white font-medium',
50 | style: {
51 | color: 'white',
52 | fontWeight: '500',
53 | fontSize: 16
54 | }
55 | });
56 |
57 | return (
58 |
59 | {google && (
60 |
61 | Sign in with Google
62 |
63 | )}
64 | {apple && (
65 |
66 | Sign in with Apple
67 |
68 | )}
69 | {facebook && (
70 |
71 | Sign in with Facebook
72 |
73 | )}
74 | {email && (
75 |
76 | Sign in with Email
77 |
78 | )}
79 |
80 | );
81 | };
82 |
83 | export default SocialLogin;
84 |
--------------------------------------------------------------------------------
/src/Navtabs/Navtabs.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { View, Text, TouchableOpacity } from 'react-native';
3 | import { NavTabProps } from './Navtabs.type';
4 | import { createStyle } from '../utils/styleCompat';
5 |
6 | const NavTabs: React.FC = ({
7 | tabs,
8 | initialActiveIndex = 0,
9 | onTabChange,
10 | className,
11 | style,
12 | ...props
13 | }) => {
14 | const [activeIndex, setActiveIndex] = useState(initialActiveIndex);
15 |
16 | const handleTabPress = (index: number) => {
17 | setActiveIndex(index);
18 | if (onTabChange) {
19 | onTabChange(index);
20 | }
21 | };
22 |
23 | const containerStyle = createStyle({
24 | className: `${className || ''} flex-row absolute top-0 w-full bg-gray-100 rounded-lg p-1`,
25 | style: [
26 | {
27 | flexDirection: 'row',
28 | position: 'absolute',
29 | top: 0,
30 | width: '100%',
31 | backgroundColor: '#f3f4f6',
32 | borderRadius: 8,
33 | padding: 4
34 | },
35 | style
36 | ].filter(Boolean)
37 | });
38 |
39 | return (
40 |
41 | {tabs.map((tab, index) => {
42 | const isActive = activeIndex === index;
43 |
44 | const tabStyle = createStyle({
45 | className: `flex-1 py-2 px-4 items-center justify-center ${isActive ? 'bg-white rounded-md shadow' : 'bg-transparent'}`,
46 | style: {
47 | flex: 1,
48 | paddingVertical: 8,
49 | paddingHorizontal: 16,
50 | alignItems: 'center',
51 | justifyContent: 'center',
52 | backgroundColor: isActive ? '#ffffff' : 'transparent',
53 | borderRadius: isActive ? 6 : 0,
54 | shadowColor: isActive ? '#000' : 'transparent',
55 | shadowOffset: isActive ? { width: 0, height: 1 } : { width: 0, height: 0 },
56 | shadowOpacity: isActive ? 0.1 : 0,
57 | shadowRadius: isActive ? 3 : 0,
58 | elevation: isActive ? 2 : 0
59 | }
60 | });
61 |
62 | const textStyle = createStyle({
63 | className: `text-sm font-medium ${isActive ? 'text-black' : 'text-gray-500'}`,
64 | style: {
65 | fontSize: 14,
66 | fontWeight: '500',
67 | color: isActive ? '#000000' : '#6b7280'
68 | }
69 | });
70 |
71 | return (
72 | handleTabPress(index)}
76 | >
77 |
78 | {tab}
79 |
80 |
81 | );
82 | })}
83 |
84 | );
85 | };
86 |
87 | export default NavTabs;
--------------------------------------------------------------------------------
/src/Button/rect.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Pressable, Text } from "react-native";
3 | import { ActivityIndicator } from "react-native";
4 | import { ButtonProps } from "./Button.type";
5 | import { createStyle, mergeStyles } from '../utils/styleCompat';
6 |
7 | const RectButton = ({
8 | label = "Button",
9 | state = "default",
10 | size = "medium",
11 | disabled,
12 | background,
13 | variant = "primary",
14 |
15 | ...props
16 | }: ButtonProps) => {
17 | const buttonVariant = variant;
18 |
19 | const sizes = {
20 | small: "min-w-[80px] h-fit p-[10px]",
21 | medium: "min-w-[120px] h-fit p-[10px]",
22 | large: "min-w-[160px] h-fit p-[15px]",
23 | };
24 |
25 | const primaryStates = {
26 | default: "bg-buttons-primary-default",
27 | pressed: "bg-buttons-primary-pressed",
28 | hover: "bg-buttons-primary-hover",
29 | disabled: "bg-buttons-primary-disabled",
30 | loading: "bg-buttons-primary-default",
31 | };
32 |
33 | const secondaryStates = {
34 | default: 'btn-secondary-default',
35 | pressed: 'btn-secondary-pressed',
36 | hover: 'btn-secondary-hover',
37 | disabled: 'btn-secondary-disabled',
38 | loading: 'btn-secondary-loading',
39 | };
40 |
41 | const states = buttonVariant === "primary" ? primaryStates : secondaryStates;
42 |
43 | background = disabled ? null : background;
44 |
45 | const [isHovered, setIsHovered] = useState(false);
46 |
47 | const isHoverEffectEnabled = !disabled && state !== "disabled" && state !== "loading";
48 |
49 | // Create the style object combining className and style props
50 | const baseStyle = {
51 | backgroundColor: background ? background : undefined
52 | };
53 |
54 | const containerStyle = createStyle({
55 | className: `${sizes[size]} ${background
56 | ? ''
57 | : disabled
58 | ? states.disabled
59 | : isHovered && isHoverEffectEnabled
60 | ? states.hover
61 | : states[state]
62 | } flex items-center justify-center`,
63 | style: [baseStyle, props.style].filter(Boolean)
64 | });
65 |
66 | return (
67 | isHoverEffectEnabled && setIsHovered(true)}
70 | onHoverOut={() => isHoverEffectEnabled && setIsHovered(false)}
71 | style={containerStyle}
72 | {...props}
73 | >
74 | {state === "loading" ? (
75 |
76 | ) : (
77 |
80 | {label}
81 |
82 | )}
83 |
84 | );
85 | };
86 |
87 | export default RectButton;
--------------------------------------------------------------------------------
/src/Input/Input.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text, TextInput, TextInputProps } from 'react-native';
3 | import { createStyle } from '../utils/styleCompat';
4 | import { BaseComponentProps, ComponentSize, ComponentState } from '../types/common';
5 |
6 | export interface InputProps extends BaseComponentProps, Omit {
7 | label?: string;
8 | hint?: string;
9 | size?: ComponentSize;
10 | state?: 'default' | 'success' | 'error';
11 | curved?: boolean;
12 | required?: boolean;
13 | }
14 |
15 | const Input: React.FC = ({
16 | label,
17 | hint,
18 | size = 'medium',
19 | state = 'default',
20 | curved = false,
21 | required = false,
22 | className,
23 | style,
24 | disabled,
25 | ...textInputProps
26 | }) => {
27 | const sizes = {
28 | small: 'text-sm p-2',
29 | medium: 'text-base p-3',
30 | large: 'text-lg p-4'
31 | };
32 |
33 | const getBorderColor = () => {
34 | switch (state) {
35 | case 'success':
36 | return 'border-green-500';
37 | case 'error':
38 | return 'border-red-500';
39 | default:
40 | return 'border-gray-300';
41 | }
42 | };
43 |
44 | const getHintColor = () => {
45 | switch (state) {
46 | case 'success':
47 | return 'text-green-600';
48 | case 'error':
49 | return 'text-red-600';
50 | default:
51 | return 'text-gray-600';
52 | }
53 | };
54 |
55 | const containerStyle = createStyle({
56 | className: 'flex flex-col gap-2',
57 | style
58 | });
59 |
60 | const labelStyle = createStyle({
61 | className: 'text-sm font-medium text-gray-700'
62 | });
63 |
64 | const inputStyle = createStyle({
65 | className: `${sizes[size]} bg-white border-2 ${getBorderColor()} ${curved ? 'rounded-md' : ''} ${disabled ? 'bg-gray-100 text-gray-500' : 'text-gray-900'}`,
66 | style: {
67 | outlineWidth: 0, // Remove web outline
68 | }
69 | });
70 |
71 | const hintStyle = createStyle({
72 | className: `text-xs mt-1 ${getHintColor()}`
73 | });
74 |
75 | return (
76 |
77 | {label && (
78 |
79 |
80 | {label}
81 | {required && *}
82 |
83 |
84 | )}
85 |
86 |
92 |
93 | {hint && (
94 |
95 | {hint}
96 |
97 | )}
98 |
99 | );
100 | };
101 |
102 | export default Input;
103 |
104 |
--------------------------------------------------------------------------------
/src/statusbutton/statusbutton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { TouchableOpacity, Text, View } from 'react-native';
3 | import { Ionicons } from '@expo/vector-icons';
4 | import { StatusButtonProps } from './statusbutton.type';
5 |
6 | const StatusButton: React.FC = ({
7 | label = 'Label',
8 | type = 'information',
9 | buttonType = 'primary',
10 | iconLeft = false,
11 | iconRight = false,
12 | iconOnly = false,
13 | onPress = () => {},
14 | }) => {
15 | const colorStyles = {
16 | information: {
17 | primary: {
18 | backgroundColor: '#1F1F1F',
19 | color: '#FFFFFF',
20 | },
21 | subtle: {
22 | backgroundColor: '#DDDDDD',
23 | color: '#1F1F1F',
24 | },
25 | },
26 | success: {
27 | primary: {
28 | backgroundColor: '#0E8345',
29 | color: '#FFFFFF',
30 | },
31 | subtle: {
32 | backgroundColor: '#B1EAC2',
33 | color: '#166C3B',
34 | },
35 | },
36 | warning: {
37 | primary: {
38 | backgroundColor: '#C54600',
39 | color: '#FFFFFF',
40 | },
41 | subtle: {
42 | backgroundColor: '#FFD3BC',
43 | color: '#C54600',
44 | },
45 | },
46 | error: {
47 | primary: {
48 | backgroundColor: '#950F22',
49 | color: '#FFFFFF',
50 | },
51 | subtle: {
52 | backgroundColor: '#FFD2CD',
53 | color: '#950F22',
54 | },
55 | },
56 | };
57 |
58 | const currentStyle = colorStyles[type][buttonType];
59 | const textColor = currentStyle.color;
60 | const backgroundColor = currentStyle.backgroundColor;
61 |
62 | const renderContent = () => {
63 | if (iconOnly) {
64 | return (
65 |
70 | );
71 | }
72 |
73 | return (
74 |
75 | {iconLeft && (
76 |
81 | )}
82 |
83 | {label}
84 |
85 | {iconRight && (
86 |
91 | )}
92 |
93 | );
94 | };
95 |
96 | return (
97 |
103 | {renderContent()}
104 |
105 | );
106 | };
107 |
108 | export default StatusButton;
--------------------------------------------------------------------------------
/src/segmentedswitchios/segmentedswitchios.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { View, Text, TouchableOpacity, useWindowDimensions } from 'react-native';
3 | import { Ionicons } from '@expo/vector-icons';
4 | import { SegmentedSwitchIOSProps } from './segmentedswitchios.type';
5 |
6 | const SegmentedSwitchIOS: React.FC = ({
7 | title,
8 | items,
9 | selectedIndex = null,
10 | messageType = 'none',
11 | message = '',
12 | onChange
13 | }) => {
14 | const [selected, setSelected] = useState(selectedIndex);
15 | const { width } = useWindowDimensions();
16 | const fontSize = width < 360 ? 'text-sm' : 'text-base';
17 | const padding = width < 360 ? 'py-2' : 'py-3';
18 |
19 | const handleSelect = (index: number) => {
20 | setSelected(index);
21 | if (onChange) {
22 | onChange(index);
23 | }
24 | };
25 |
26 | const getIconName = () => {
27 | if (messageType === 'error') return 'alert-circle';
28 | return 'information-circle';
29 | };
30 |
31 | return (
32 |
33 | {title}
34 |
37 | {items.map((item, index) => (
38 |
48 | handleSelect(index)}>
50 |
51 | {item}
52 |
53 |
54 |
55 | ))}
56 |
57 |
58 | {messageType !== 'none' && message && (
59 |
60 |
61 | {message}
64 |
65 | )}
66 |
67 | );
68 | };
69 |
70 | export default SegmentedSwitchIOS;
--------------------------------------------------------------------------------
/src/dialog/dialog.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text } from 'react-native';
3 | import RectButton from "../Button/rect";
4 | import { DialogProps } from "./dialog.type";
5 | import { createStyle } from '../utils/styleCompat';
6 |
7 | const Dialog: React.FC = ({
8 | title,
9 | description,
10 | buttonLabel,
11 | onButtonPress,
12 | children,
13 | className,
14 | style,
15 | ...props
16 | }) => {
17 | return (
18 |
35 |
49 |
60 | {children}
61 |
62 |
63 |
67 |
76 | {title}
77 |
78 |
79 |
87 | {description}
88 |
89 |
90 |
94 |
95 |
96 |
97 |
98 |
99 | );
100 | };
101 |
102 | export default Dialog;
--------------------------------------------------------------------------------
/src/bottomnav/bottomnav.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
3 | import { Ionicons } from "@expo/vector-icons";
4 | import { BottomNavProps, NavItemProps } from './bottomnav.type';
5 |
6 | const NavItem = ({ icon, label, active, theme, onPress }: NavItemProps) => {
7 | return (
8 |
9 |
14 |
18 | {label}
19 |
20 |
21 | );
22 | };
23 |
24 | const BottomNav = ({
25 | items,
26 | theme = 'light',
27 | initialActiveIndex = 0,
28 | onItemPress
29 | }: BottomNavProps) => {
30 | const [activeIndex, setActiveIndex] = useState(initialActiveIndex);
31 |
32 | const handlePress = (index: number) => {
33 | setActiveIndex(index);
34 | if (onItemPress) {
35 | onItemPress(index);
36 | }
37 | };
38 |
39 | return (
40 |
44 |
45 | {items.map((item, index) => (
46 | handlePress(index)}
53 | />
54 | ))}
55 |
56 |
60 |
64 |
65 |
66 | );
67 | };
68 |
69 | const styles = StyleSheet.create({
70 | container: {
71 | position: 'absolute',
72 | bottom: 0,
73 | left: 0,
74 | right: 0,
75 | width: '100%',
76 | borderTopWidth: 1,
77 | borderTopColor: '#E5E5E5',
78 | paddingVertical: 8,
79 | elevation: 8,
80 | shadowColor: '#000000',
81 | shadowOffset: { width: 0, height: -2 },
82 | shadowOpacity: 0.1,
83 | shadowRadius: 5,
84 | },
85 | navContainer: {
86 | flexDirection: 'row',
87 | justifyContent: 'space-around',
88 | alignItems: 'center',
89 | },
90 | navItem: {
91 | alignItems: 'center',
92 | justifyContent: 'center',
93 | paddingVertical: 8,
94 | },
95 | label: {
96 | fontSize: 12,
97 | marginTop: 4,
98 | },
99 | indicatorContainer: {
100 | width: '100%',
101 | paddingHorizontal: 40,
102 | flexDirection: 'row',
103 | marginTop: 8,
104 | },
105 | indicator: {
106 | height: 3,
107 | width: 90,
108 | borderRadius: 1.5,
109 | }
110 | });
111 |
112 | export default BottomNav;
--------------------------------------------------------------------------------
/src/iconography/iconography.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { View, Text } from "react-native";
3 | import { Ionicons } from "@expo/vector-icons";
4 | import { createStyle } from '../utils/styleCompat';
5 | import { iconMap, IconName, PlaceholderSize, AvatarSize, IconComponentProps, sizeMapping } from "./iconography.type";
6 |
7 | const IconComponent: React.FC = ({
8 | type,
9 | iconName,
10 | size = 24,
11 | theme = "light",
12 | placeholderSize = "medium",
13 | filled = false,
14 | avatarSize = "medium",
15 | initials = "AB",
16 | className = "",
17 | }) => {
18 | if (type === "icon") {
19 | return renderIcon(iconName, size, theme, className);
20 | }
21 |
22 | if (type === "placeholder") {
23 | return renderPlaceholder(placeholderSize, filled, className);
24 | }
25 |
26 | if (type === "avatar") {
27 | return renderAvatar(avatarSize, initials, className);
28 | }
29 |
30 | return null;
31 | };
32 |
33 |
34 | const renderIcon = (
35 | iconName?: IconName,
36 | size: number = 24,
37 | theme: "light" | "dark" = "light",
38 | className = ""
39 | ) => {
40 | if (!iconName) return null;
41 |
42 | const baseIconName = iconMap[iconName];
43 |
44 |
45 | const ionIconName =
46 | theme === "dark" && baseIconName.endsWith("-outline")
47 | ? baseIconName.replace("-outline", "")
48 | : baseIconName;
49 |
50 | return (
51 |
52 |
53 |
54 | );
55 | };
56 |
57 |
58 | const renderPlaceholder = (
59 | size: PlaceholderSize = "medium",
60 | filled: boolean = false,
61 | className = ""
62 | ) => {
63 | const pixelSize = sizeMapping[size];
64 | const bgClass = filled ? "bg-black" : "bg-transparent";
65 | const borderClass = filled ? "" : "border border-black";
66 |
67 | return (
68 |
74 | );
75 | };
76 |
77 |
78 | const renderAvatar = (
79 | size: AvatarSize = "medium",
80 | initials: string = "AB",
81 | className = ""
82 | ) => {
83 | const pixelSize = sizeMapping[size];
84 |
85 |
86 | const fontSize = Math.max(Math.floor(pixelSize * 0.4), 8);
87 |
88 |
89 | const fontWeight =
90 | size === "xl" || size === "large"
91 | ? "900"
92 | : size === "medium" || size === "small"
93 | ? "700"
94 | : "500";
95 |
96 | return (
97 |
103 |
111 | {initials}
112 |
113 |
114 | );
115 | };
116 |
117 | export default IconComponent;
--------------------------------------------------------------------------------
/src/emptystates/emptystates.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { View, Text } from "react-native";
3 | import Button from "../Button/Button";
4 | import { createStyle } from '../utils/styleCompat';
5 | import { ContainerComponentProps } from '../types/common';
6 |
7 | export interface EmptyStatesProps extends ContainerComponentProps {
8 | message: string;
9 | description?: string;
10 | label?: string;
11 | onPress?: () => void;
12 | action?: "none" | "primary" | "secondary";
13 | children?: React.ReactNode;
14 | }
15 |
16 | const EmptyState: React.FC = ({
17 | message,
18 | description,
19 | label = "Okay",
20 | onPress,
21 | action = "none",
22 | children,
23 | className,
24 | style,
25 | ...props
26 | }) => {
27 | const renderButton = () => {
28 | if (action === "none") return null;
29 |
30 | const buttontype = action === "primary" ? "primary" : "secondary";
31 |
32 | return (
33 |
38 | );
39 | };
40 |
41 | const containerStyle = createStyle({
42 | className: `${className || ''} flex bg-white justify-center items-center`,
43 | style: [
44 | {
45 | display: 'flex',
46 | backgroundColor: 'white',
47 | justifyContent: 'center',
48 | alignItems: 'center',
49 | width: '75%'
50 | },
51 | style
52 | ].filter(Boolean)
53 | });
54 |
55 | const childrenContainerStyle = createStyle({
56 | className: "w-full min-h-32",
57 | style: {
58 | width: '100%',
59 | minHeight: 128
60 | }
61 | });
62 |
63 | const contentContainerStyle = createStyle({
64 | className: "flex bg-white justify-center items-center py-2 px-4",
65 | style: {
66 | display: 'flex',
67 | backgroundColor: 'white',
68 | justifyContent: 'center',
69 | alignItems: 'center',
70 | paddingVertical: 8,
71 | paddingHorizontal: 16
72 | }
73 | });
74 |
75 | const messageStyle = createStyle({
76 | className: "text-center text-sm font-semibold text-black mb-2",
77 | style: {
78 | textAlign: 'center',
79 | fontSize: 14,
80 | fontWeight: '600',
81 | color: '#000000',
82 | marginBottom: 8
83 | }
84 | });
85 |
86 | const descriptionStyle = createStyle({
87 | className: "text-center text-xs text-gray-500 mb-2 max-w-xs",
88 | style: {
89 | textAlign: 'center',
90 | fontSize: 12,
91 | color: '#6b7280',
92 | marginBottom: 8,
93 | maxWidth: 288 // Approx equivalent to max-w-xs
94 | }
95 | });
96 |
97 | return (
98 |
99 | {children && {children}}
100 |
101 |
102 | {message}
103 |
104 |
105 | {description && (
106 |
107 | {description}
108 |
109 | )}
110 |
111 | {renderButton()}
112 |
113 |
114 | );
115 | };
116 |
117 | export default EmptyState;
118 |
119 |
--------------------------------------------------------------------------------
/src/types/common.d.ts:
--------------------------------------------------------------------------------
1 | import { ViewStyle, TextStyle, ImageStyle } from 'react-native';
2 | import { StyleCompatProps } from '../utils/styleCompat';
3 | export type RNStyle = ViewStyle | TextStyle | ImageStyle;
4 | export { StyleCompatProps };
5 | /**
6 | * Base props that all components should extend from
7 | * Provides both className (NativeWind) and style (React Native) support
8 | */
9 | export interface BaseComponentProps extends StyleCompatProps {
10 | /**
11 | * Test ID for automated testing
12 | */
13 | testID?: string;
14 | /**
15 | * Accessibility label for screen readers
16 | */
17 | accessibilityLabel?: string;
18 | /**
19 | * Whether the component is disabled
20 | */
21 | disabled?: boolean;
22 | }
23 | /**
24 | * Props for components that can be pressed/tapped
25 | */
26 | export interface PressableComponentProps extends BaseComponentProps {
27 | /**
28 | * Function called when the component is pressed
29 | */
30 | onPress?: () => void;
31 | /**
32 | * Whether the component should be disabled
33 | */
34 | disabled?: boolean;
35 | /**
36 | * Opacity when pressed (for TouchableOpacity-style feedback)
37 | */
38 | activeOpacity?: number;
39 | }
40 | /**
41 | * Props for text-based components
42 | */
43 | export interface TextComponentProps extends BaseComponentProps {
44 | /**
45 | * The text content
46 | */
47 | children?: string | React.ReactNode;
48 | /**
49 | * Text color (can be used alongside className/style)
50 | */
51 | color?: string;
52 | /**
53 | * Font size (can be used alongside className/style)
54 | */
55 | fontSize?: number;
56 | /**
57 | * Font weight (can be used alongside className/style)
58 | */
59 | fontWeight?: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
60 | }
61 | /**
62 | * Props for container components
63 | */
64 | export interface ContainerComponentProps extends BaseComponentProps {
65 | /**
66 | * Child components
67 | */
68 | children?: React.ReactNode;
69 | /**
70 | * Background color (can be used alongside className/style)
71 | */
72 | backgroundColor?: string;
73 | /**
74 | * Border radius (can be used alongside className/style)
75 | */
76 | borderRadius?: number;
77 | /**
78 | * Padding (can be used alongside className/style)
79 | */
80 | padding?: number;
81 | /**
82 | * Margin (can be used alongside className/style)
83 | */
84 | margin?: number;
85 | }
86 | /**
87 | * Common size options used across components
88 | */
89 | export type ComponentSize = 'small' | 'medium' | 'large';
90 | /**
91 | * Common variant options used across components
92 | */
93 | export type ComponentVariant = 'primary' | 'secondary' | 'outline' | 'ghost';
94 | /**
95 | * Common state options used across components
96 | */
97 | export type ComponentState = 'default' | 'hover' | 'pressed' | 'disabled' | 'loading' | 'success' | 'error';
98 | /**
99 | * Color theme options
100 | */
101 | export type ComponentColor = 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' | 'gray' | 'red' | 'blue' | 'green' | 'yellow' | 'purple' | 'pink';
102 |
--------------------------------------------------------------------------------
/src/types/common.ts:
--------------------------------------------------------------------------------
1 | import { ViewStyle, TextStyle, ImageStyle } from 'react-native';
2 | import { StyleCompatProps } from '../utils/styleCompat';
3 |
4 | // Re-export for convenience
5 | export type RNStyle = ViewStyle | TextStyle | ImageStyle;
6 | export { StyleCompatProps };
7 |
8 | /**
9 | * Base props that all components should extend from
10 | * Provides both className (NativeWind) and style (React Native) support
11 | */
12 | export interface BaseComponentProps extends StyleCompatProps {
13 | /**
14 | * Test ID for automated testing
15 | */
16 | testID?: string;
17 |
18 | /**
19 | * Accessibility label for screen readers
20 | */
21 | accessibilityLabel?: string;
22 |
23 | /**
24 | * Whether the component is disabled
25 | */
26 | disabled?: boolean;
27 | }
28 |
29 | /**
30 | * Props for components that can be pressed/tapped
31 | */
32 | export interface PressableComponentProps extends BaseComponentProps {
33 | /**
34 | * Function called when the component is pressed
35 | */
36 | onPress?: () => void;
37 |
38 | /**
39 | * Whether the component should be disabled
40 | */
41 | disabled?: boolean;
42 |
43 | /**
44 | * Opacity when pressed (for TouchableOpacity-style feedback)
45 | */
46 | activeOpacity?: number;
47 | }
48 |
49 | /**
50 | * Props for text-based components
51 | */
52 | export interface TextComponentProps extends BaseComponentProps {
53 | /**
54 | * The text content
55 | */
56 | children?: string | React.ReactNode;
57 |
58 | /**
59 | * Text color (can be used alongside className/style)
60 | */
61 | color?: string;
62 |
63 | /**
64 | * Font size (can be used alongside className/style)
65 | */
66 | fontSize?: number;
67 |
68 | /**
69 | * Font weight (can be used alongside className/style)
70 | */
71 | fontWeight?: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
72 | }
73 |
74 | /**
75 | * Props for container components
76 | */
77 | export interface ContainerComponentProps extends BaseComponentProps {
78 | /**
79 | * Child components
80 | */
81 | children?: React.ReactNode;
82 |
83 | /**
84 | * Background color (can be used alongside className/style)
85 | */
86 | backgroundColor?: string;
87 |
88 | /**
89 | * Border radius (can be used alongside className/style)
90 | */
91 | borderRadius?: number;
92 |
93 | /**
94 | * Padding (can be used alongside className/style)
95 | */
96 | padding?: number;
97 |
98 | /**
99 | * Margin (can be used alongside className/style)
100 | */
101 | margin?: number;
102 | }
103 |
104 | /**
105 | * Common size options used across components
106 | */
107 | export type ComponentSize = 'small' | 'medium' | 'large';
108 |
109 | /**
110 | * Common variant options used across components
111 | */
112 | export type ComponentVariant = 'primary' | 'secondary' | 'outline' | 'ghost';
113 |
114 | /**
115 | * Common state options used across components
116 | */
117 | export type ComponentState = 'default' | 'hover' | 'pressed' | 'disabled' | 'loading' | 'success' | 'error';
118 |
119 | /**
120 | * Color theme options
121 | */
122 | export type ComponentColor =
123 | | 'primary'
124 | | 'secondary'
125 | | 'success'
126 | | 'warning'
127 | | 'error'
128 | | 'info'
129 | | 'gray'
130 | | 'red'
131 | | 'blue'
132 | | 'green'
133 | | 'yellow'
134 | | 'purple'
135 | | 'pink';
136 |
--------------------------------------------------------------------------------
/src/collapse/collapse.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { View, Text, TouchableOpacity, Animated } from 'react-native';
3 | import Ionicons from '@expo/vector-icons/Ionicons';
4 | import { createStyle } from '../utils/styleCompat';
5 | import { PressableComponentProps } from '../types/common';
6 |
7 | export interface CollapseProps extends PressableComponentProps {
8 | title: string;
9 | text?: string;
10 | initialCollapsed?: boolean;
11 | children?: React.ReactNode;
12 | }
13 |
14 | const Collapsible: React.FC = ({
15 | title,
16 | text,
17 | initialCollapsed = false,
18 | children,
19 | className,
20 | style,
21 | ...props
22 | }) => {
23 | const [collapsed, setCollapsed] = useState(initialCollapsed);
24 | const [height] = useState(new Animated.Value(initialCollapsed ? 0 : 1));
25 |
26 | useEffect(() => {
27 | Animated.timing(height, {
28 | toValue: collapsed ? 0 : 1,
29 | duration: 300,
30 | useNativeDriver: false,
31 | }).start();
32 | }, [collapsed, height]);
33 |
34 | const toggleCollapse = () => {
35 | setCollapsed(!collapsed);
36 | };
37 |
38 | const containerStyle = createStyle({
39 | className: `${className || ''} mb-4 bg-white py-2 border-b border-gray-400`,
40 | style: [
41 | {
42 | marginBottom: 16,
43 | backgroundColor: 'white',
44 | paddingVertical: 8,
45 | borderBottomWidth: 1,
46 | borderBottomColor: '#9ca3af'
47 | },
48 | style
49 | ].filter(Boolean)
50 | });
51 |
52 | const headerStyle = createStyle({
53 | className: "flex-row justify-between items-center px-2 py-3 border-gray-200",
54 | style: {
55 | flexDirection: 'row',
56 | justifyContent: 'space-between',
57 | alignItems: 'center',
58 | paddingHorizontal: 8,
59 | paddingVertical: 12,
60 | borderColor: '#e5e7eb'
61 | }
62 | });
63 |
64 | const titleStyle = createStyle({
65 | className: "text-lg font-medium",
66 | style: {
67 | fontSize: 18,
68 | fontWeight: '500',
69 | color: '#252A31'
70 | }
71 | });
72 |
73 | const contentContainerStyle = createStyle({
74 | className: "p-4",
75 | style: [
76 | {
77 | padding: 16,
78 | width: '90%'
79 | },
80 | !collapsed && { backgroundColor: '#f0fdf4' }
81 | ].filter(Boolean)
82 | });
83 |
84 | const textStyle = createStyle({
85 | className: "text-gray-700",
86 | style: {
87 | color: '#374151'
88 | }
89 | });
90 |
91 | return (
92 |
93 |
97 |
98 | {title}
99 |
100 |
105 |
106 |
107 |
117 |
118 | {text && {text}}
119 | {children}
120 |
121 |
122 |
123 | );
124 | };
125 |
126 | export default Collapsible;
127 |
128 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | // Main components (work with both NativeWind and plain React Native)
2 | export { default as Alert } from './alert/alert';
3 | export { default as AndroidModalSheet } from './androidmodalsheets/androidmodalsheets';
4 | export { default as BottomNav } from './bottomnav/bottomnav';
5 | export { default as Button } from './Button/Button';
6 | export { default as ButtonIcon } from './Button/icon';
7 | export { default as ButtonRect } from './Button/rect';
8 | export { default as ButtonTypeDoc } from './Button/typeDoc';
9 | export { default as Card } from './card/card';
10 | export { default as CardSection } from './card/section';
11 | export { default as Collapse } from './collapse/collapse';
12 | export { default as Dialog } from './dialog/dialog';
13 | export { default as EmptyStates } from './emptystates/emptystates';
14 | export { Grid } from './grid/grid';
15 | export { default as Iconography } from './iconography/iconography';
16 | export { default as Input } from './Input/Input';
17 | export { default as Loading } from './loading/loading';
18 | export { default as NavTabs } from './Navtabs/Navtabs';
19 | export { default as Select } from './select/select';
20 | export { default as Skeleton } from './skeleton/skeleton';
21 | export { default as Tabs } from './tabs/tabs';
22 | export { default as Tile } from './Tile/Tile';
23 |
24 | // Social Media Stack components
25 | export { default as SocialMediaStack } from './socialmediastack/socialmediastack';
26 | export { default as SocialMediaContainer } from './socialmediastack/socialmediacontainer';
27 | export { default as SocialMediaEngagement } from './socialmediastack/socialmediaengagement';
28 | export { default as StoryProgressBar } from './socialmediastack/storyprogressbar';
29 |
30 | // Legacy aliases for backward compatibility
31 | export { default as TypeDoc } from './Button/typeDoc';
32 | export { default as NormalInput } from './Input/Input'; // Alias to main Input
33 | export { default as SocialLogin } from './sociallogin'; // Legacy name
34 | export { default as RectButton } from './Button/rect'; // Alias for ButtonRect
35 |
36 | // Export prop types for TypeScript users
37 | export type { AlertProps } from './alert/alert';
38 | export type { AndroidModalSheetProps } from './androidmodalsheets/androidmodalsheets.type';
39 | export type { BottomNavProps } from './bottomnav/bottomnav.type';
40 | export type { BottomSheetProps } from './bottomsheet/bottomsheet.type';
41 | export type { ButtonProps, TypeDocsProps } from './Button/Button.type';
42 | export type { CardProps } from './card/card.type';
43 | export type { CollapsibleProps as CollapseProps } from './collapse/collapse.type';
44 | export type { DialogProps } from './dialog/dialog.type';
45 | export type { EmptyStateProps as EmptyStatesProps } from './emptystates/emptystates.type';
46 | export type { GridProps } from './grid/Grid.type';
47 | export type { AInputProp, NInputProp } from './Input/Input.type';
48 | export type { IOSModalSheetProps as IosModalSheetProps } from './iosmodalsheets/iosmodalsheets.type';
49 | export type { LoadingProps } from './loading/loading.type';
50 | export type { NavigationBarProps } from './navigationbar/navigationbar.type';
51 | export type { NavTabProps } from './Navtabs/Navtabs.type';
52 | export type { SegmentedSwitchIOSProps } from './segmentedswitchios/segmentedswitchios.type';
53 | export type { SelectProps, SelectValue } from './select/select.type';
54 | export type { SkeletonProps } from './skeleton/skeleton.type';
55 | export type { SocialButtonProps as SocialButtonsProps } from './socialbuttons/socialbuttons.type';
56 | export type { SocialMediaStackProps } from './socialmediastack/socialmediastack.type';
57 | export type { StatusButtonProps } from './statusbutton/statusbutton.type';
58 | export type { TabsProps } from './tabs/tabs.type';
59 | export type { TileProps } from './Tile/Tile.type';
60 | export type { CardSectionProps } from './card/section';
61 |
62 | // Utility functions and types
63 | export * from './utils/styleCompat';
64 | export type {
65 | BaseComponentProps,
66 | PressableComponentProps,
67 | TextComponentProps,
68 | ContainerComponentProps,
69 | ComponentSize,
70 | ComponentVariant,
71 | ComponentState,
72 | ComponentColor,
73 | } from './types/common';
74 |
--------------------------------------------------------------------------------
/src/skeleton/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, ScrollView } from 'react-native';
3 | import { createStyle } from '../utils/styleCompat';
4 | import { ContainerComponentProps } from '../types/common';
5 |
6 | export interface SkeletonProps extends ContainerComponentProps {
7 | }
8 |
9 | const SkeletonLoader: React.FC = ({
10 | className = "",
11 | style,
12 | ...props
13 | }) => {
14 | const containerStyle = createStyle({
15 | className: `${className || ''} flex-1 bg-white p-4 w-full`,
16 | style: [
17 | {
18 | flex: 1,
19 | backgroundColor: 'white',
20 | padding: 16,
21 | width: '100%'
22 | },
23 | style
24 | ].filter(Boolean)
25 | });
26 |
27 | const avatarStyle = createStyle({
28 | className: "h-16 w-16 rounded-full bg-gray-100 mb-4",
29 | style: {
30 | height: 64,
31 | width: 64,
32 | borderRadius: 32,
33 | backgroundColor: '#f3f4f6',
34 | marginBottom: 16
35 | }
36 | });
37 |
38 | const titleStyle = createStyle({
39 | className: "h-4 bg-gray-100 rounded-md my-4 w-full",
40 | style: {
41 | height: 16,
42 | backgroundColor: '#f3f4f6',
43 | borderRadius: 6,
44 | marginVertical: 16,
45 | width: '100%'
46 | }
47 | });
48 |
49 | const rowStyle = createStyle({
50 | className: "flex-row items-center mb-3",
51 | style: {
52 | flexDirection: 'row',
53 | alignItems: 'center',
54 | marginBottom: 12
55 | }
56 | });
57 |
58 | const smallBoxStyle = createStyle({
59 | className: "h-4 w-4 bg-gray-100 rounded-sm mr-2",
60 | style: {
61 | height: 16,
62 | width: 16,
63 | backgroundColor: '#f3f4f6',
64 | borderRadius: 2,
65 | marginRight: 8
66 | }
67 | });
68 |
69 | const lineStyle = createStyle({
70 | className: "h-4 bg-gray-100 rounded-md flex-1",
71 | style: {
72 | height: 16,
73 | backgroundColor: '#f3f4f6',
74 | borderRadius: 6,
75 | flex: 1
76 | }
77 | });
78 |
79 | const imageStyle = createStyle({
80 | className: "h-40 bg-gray-100 rounded-md w-full my-4",
81 | style: {
82 | height: 160,
83 | backgroundColor: '#f3f4f6',
84 | borderRadius: 6,
85 | width: '100%',
86 | marginVertical: 16
87 | }
88 | });
89 |
90 | const cardStyle = createStyle({
91 | className: "h-52 bg-gray-100 rounded-md w-full mb-4",
92 | style: {
93 | height: 208,
94 | backgroundColor: '#f3f4f6',
95 | borderRadius: 6,
96 | width: '100%',
97 | marginBottom: 16
98 | }
99 | });
100 |
101 | const buttonStyle = createStyle({
102 | className: "h-8 bg-gray-100 rounded-md w-full mb-4",
103 | style: {
104 | height: 32,
105 | backgroundColor: '#f3f4f6',
106 | borderRadius: 6,
107 | width: '100%',
108 | marginBottom: 16
109 | }
110 | });
111 |
112 | const textLineStyle = createStyle({
113 | className: "h-4 bg-gray-100 rounded-md mb-3 w-full",
114 | style: {
115 | height: 16,
116 | backgroundColor: '#f3f4f6',
117 | borderRadius: 6,
118 | marginBottom: 12,
119 | width: '100%'
120 | }
121 | });
122 |
123 | const shortLineStyle = createStyle({
124 | className: "h-4 bg-gray-100 rounded-md w-3/4",
125 | style: {
126 | height: 16,
127 | backgroundColor: '#f3f4f6',
128 | borderRadius: 6,
129 | width: '75%'
130 | }
131 | });
132 |
133 | return (
134 |
135 |
136 |
137 |
138 |
139 | {[1, 2, 3].map((_, index) => (
140 |
141 |
142 |
143 |
144 | ))}
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 | {[1, 2, 3].map((_, index) => (
153 |
154 | ))}
155 |
156 |
157 |
158 | );
159 | };
160 |
161 | export default SkeletonLoader;
162 |
163 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are always welcome, no matter how large or small!
4 |
5 | We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project. Before contributing, please read the [code of conduct](./CODE_OF_CONDUCT.md).
6 |
7 | ## Development workflow
8 |
9 | This project is a monorepo managed using [`npm workspaces`](https://docs.npmjs.com/cli/v9/using-npm/workspaces). It contains the following packages:
10 |
11 | - The library package in the root directory.
12 | - An example app in the `example/` directory.
13 |
14 | To get started with the project, run `npm install` in the root directory to install the required dependencies for each package:
15 |
16 | ```sh
17 | npm install
18 | ```
19 |
20 | > Since the project relies on npm workspaces, you cannot use [`yarn`](https://yarnpkg.com/) for development.
21 |
22 | The [example app](/example/) demonstrates usage of the library. You need to run it to test any changes you make.
23 |
24 | It is configured to use the local version of the library, so any changes you make to the library's source code will be reflected in the example app. Changes to the library's JavaScript code will be reflected in the example app without a rebuild, but native code changes will require a rebuild of the example app.
25 |
26 | You can use various commands from the root directory to work with the project.
27 |
28 | To start the Metro bundler:
29 |
30 | ```sh
31 | npm run start
32 | ```
33 |
34 | To run the example app on Android:
35 |
36 | ```sh
37 | npm run android
38 | ```
39 |
40 | To run the example app on iOS:
41 |
42 | ```sh
43 | npm run ios
44 | ```
45 |
46 | To run the example app on Web:
47 |
48 | ```sh
49 | npm run web
50 | ```
51 |
52 | Make sure your code passes TypeScript and ESLint. Run the following to verify:
53 |
54 | ```sh
55 | npm run lint
56 | npm run test
57 | ```
58 |
59 | To fix formatting errors, run the following:
60 |
61 | ```sh
62 | npm run lint -- --fix
63 | ```
64 |
65 | Remember to add tests for your change if possible. Run the unit tests by:
66 |
67 | ```sh
68 | npm run test
69 | ```
70 |
71 | ### Commit message convention
72 |
73 | We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages:
74 |
75 | - `fix`: bug fixes, e.g. fix crash due to deprecated method.
76 | - `feat`: new features, e.g. add new method to the module.
77 | - `refactor`: code refactor, e.g. migrate from class components to hooks.
78 | - `docs`: changes into documentation, e.g. add usage example for the module.
79 | - `test`: adding or updating tests, e.g. add integration tests using Jest.
80 | - `chore`: tooling changes, e.g. change CI config.
81 |
82 | Our pre-commit hooks verify that your commit message matches this format when committing.
83 |
84 | ### Linting and tests
85 |
86 | We use [TypeScript](https://www.typescriptlang.org/) for type checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing.
87 |
88 | Our pre-commit hooks verify that the linter and tests pass when committing.
89 |
90 | ### Scripts
91 |
92 | The `package.json` file contains various scripts for common tasks:
93 |
94 | - `npm install`: setup project by installing dependencies.
95 | - `npm run lint`: lint files with ESLint.
96 | - `npm run test`: run unit tests with Jest.
97 | - `npm run start`: start the Metro bundler for the example app.
98 | - `npm run android`: run the example app on Android.
99 | - `npm run ios`: run the example app on iOS.
100 | - `npm run web`: run the example app on Web.
101 | - `npm run reset-project`: resets the project state.
102 |
103 | ### Sending a pull request
104 |
105 | > **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github).
106 |
107 | When you're sending a pull request:
108 |
109 | - Prefer small pull requests focused on one change.
110 | - Verify that linters and tests are passing.
111 | - Review the documentation to make sure it looks good.
112 | - Follow the pull request template when opening a pull request.
113 | - For pull requests that change the API or implementation, discuss with maintainers first by opening an issue.
--------------------------------------------------------------------------------
/src/tabs/tabs.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text, TouchableOpacity } from 'react-native';
3 | import Ionicons from '@expo/vector-icons/Ionicons';
4 | import { createStyle } from '../utils/styleCompat';
5 | import { PressableComponentProps } from '../types/common';
6 |
7 | export interface TabsProps extends PressableComponentProps {
8 | label: string;
9 | placeholder?: string;
10 | value?: string;
11 | messageType?: 'none' | 'error' | 'help';
12 | message?: string;
13 | onPress?: () => void;
14 | }
15 |
16 | const Tabs: React.FC = ({
17 | label,
18 | placeholder = 'Placeholder',
19 | value = '',
20 | messageType = 'none',
21 | message = '',
22 | onPress = () => {},
23 | className,
24 | style,
25 | ...props
26 | }) => {
27 | const hasValue = value.trim().length > 0;
28 |
29 | const getBorderStyles = () => {
30 | switch (messageType) {
31 | case 'error':
32 | return {
33 | className: 'border border-red-500 rounded-md',
34 | style: { borderWidth: 1, borderColor: '#ef4444', borderRadius: 6 }
35 | };
36 | case 'help':
37 | return {
38 | className: 'border border-black rounded-md',
39 | style: { borderWidth: 1, borderColor: '#000000', borderRadius: 6 }
40 | };
41 | default:
42 | return {
43 | className: 'border-0 rounded-md',
44 | style: { borderWidth: 0, borderRadius: 6 }
45 | };
46 | }
47 | };
48 |
49 | const getMessageIcon = () => {
50 | switch (messageType) {
51 | case 'error':
52 | return ;
53 | case 'help':
54 | return ;
55 | default:
56 | return null;
57 | }
58 | };
59 |
60 | const getMessageTextColor = () => {
61 | return messageType === 'error' ? '#BB032A' : '#000000';
62 | };
63 |
64 | const borderStyles = getBorderStyles();
65 |
66 | const containerStyle = createStyle({
67 | className: `${className || ''} mb-4 bg-white p-2`,
68 | style: [
69 | {
70 | marginBottom: 16,
71 | backgroundColor: 'white',
72 | padding: 8,
73 | width: '48%'
74 | },
75 | style
76 | ].filter(Boolean)
77 | });
78 |
79 | const labelStyle = createStyle({
80 | className: "text-sm font-medium text-black mb-1",
81 | style: {
82 | fontSize: 14,
83 | fontWeight: '500',
84 | color: '#000000',
85 | marginBottom: 4
86 | }
87 | });
88 |
89 | const touchableStyle = createStyle({
90 | className: `w-full min-h-32 p-2 ${borderStyles.className} bg-gray-100`,
91 | style: [
92 | {
93 | width: '100%',
94 | minHeight: 128,
95 | padding: 8,
96 | backgroundColor: '#f3f4f6'
97 | },
98 | borderStyles.style
99 | ].filter(Boolean)
100 | });
101 |
102 | const textStyle = createStyle({
103 | className: hasValue ? 'text-black font-medium' : 'text-gray-500',
104 | style: {
105 | color: hasValue ? '#000000' : '#6b7280',
106 | fontWeight: hasValue ? '500' : 'normal'
107 | }
108 | });
109 |
110 | const messageContainerStyle = createStyle({
111 | className: "flex-row items-center mt-1 space-x-1",
112 | style: {
113 | flexDirection: 'row',
114 | alignItems: 'center',
115 | marginTop: 4
116 | }
117 | });
118 |
119 | const messageTextStyle = createStyle({
120 | className: "text-xs",
121 | style: {
122 | fontSize: 12,
123 | color: getMessageTextColor(),
124 | marginLeft: 4
125 | }
126 | });
127 |
128 | return (
129 |
130 | {label}
131 |
136 |
137 | {hasValue ? value : placeholder}
138 |
139 |
140 | {messageType !== 'none' && message && (
141 |
142 | {getMessageIcon()}
143 |
144 | {message}
145 |
146 |
147 | )}
148 |
149 | );
150 | };
151 |
152 | export default Tabs;
153 |
154 |
--------------------------------------------------------------------------------
/src/Button/icon.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Pressable, Text, View } from "react-native";
3 | import { ActivityIndicator } from "react-native";
4 | import { Ionicons } from "@expo/vector-icons";
5 | import { createStyle } from '../utils/styleCompat';
6 | import { ButtonProps } from "./Button.type";
7 |
8 | const Icon: React.FC = ({
9 | label = "Button",
10 | state = "default",
11 | size = "medium",
12 | icon = "right",
13 | disabled,
14 | background,
15 | variant = "primary",
16 | className,
17 | style,
18 | ...props
19 | }) => {
20 |
21 | const buttonVariant = variant;
22 |
23 | // Size mappings for React Native styles
24 | const sizeMappings = {
25 | small: { minWidth: 169, padding: 10 },
26 | medium: { minWidth: 270, padding: 10 },
27 | large: { minWidth: 270, padding: 15 },
28 | };
29 |
30 | // Primary button state styles
31 | const primaryStateMappings = {
32 | default: { backgroundColor: "#2563eb" },
33 | pressed: { backgroundColor: "#1d4ed8" },
34 | hover: { backgroundColor: "#3b82f6" },
35 | disabled: { backgroundColor: "#d1d5db" },
36 | loading: { backgroundColor: "#2563eb" },
37 | };
38 |
39 | // Secondary button state styles
40 | const secondaryStateMappings = {
41 | default: { borderWidth: 1, borderColor: "#d1d5db", backgroundColor: "#ffffff" },
42 | pressed: { borderWidth: 1, borderColor: "#9ca3af", backgroundColor: "#f9fafb" },
43 | hover: { borderWidth: 1, borderColor: "#9ca3af", backgroundColor: "#f9fafb" },
44 | disabled: { borderWidth: 1, borderColor: "#e5e7eb", backgroundColor: "#f3f4f6" },
45 | loading: { borderWidth: 1, borderColor: "#d1d5db", backgroundColor: "#ffffff" },
46 | };
47 |
48 | background = disabled ? null : background;
49 |
50 | const [isHovered, setIsHovered] = useState(false);
51 |
52 | const isHoverEffectEnabled = !disabled && state !== "disabled" && state !== "loading";
53 |
54 | // Determine current state for styling
55 | const currentState = disabled ? "disabled" : isHovered && isHoverEffectEnabled ? "hover" : state;
56 | const stateStyle = buttonVariant === "primary" ? primaryStateMappings[currentState] : secondaryStateMappings[currentState];
57 |
58 | const iconColor =
59 | state === "disabled"
60 | ? "#868686"
61 | : buttonVariant === "primary"
62 | ? "#ffffff"
63 | : "#000000";
64 |
65 | // Create the combined container style
66 | const containerStyle = createStyle({
67 | className: `${className || ''} flex flex-row items-center justify-between`,
68 | style: [
69 | sizeMappings[size],
70 | stateStyle,
71 | background ? { backgroundColor: background } : {},
72 | style
73 | ].filter(Boolean)
74 | });
75 |
76 | // Text styles
77 | const getTextColor = () => {
78 | if (state === "disabled") return "#868686";
79 | if (buttonVariant === "primary") return "#ffffff";
80 | return "#000000";
81 | };
82 |
83 | const textStyle = createStyle({
84 | className: "font-medium",
85 | style: {
86 | color: getTextColor(),
87 | fontSize: 16,
88 | fontWeight: "500"
89 | }
90 | });
91 |
92 | const loadingContainerStyle = createStyle({
93 | className: "flex-1 justify-center items-center"
94 | });
95 |
96 | return (
97 | isHoverEffectEnabled && setIsHovered(true)}
100 | onPressOut={() => isHoverEffectEnabled && setIsHovered(false)}
101 | style={containerStyle}
102 | {...props}
103 | >
104 | {state === "loading" ? (
105 |
106 |
107 |
108 | ) : (
109 | <>
110 | {icon === "left" && (
111 |
117 | )}
118 |
119 |
120 | {label}
121 |
122 |
123 | {icon === "right" && (
124 |
130 | )}
131 | >
132 | )}
133 |
134 | );
135 | };
136 |
137 | export default Icon;
--------------------------------------------------------------------------------
/src/Button/Button.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Pressable, Text } from "react-native";
3 | import { ActivityIndicator } from "react-native";
4 | import { createStyle } from '../utils/styleCompat';
5 | import { PressableComponentProps, ComponentSize, ComponentState } from '../types/common';
6 |
7 | export interface ButtonProps extends PressableComponentProps {
8 | label: string;
9 | variant?: "primary" | "secondary";
10 | size?: ComponentSize;
11 | icon?: "right" | "left";
12 | state?: ComponentState;
13 | background?: string | null;
14 | }
15 |
16 | const Button: React.FC = ({
17 | label = "Button",
18 | state = "default",
19 | size = "medium",
20 | disabled,
21 | background,
22 | variant = "primary",
23 | className,
24 | style,
25 | ...props
26 | }) => {
27 | const buttonVariant = variant;
28 |
29 | const sizes = {
30 | small: "min-w-[80px] h-fit p-[10px]",
31 | medium: "min-w-[120px] h-fit p-[10px]",
32 | large: "min-w-[160px] h-fit p-[15px]",
33 | };
34 |
35 | const sizeMappings = {
36 | small: { minWidth: 80, padding: 10 },
37 | medium: { minWidth: 120, padding: 10 },
38 | large: { minWidth: 160, padding: 15 },
39 | };
40 |
41 | const primaryStates = {
42 | default: "bg-blue-600",
43 | pressed: "bg-blue-700",
44 | hover: "bg-blue-500",
45 | disabled: "bg-gray-300",
46 | loading: "bg-blue-600",
47 | };
48 |
49 | const primaryStateMappings = {
50 | default: { backgroundColor: "#2563eb" },
51 | pressed: { backgroundColor: "#1d4ed8" },
52 | hover: { backgroundColor: "#3b82f6" },
53 | disabled: { backgroundColor: "#d1d5db" },
54 | loading: { backgroundColor: "#2563eb" },
55 | };
56 |
57 | const secondaryStates = {
58 | default: 'border border-gray-300 bg-white',
59 | pressed: 'border border-gray-400 bg-gray-50',
60 | hover: 'border border-gray-400 bg-gray-50',
61 | disabled: 'border border-gray-200 bg-gray-100',
62 | loading: 'border border-gray-300 bg-white',
63 | };
64 |
65 | const secondaryStateMappings = {
66 | default: { borderWidth: 1, borderColor: "#d1d5db", backgroundColor: "#ffffff" },
67 | pressed: { borderWidth: 1, borderColor: "#9ca3af", backgroundColor: "#f9fafb" },
68 | hover: { borderWidth: 1, borderColor: "#9ca3af", backgroundColor: "#f9fafb" },
69 | disabled: { borderWidth: 1, borderColor: "#e5e7eb", backgroundColor: "#f3f4f6" },
70 | loading: { borderWidth: 1, borderColor: "#d1d5db", backgroundColor: "#ffffff" },
71 | };
72 |
73 | background = disabled ? null : background;
74 |
75 | const [isHovered, setIsHovered] = useState(false);
76 |
77 | const isHoverEffectEnabled = !disabled && state !== "disabled" && state !== "loading";
78 |
79 | // Determine current state for styling
80 | const currentState = disabled ? "disabled" : isHovered && isHoverEffectEnabled ? "hover" : state;
81 | const stateClassName = buttonVariant === "primary" ? primaryStates[currentState] : secondaryStates[currentState];
82 | const stateStyle = buttonVariant === "primary" ? primaryStateMappings[currentState] : secondaryStateMappings[currentState];
83 |
84 | // Create the combined container style
85 | const containerStyle = createStyle({
86 | className: `${className || ''} flex items-center justify-center ${stateClassName}`,
87 | style: [
88 | sizeMappings[size],
89 | stateStyle,
90 | background ? { backgroundColor: background } : {},
91 | style
92 | ].filter(Boolean)
93 | });
94 |
95 | // Text styles
96 | const getTextColor = () => {
97 | if (state === "disabled") return "#868686";
98 | if (buttonVariant === "primary") return "#ffffff";
99 | return "#000000";
100 | };
101 |
102 | const textStyle = createStyle({
103 | className: "font-medium",
104 | style: {
105 | color: getTextColor(),
106 | fontSize: 16,
107 | fontWeight: "500"
108 | }
109 | });
110 |
111 | return (
112 | isHoverEffectEnabled && setIsHovered(true)}
115 | onPressOut={() => isHoverEffectEnabled && setIsHovered(false)}
116 | style={containerStyle}
117 | {...props}
118 | >
119 | {state === "loading" ? (
120 |
124 | ) : (
125 |
126 | {label}
127 |
128 | )}
129 |
130 | );
131 | };
132 |
133 | export default Button;
134 |
--------------------------------------------------------------------------------
/src/Button/typeDoc.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { Pressable, Text, View, ActivityIndicator } from "react-native";
3 | import { Ionicons } from "@expo/vector-icons";
4 | import { createStyle } from '../utils/styleCompat';
5 | import { TypeDocsProps } from "./Button.type";
6 |
7 | const TypeDoc: React.FC = ({
8 | icon = "right",
9 | buttonLabel = "Button",
10 | label = "I agree to the terms and conditions.",
11 | size = "medium",
12 | state = "default",
13 | disabled = false,
14 | background = null,
15 | onClick = () => { },
16 | className,
17 | style,
18 | ...props
19 | }) => {
20 | const [isChecked, setIsChecked] = useState(false);
21 | const [isHovered, setIsHovered] = useState(false);
22 | const [isPressed, setIsPressed] = useState(false);
23 |
24 | useEffect(() => {
25 | if (state === "loading" || state === "pressed") {
26 | setIsChecked(true);
27 | }
28 | }, [state]);
29 |
30 | // Size mappings for React Native styles
31 | const sizeMappings = {
32 | small: { width: '75%', minWidth: 80, padding: 10 },
33 | medium: { width: '100%', minWidth: 120, padding: 10 },
34 | large: { width: '100%', minWidth: 160, padding: 15 },
35 | };
36 |
37 | // Button state styles
38 | const buttonStateMappings = {
39 | default: { backgroundColor: "#000000" },
40 | pressed: { backgroundColor: "#1f2937" },
41 | hover: { backgroundColor: "#111827" },
42 | disabled: { backgroundColor: "#d1d5db" },
43 | loading: { backgroundColor: "#000000" },
44 | };
45 |
46 | const isButtonDisabled = disabled || !isChecked;
47 |
48 | const isHoverEffectEnabled = !isButtonDisabled && state !== "disabled" && state !== "loading";
49 |
50 | const handlePress = () => {
51 | if (!isButtonDisabled) {
52 | setIsPressed(true);
53 | setTimeout(() => setIsPressed(false), 100);
54 | }
55 | };
56 |
57 | // Create container styles
58 | const containerStyle = createStyle({
59 | className: `${className || ''} justify-center items-center p-5`,
60 | style: [{ width: 335 }, style].filter(Boolean)
61 | });
62 |
63 | const checkboxContainerStyle = createStyle({
64 | className: "flex-row items-center mb-5"
65 | });
66 |
67 | const labelTextStyle = createStyle({
68 | className: icon === "right" ? "ml-2.5" : "mr-2.5",
69 | style: { fontSize: 16 }
70 | });
71 |
72 | // Determine current button state for styling
73 | const currentButtonState = isButtonDisabled ? "disabled" : isPressed ? "pressed" : isHovered && isHoverEffectEnabled ? "hover" : state;
74 | const buttonStateStyle = buttonStateMappings[currentButtonState];
75 |
76 | const buttonStyle = createStyle({
77 | className: "flex items-center justify-center",
78 | style: [
79 | sizeMappings[size],
80 | buttonStateStyle,
81 | background ? { backgroundColor: background } : {}
82 | ].filter(Boolean)
83 | });
84 |
85 | const buttonTextStyle = createStyle({
86 | style: {
87 | color: isButtonDisabled ? "#868686" : "#ffffff",
88 | fontSize: 16,
89 | fontWeight: "500"
90 | }
91 | });
92 |
93 | return (
94 |
95 | setIsChecked(!isChecked)}
97 | style={checkboxContainerStyle}
98 | {...props}
99 | >
100 | {icon === "right" && (
101 | <>
102 | {isChecked ? (
103 |
104 | ) : (
105 |
106 | )}
107 | {label}
108 | >
109 | )}
110 |
111 | {icon === "left" && (
112 | <>
113 | {label}
114 | {isChecked ? (
115 |
116 | ) : (
117 |
118 | )}
119 | >
120 | )}
121 |
122 |
123 | isHoverEffectEnabled && setIsHovered(true)}
127 | onPressOut={() => isHoverEffectEnabled && setIsHovered(false)}
128 | style={buttonStyle}
129 | >
130 | {state === "loading" ? (
131 |
132 | ) : (
133 |
134 | {buttonLabel}
135 |
136 | )}
137 |
138 |
139 | );
140 | };
141 |
142 | export default TypeDoc;
--------------------------------------------------------------------------------
/src/Input/Autocomplete.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { TextInput, View, ActivityIndicator, TouchableOpacity, Text } from "react-native";
3 | import { Ionicons } from "@expo/vector-icons";
4 | import { AInputProp } from "./Input.type";
5 |
6 | const Sizes = {
7 | small: "w-[375px] h-[36px]",
8 | medium: "w-[375px] h-[48px]",
9 | large: "w-[375px] h-[56px]",
10 | full: "w-full h-full",
11 | fit: "w-fit h-full",
12 | };
13 |
14 | export default function AutoComplete({
15 | Size = "small",
16 | Label = "",
17 | Hint = "",
18 | State = "Default",
19 | value = "",
20 | input = [],
21 | curved = false,
22 | ...props
23 | }: AInputProp) {
24 | const [inputValue, setInputValue] = useState(value);
25 | const [isFocused, setIsFocused] = useState(false);
26 | const [suggestions, setSuggestions] = useState([]);
27 |
28 | const handleInputChange = (text: string) => {
29 | setInputValue(text);
30 |
31 | if (text.length > 0) {
32 | const filteredSuggestions = input.filter((item) =>
33 | item.toLowerCase().startsWith(text.toLowerCase())
34 | );
35 | setSuggestions(filteredSuggestions);
36 | } else {
37 | setSuggestions([]);
38 | }
39 | };
40 |
41 | const clearInput = () => {
42 | setInputValue("");
43 | setSuggestions([]);
44 | };
45 |
46 | const getBorderColor = () => {
47 | switch (State) {
48 | case "Error":
49 | return "border-borderNegative";
50 | case "Success":
51 | return "border-borderPositive";
52 | case "Default":
53 | return isFocused ? "border-black" : "border-transparent";
54 | default:
55 | return "border-transparent";
56 | }
57 | };
58 |
59 | const getHintColor = () => {
60 | switch (State) {
61 | case "Error":
62 | return "text-contentNegative";
63 | case "Success":
64 | return "text-contentPositive";
65 | default:
66 | return "text-inputHint";
67 | }
68 | };
69 |
70 | return (
71 |
72 | {Label ? (
73 |
74 | {Label}
75 |
76 | ) : null}
77 |
78 |
79 |
80 | setIsFocused(true)}
85 | onBlur={(e) => {
86 | setIsFocused(false)
87 | setInputValue(suggestions[0] ? suggestions[0] : inputValue)
88 |
89 | }}
90 | onChangeText={handleInputChange}
91 | style={{ width: 375 }}
92 | {...props}
93 | />
94 |
95 | {State === "Default" && inputValue.length > 0 && (
96 |
100 |
101 |
102 | )}
103 |
104 | {State === "Loading" && (
105 |
106 |
107 |
108 | )}
109 |
110 |
111 | {suggestions.length > 0 && (
112 |
113 |
120 |
121 | )}
122 |
123 | {Hint ? (
124 |
125 |
126 | {State === "Error" && (
127 |
128 | )}
129 |
130 | {State === "Success" && (
131 |
132 | )}
133 | {Hint}
134 |
135 | ) : null}
136 |
137 | );
138 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reactnativeepictrailsds",
3 | "version": "1.0.2",
4 | "description": "A modern, production-ready design system for React Native — featuring pre-styled, customizable components, state-based UI logic, theme support, and seamless integration with Tailwind, Expo, and TypeScript. Built for scalable mobile app development with accessibility and performance in mind.",
5 | "keywords": [
6 | "react-native",
7 | "design-system",
8 | "react-native-ui",
9 | "ui-library",
10 | "component-library",
11 | "mobile-ui",
12 | "native-components",
13 | "tailwindcss",
14 | "nativewind",
15 | "expo",
16 | "typescript",
17 | "theme-provider",
18 | "lightweight-ui",
19 | "cross-platform",
20 | "mobile-first",
21 | "react-native-theme",
22 | "custom-components",
23 | "prebuilt-components",
24 | "reusable-ui",
25 | "dark-mode",
26 | "light-mode",
27 | "variant-based-ui",
28 | "react-native-design-system",
29 | "gesture-ui",
30 | "performance",
31 | "accessibility",
32 | "a11y",
33 | "animation",
34 | "stateful-ui",
35 | "customizable",
36 | "ios",
37 | "android",
38 | "open-source",
39 | "storybook",
40 | "react-native-paper",
41 | "react-native-elements",
42 | "responsive-design",
43 | "material-design",
44 | "modern-ui",
45 | "adaptive-ui",
46 | "headless-ui",
47 | "hover-states",
48 | "pressable-components"
49 | ],
50 | "homepage": "https://github.com/gaureshpai/reactnativeepictrailsds#readme",
51 | "bugs": {
52 | "url": "https://github.com/gaureshpai/reactnativeepictrailsds/issues"
53 | },
54 | "repository": {
55 | "type": "git",
56 | "url": "git+https://github.com/gaureshpai/reactnativeepictrailsds.git"
57 | },
58 | "license": "MIT",
59 | "author": "Gauresh G Pai",
60 | "main": "dist/commonjs/index.js",
61 | "module": "dist/commonjs/index.js",
62 | "react-native": "dist/commonjs/index.js",
63 | "types": "dist/typescript/index.d.ts",
64 | "files": [
65 | "dist/",
66 | "preset.js",
67 | "package.json",
68 | "README.md",
69 | "global.css"
70 | ],
71 | "publishConfig": {
72 | "registry": "https://npm.pkg.github.com"
73 | },
74 | "exports": {
75 | ".": {
76 | "types": "./dist/typescript/index.d.ts",
77 | "import": "./dist/commonjs/index.js",
78 | "require": "./dist/commonjs/index.js",
79 | "react-native": "./dist/commonjs/index.js",
80 | "default": "./dist/commonjs/index.js"
81 | },
82 | "./preset": "./preset.js"
83 | },
84 | "scripts": {
85 | "start": "expo start",
86 | "reset-project": "node ./scripts/reset-project.js",
87 | "android": "expo run:android",
88 | "ios": "expo run:ios",
89 | "web": "expo start --web",
90 | "test": "jest --watchAll",
91 | "lint": "expo lint",
92 | "build": "tsc --project tsconfig.build.json",
93 | "prepare": "node build.js",
94 | "postbuild": "npx tailwindcss --input src/styles/global.css --output dist/styles.css"
95 | },
96 | "react-native-builder-bob": {
97 | "source": "src",
98 | "output": "dist",
99 | "targets": [
100 | {
101 | "target": "commonjs",
102 | "copyFlow": false
103 | },
104 | {
105 | "target": "module",
106 | "copyFlow": false
107 | },
108 | {
109 | "target": "typescript",
110 | "copyFlow": false
111 | }
112 | ]
113 | },
114 | "dependencies": {
115 | "@emotion/react": "^11.13.5",
116 | "@emotion/styled": "^11.13.5",
117 | "@expo/vector-icons": "^14.0.5",
118 | "@react-native-community/cli": "^20.0.2",
119 | "@react-navigation/bottom-tabs": "^7.2.0",
120 | "@react-navigation/native": "^7.0.14",
121 | "autoprefixer": "^10.4.21",
122 | "expo": "~52.0.46",
123 | "expo-blur": "~14.0.3",
124 | "expo-constants": "~17.0.8",
125 | "expo-font": "~13.0.4",
126 | "expo-haptics": "~14.0.1",
127 | "expo-linking": "~7.0.5",
128 | "expo-router": "~4.0.20",
129 | "expo-splash-screen": "~0.29.24",
130 | "expo-status-bar": "~2.0.1",
131 | "expo-symbols": "~0.2.2",
132 | "expo-system-ui": "~4.0.9",
133 | "expo-web-browser": "~14.0.2",
134 | "nativewind": "^4.1.23",
135 | "postcss": "^8.5.3",
136 | "react": "^18.3.1",
137 | "react-dom": "^18.3.1",
138 | "react-native": "0.76.9",
139 | "react-native-gesture-handler": "~2.20.2",
140 | "react-native-reanimated": "^3.16.2",
141 | "react-native-safe-area-context": "^4.12.0",
142 | "react-native-screens": "~4.4.0",
143 | "react-native-video": "^6.14.1",
144 | "react-native-web": "~0.19.13",
145 | "react-native-webview": "13.12.5",
146 | "tailwind-merge": "^3.2.1",
147 | "tailwindcss": "^3.4.17"
148 | },
149 | "devDependencies": {
150 | "@babel/core": "^7.26.0",
151 | "@types/jest": "^29.5.14",
152 | "@types/react": "~18.3.12",
153 | "@types/react-native": "^0.73.0",
154 | "@types/react-test-renderer": "^18.3.0",
155 | "jest": "^29.7.0",
156 | "jest-expo": "~52.0.6",
157 | "react-native-builder-bob": "^0.30.2",
158 | "react-test-renderer": "18.3.1",
159 | "typescript": "^5.9.2"
160 | },
161 | "jest": {
162 | "preset": "jest-expo"
163 | },
164 | "directories": {
165 | "example": "example"
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src/androidmodalsheets/androidmodalsheets.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text, TouchableOpacity, SafeAreaView } from 'react-native';
3 | import { Ionicons } from '@expo/vector-icons';
4 | import RectButton from "../Button/rect";
5 | import { AndroidModalSheetProps } from './androidmodalsheets.type';
6 | import { createStyle } from '../utils/styleCompat';
7 | import { ContainerComponentProps } from '../types/common';
8 |
9 | const AndroidModalSheet: React.FC = ({
10 | title,
11 | label,
12 | action = 1,
13 | compact = false,
14 | onClose,
15 | onButtonPress,
16 | onClipboard,
17 | onForward,
18 | children,
19 | className,
20 | style,
21 | ...props
22 | }) => {
23 | const containerStyle = createStyle({
24 | className: `${className || ''} flex-1 bg-gray-800 bg-opacity-50 w-full`,
25 | style: [
26 | {
27 | flex: 1,
28 | backgroundColor: 'rgba(31, 41, 55, 0.5)',
29 | width: '100%'
30 | },
31 | style
32 | ].filter(Boolean)
33 | });
34 |
35 | return (
36 |
37 |
41 |
45 |
49 |
53 |
57 |
61 |
62 |
63 |
64 | {title}
68 |
69 |
73 | {action >= 3 && (
74 |
78 |
79 |
80 | )}
81 | {action >= 2 && (
82 |
86 |
87 |
88 | )}
89 | {action === 1 && }
93 |
94 |
95 | {!compact && (
96 | {title}
100 | )}
101 |
102 |
103 |
107 |
111 | {children}
112 |
113 |
114 |
115 |
119 |
120 |
121 |
122 |
126 |
130 |
131 |
132 |
133 |
134 | );
135 | };
136 |
137 | export default AndroidModalSheet;
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, caste, color, religion, or sexual
10 | identity and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the overall
26 | community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or advances of
31 | any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email address,
35 | without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | [INSERT CONTACT METHOD].
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series of
86 | actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or permanent
93 | ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within the
113 | community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.1, available at
119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120 |
121 | Community Impact Guidelines were inspired by
122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123 |
124 | For answers to common questions about this code of conduct, see the FAQ at
125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126 | [https://www.contributor-covenant.org/translations][translations].
127 |
128 | [homepage]: https://www.contributor-covenant.org
129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130 | [Mozilla CoC]: https://github.com/mozilla/diversity
131 | [FAQ]: https://www.contributor-covenant.org/faq
132 | [translations]: https://www.contributor-covenant.org/translations
--------------------------------------------------------------------------------
/src/grid/grid.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from "react";
2 | import { View, StyleSheet } from "react-native";
3 | import { createStyle } from '../utils/styleCompat';
4 | import { GridProps, ItemProps } from "./Grid.type";
5 | import { Breakpoint, ResponsiveValue } from "./Grid.type";
6 |
7 | function isResponsiveObject(value: ResponsiveValue): value is Partial> {
8 | return typeof value === 'object' && value !== null && !Array.isArray(value);
9 | }
10 |
11 | function resolveResponsiveValue(value: ResponsiveValue | undefined, breakpoint: Breakpoint = 'xs'): T | undefined {
12 | if (value === undefined) return undefined;
13 |
14 | if (isResponsiveObject(value)) {
15 | if (value[breakpoint] !== undefined) {
16 | return value[breakpoint];
17 | }
18 |
19 | if (value.xs !== undefined) {
20 | return value.xs;
21 | }
22 |
23 | return undefined;
24 | }
25 |
26 | return value;
27 | }
28 |
29 | const Grid = React.forwardRef(
30 | (
31 | {
32 | children,
33 | container = false,
34 | spacing,
35 | rowSpacing,
36 | columnSpacing,
37 | columns,
38 | rows,
39 | direction = "row",
40 | className = "",
41 | style,
42 | size,
43 | ...props
44 | },
45 | ref
46 | ) => {
47 | const currentBreakpoint: Breakpoint = 'xs';
48 |
49 | const resolvedDirection = resolveResponsiveValue(direction, currentBreakpoint) || "row";
50 | const resolvedColumns = resolveResponsiveValue(columns, currentBreakpoint);
51 | const resolvedRows = resolveResponsiveValue(rows, currentBreakpoint);
52 | const resolvedSpacing = resolveResponsiveValue(spacing, currentBreakpoint);
53 | const resolvedRowSpacing = resolveResponsiveValue(rowSpacing, currentBreakpoint);
54 | const resolvedColumnSpacing = resolveResponsiveValue(columnSpacing, currentBreakpoint);
55 | const resolvedSize = resolveResponsiveValue(size, currentBreakpoint);
56 |
57 | const effectiveRowGap = resolvedRowSpacing !== undefined ? resolvedRowSpacing * 8 :
58 | resolvedSpacing !== undefined ? resolvedSpacing * 8 : 0;
59 |
60 | const effectiveColumnGap = resolvedColumnSpacing !== undefined ? resolvedColumnSpacing * 8 :
61 | resolvedSpacing !== undefined ? resolvedSpacing * 8 : 0;
62 |
63 | let gridStyles: any = {
64 | display: "flex",
65 | flexDirection: resolvedDirection,
66 | flexWrap: "wrap",
67 | rowGap: effectiveRowGap,
68 | columnGap: effectiveColumnGap,
69 | };
70 |
71 | if (container) {
72 | gridStyles.width = "100%";
73 | }
74 |
75 | if (resolvedSize !== undefined) {
76 | const sizePercentage = ((resolvedSize / 12) * 100).toFixed(2) + '%';
77 | gridStyles.width = sizePercentage;
78 | gridStyles.flexBasis = sizePercentage;
79 | gridStyles.flexGrow = 0;
80 | gridStyles.flexShrink = 0;
81 | }
82 |
83 | const gridContext = {
84 | direction: resolvedDirection,
85 | columns: resolvedColumns || 12,
86 | rows: resolvedRows,
87 | columnGap: effectiveColumnGap,
88 | rowGap: effectiveRowGap,
89 | };
90 |
91 | const gridChildren = React.Children.map(children, child => {
92 | if (React.isValidElement(child)) {
93 | return React.cloneElement(child, {
94 | ...child.props,
95 | _gridContext: gridContext
96 | });
97 | }
98 | return child;
99 | });
100 |
101 | const containerStyle = createStyle({
102 | className: className,
103 | style: [styles.base, gridStyles, style].filter(Boolean)
104 | });
105 |
106 | return (
107 |
108 | {gridChildren}
109 |
110 | );
111 | }
112 | );
113 |
114 | Grid.displayName = "Grid";
115 |
116 | interface ExtendedItemProps extends ItemProps {
117 | _gridContext?: {
118 | direction: "row" | "column";
119 | columns: number;
120 | rows?: number;
121 | columnGap: number;
122 | rowGap: number;
123 | };
124 | }
125 |
126 | const GridItem = React.forwardRef(
127 | ({ children, xs, sm, md, lg, xl, className = "", style, _gridContext, size, ...props }, ref) => {
128 | const currentBreakpoint: Breakpoint = 'xs';
129 |
130 | let span = size;
131 | if (span === undefined) {
132 | if ((currentBreakpoint as Breakpoint) === 'xl' && xl !== undefined) span = xl;
133 | else if ((currentBreakpoint as Breakpoint) === 'lg' && lg !== undefined) span = lg;
134 | else if ((currentBreakpoint as Breakpoint) === 'md' && md !== undefined) span = md;
135 | else if ((currentBreakpoint as Breakpoint) === 'sm' && sm !== undefined) span = sm;
136 | else if (xs !== undefined) span = xs;
137 | else span = 1;
138 | }
139 |
140 | let itemStyles: any = {};
141 |
142 | if (_gridContext) {
143 | const { direction, columns } = _gridContext;
144 |
145 | if (columns) {
146 | const widthPercentage = ((span / columns) * 100).toFixed(2) + '%';
147 |
148 | itemStyles.flexBasis = widthPercentage;
149 | itemStyles.maxWidth = widthPercentage;
150 | itemStyles.flexGrow = 0;
151 | itemStyles.flexShrink = 0;
152 | }
153 | } else {
154 | itemStyles.flexGrow = span;
155 | itemStyles.flexShrink = 0;
156 | itemStyles.flexBasis = "auto";
157 | }
158 |
159 | const itemStyle = createStyle({
160 | className: className,
161 | style: [styles.item, itemStyles, style].filter(Boolean)
162 | });
163 |
164 | return (
165 |
166 | {children}
167 |
168 | );
169 | }
170 | );
171 |
172 | GridItem.displayName = "GridItem";
173 |
174 | const styles = StyleSheet.create({
175 | base: {
176 | width: "100%",
177 | },
178 | item: {
179 | minWidth: 0,
180 | },
181 | });
182 |
183 | const GridRoot = Object.assign(Grid, { Item: GridItem });
184 |
185 | export { GridRoot as Grid };
186 | export type { GridProps, ItemProps as GridItemProps };
--------------------------------------------------------------------------------
/src/bottomsheet/bottomsheet.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import {
3 | View,
4 | Text,
5 | StyleSheet,
6 | TouchableOpacity,
7 | TextInput,
8 | } from "react-native";
9 | import { Ionicons } from "@expo/vector-icons";
10 | import { BottomSheetProps } from "./bottomsheet.type";
11 | import { RectButton } from "../Button";
12 |
13 | const BottomSheet: React.FC = ({
14 | showKnob = false,
15 | showButton = false,
16 | buttonLabel = "Submit",
17 | onButtonPress = () => { },
18 | showComments = false,
19 | comments = [],
20 | onAddComment = () => { },
21 | onLikeComment = () => { },
22 | onReplyComment = () => { },
23 | children,
24 | containerStyle = {},
25 | }) => {
26 | const [commentText, setCommentText] = useState("");
27 |
28 | const handleAddComment = (): void => {
29 | if (commentText.trim()) {
30 | onAddComment(commentText);
31 | setCommentText("");
32 | }
33 | };
34 |
35 | return (
36 |
37 | {showKnob && (
38 |
39 |
40 |
41 | )}
42 |
43 | {!showComments && (
44 |
45 | {children}
46 |
47 | )}
48 |
49 |
50 | {showComments && (
51 |
52 | {comments.map((comment, index) => (
53 |
54 |
55 |
56 |
57 | {comment.userInitials || "AB"}
58 |
59 |
60 |
61 | {comment.userName ||
62 | "Motoholic Serene Adventure in my motorbike"}
63 |
64 |
65 |
66 |
67 | onLikeComment(index)}
70 | >
71 |
72 |
73 |
74 | {comment.likes || 1}
75 |
76 |
77 | onReplyComment(index)}
80 | >
81 | Reply
82 |
83 |
84 |
85 |
86 | {comment.time || "12 hrs ago"}
87 |
88 |
89 | ))}
90 |
91 |
92 |
93 | A
94 |
95 |
102 |
106 | Post
107 |
108 |
109 |
110 | )}
111 |
112 | {showButton && (
113 |
114 | )}
115 |
116 |
117 |
118 |
119 |
120 | );
121 | };
122 |
123 | const styles = StyleSheet.create({
124 | container: {
125 | position: "absolute",
126 | bottom: 0,
127 | paddingHorizontal: 20,
128 | backgroundColor: "white",
129 | borderTopLeftRadius: 16,
130 | borderTopRightRadius: 16,
131 | overflow: "hidden",
132 | width: "100%",
133 | },
134 | knobContainer: {
135 | alignItems: "center",
136 | paddingVertical: 8,
137 | },
138 | knob: {
139 | width: 40,
140 | height: 4,
141 | backgroundColor: "#E0E0E0",
142 | borderRadius: 2,
143 | },
144 | customContentContainer: {
145 | width: "100%",
146 | minHeight: 200,
147 | padding: 5,
148 | marginTop: 10,
149 | backgroundColor: "#eee",
150 | },
151 | contentContainer: {
152 | paddingVertical: 16,
153 | },
154 | commentsContainer: {
155 | marginBottom: 16,
156 | },
157 | commentItem: {
158 | backgroundColor: "#F5F5F5",
159 | borderRadius: 8,
160 | padding: 16,
161 | marginBottom: 12,
162 | },
163 | commentHeader: {
164 | flexDirection: "row",
165 | alignItems: "center",
166 | marginBottom: 8,
167 | },
168 | userAvatar: {
169 | width: 32,
170 | height: 32,
171 | borderRadius: 16,
172 | backgroundColor: "black",
173 | justifyContent: "center",
174 | alignItems: "center",
175 | marginRight: 8,
176 | },
177 | avatarText: {
178 | color: "white",
179 | fontWeight: "bold",
180 | },
181 | userName: {
182 | fontWeight: "500",
183 | flex: 1,
184 | },
185 | commentActions: {
186 | flexDirection: "row",
187 | marginTop: 8,
188 | },
189 | likeButton: {
190 | flexDirection: "row",
191 | alignItems: "center",
192 | marginRight: 16,
193 | },
194 | likeIcon: {
195 | fontSize: 18,
196 | marginRight: 4,
197 | },
198 | likeCount: {
199 | fontSize: 14,
200 | },
201 | replyButton: {
202 | marginLeft: 8,
203 | },
204 | replyText: {
205 | color: "#666",
206 | fontWeight: "500",
207 | },
208 | commentTime: {
209 | color: "#999",
210 | fontSize: 12,
211 | marginTop: 6,
212 | },
213 | addCommentContainer: {
214 | flexDirection: "row",
215 | alignItems: "center",
216 | backgroundColor: "#F5F5F5",
217 | borderRadius: 8,
218 | padding: 8,
219 | },
220 | commentInput: {
221 | backgroundColor: "#E0E0E0",
222 | marginHorizontal: 8,
223 | flex: 1,
224 | height: 36,
225 | paddingHorizontal: 8,
226 | },
227 | postButton: {
228 | backgroundColor: "#333",
229 | paddingVertical: 8,
230 | paddingHorizontal: 16,
231 | borderRadius: 4,
232 | },
233 | postButtonText: {
234 | color: "white",
235 | fontWeight: "500",
236 | },
237 | indicatorContainer: {
238 | width: "100%",
239 | paddingHorizontal: 40,
240 | flexDirection: "row",
241 | marginTop: 0,
242 | },
243 | indicator: {
244 | height: 5,
245 | width: 120,
246 | marginBottom: 8,
247 | borderRadius: 5,
248 | },
249 | });
250 |
251 | export default BottomSheet;
--------------------------------------------------------------------------------
/src/alert/alert.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text, TouchableOpacity } from 'react-native';
3 | import { Ionicons } from '@expo/vector-icons';
4 | import { createStyle } from '../utils/styleCompat';
5 | import { ContainerComponentProps } from '../types/common';
6 |
7 | const alertStyles = {
8 | information: {
9 | backgroundColor: "#E8E8E8",
10 | color: "#1F1F1F",
11 | secondaryColor: "#DDDDDD",
12 | secondaryTextColor: "#1F1F1F",
13 | icon: "information-circle",
14 | },
15 | success: {
16 | backgroundColor: "#D3EFDA",
17 | color: "#166C3B",
18 | secondaryColor: "#B1EAC2",
19 | secondaryTextColor: "#166C3B",
20 | icon: "checkmark-circle",
21 | },
22 | warning: {
23 | backgroundColor: "#FEE2D4",
24 | color: "#C54600",
25 | secondaryColor: "#FFD3BC",
26 | secondaryTextColor: "#C54600",
27 | icon: "warning",
28 | },
29 | error: {
30 | backgroundColor: "#FFE1DE",
31 | color: "#950F22",
32 | secondaryColor: "#FFD2CD",
33 | secondaryTextColor: "#950F22",
34 | icon: "alert-circle",
35 | },
36 | };
37 |
38 | type AlertType = "information" | "success" | "warning" | "error";
39 |
40 | export interface AlertProps extends ContainerComponentProps {
41 | message: string;
42 | label?: string;
43 | description?: string;
44 | icon?: boolean;
45 | inline?: boolean;
46 | suppressed?: boolean;
47 | type?: AlertType;
48 | onPrimaryPress?: () => void;
49 | onSecondaryPress?: () => void;
50 | }
51 |
52 | const Alert: React.FC = ({
53 | message,
54 | label = '',
55 | description = '',
56 | icon = true,
57 | inline = false,
58 | suppressed = true,
59 | type = 'information',
60 | onPrimaryPress = () => { },
61 | onSecondaryPress = () => { },
62 | className,
63 | style,
64 | ...props
65 | }) => {
66 | const { backgroundColor, color, secondaryColor, secondaryTextColor } = alertStyles[type];
67 |
68 | if (inline) {
69 | const inlineContainerStyle = createStyle({
70 | className: `${className || ''} flex-row items-center bg-white p-4`,
71 | style: [
72 | {
73 | borderTopColor: color,
74 | borderTopWidth: 2,
75 | width: '80%'
76 | },
77 | style
78 | ].filter(Boolean)
79 | });
80 |
81 | const iconStyle = createStyle({
82 | className: "mr-2",
83 | style: { marginRight: 8 }
84 | });
85 |
86 | const messageStyle = createStyle({
87 | className: "flex-1 text-gray-800 font-bold",
88 | style: {
89 | flex: 1,
90 | color: "#1f2937",
91 | fontWeight: "bold"
92 | }
93 | });
94 |
95 | const primaryButtonStyle = createStyle({
96 | className: "p-2 px-8 items-center",
97 | style: {
98 | backgroundColor: color,
99 | width: '25%',
100 | padding: 8,
101 | paddingHorizontal: 32,
102 | alignItems: "center"
103 | }
104 | });
105 |
106 | const buttonTextStyle = createStyle({
107 | className: "text-white",
108 | style: { color: "#ffffff" }
109 | });
110 |
111 | return (
112 |
113 | {icon && (
114 |
120 | )}
121 | {message}
122 | {label && (
123 |
128 | {label}
129 |
130 | )}
131 |
132 | );
133 | }
134 |
135 | const containerStyle = createStyle({
136 | className: `${className || ''}`,
137 | style: [
138 | {
139 | borderTopWidth: 2,
140 | borderTopColor: color,
141 | width: '80%'
142 | },
143 | style
144 | ].filter(Boolean)
145 | });
146 |
147 | const backgroundStyle = createStyle({
148 | className: `${suppressed ? '' : 'bg-white'}`,
149 | style: {
150 | backgroundColor: suppressed ? backgroundColor : 'white'
151 | }
152 | });
153 |
154 | const headerStyle = createStyle({
155 | className: "flex-row items-center p-4",
156 | style: {
157 | flexDirection: "row",
158 | alignItems: "center",
159 | padding: 16
160 | }
161 | });
162 |
163 | const iconContainerStyle = createStyle({
164 | className: "mr-2",
165 | style: { marginRight: 8 }
166 | });
167 |
168 | const messageTextStyle = createStyle({
169 | className: "text-gray-800 flex-1 font-bold",
170 | style: {
171 | color: "#1f2937",
172 | flex: 1,
173 | fontWeight: "bold"
174 | }
175 | });
176 |
177 | const descriptionContainerStyle = createStyle({
178 | className: "px-4 pb-2",
179 | style: {
180 | paddingHorizontal: 16,
181 | paddingBottom: 8
182 | }
183 | });
184 |
185 | const descriptionTextStyle = createStyle({
186 | className: "text-gray-600",
187 | style: { color: "#4b5563" }
188 | });
189 |
190 | const buttonsContainerStyle = createStyle({
191 | className: "flex flex-row justify-between p-4",
192 | style: {
193 | flexDirection: "row",
194 | justifyContent: "space-between",
195 | padding: 16
196 | }
197 | });
198 |
199 | const primaryButtonStyle = createStyle({
200 | className: "p-2 px-8 items-center",
201 | style: {
202 | backgroundColor: color,
203 | width: '45%',
204 | padding: 8,
205 | paddingHorizontal: 32,
206 | alignItems: "center"
207 | }
208 | });
209 |
210 | const secondaryButtonStyle = createStyle({
211 | className: "p-2 px-8 items-center",
212 | style: {
213 | backgroundColor: secondaryColor,
214 | width: '45%',
215 | padding: 8,
216 | paddingHorizontal: 32,
217 | alignItems: "center"
218 | }
219 | });
220 |
221 | const primaryButtonTextStyle = createStyle({
222 | className: "text-white",
223 | style: { color: "#ffffff" }
224 | });
225 |
226 | const secondaryButtonTextStyle = createStyle({
227 | style: { color: secondaryTextColor }
228 | });
229 |
230 | return (
231 |
232 |
233 |
234 | {icon && (
235 |
241 | )}
242 | {message}
243 |
244 |
245 | {description && (
246 |
247 |
248 | {description}
249 |
250 |
251 | )}
252 |
253 | {label && (
254 |
255 |
260 | {label}
261 |
262 |
267 | {label}
268 |
269 |
270 | )}
271 |
272 |
273 | );
274 | };
275 |
276 | export default Alert;
277 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/src/socialmediastack/socialmediacontainer.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from "react";
2 | import {
3 | View,
4 | StyleSheet,
5 | Text,
6 | Image,
7 | Platform,
8 | Pressable,
9 | } from "react-native";
10 | import Video, { VideoRef } from "react-native-video";
11 | import {
12 | SocialMediaContainerProps,
13 | ImageContent,
14 | } from "./socialmediastack.type";
15 | const SocialMediaContainer: React.FC = ({
16 | contentItems = [],
17 | duration = 10000,
18 | onActiveIndexChange = () => {},
19 | index,
20 | }) => {
21 | const [activeIndex, setActiveIndex] = useState(index??0);
22 | const [imageLoaded, setImageLoaded] = useState(false);
23 | const [imageError, setImageError] = useState(false);
24 | const videoRef = useRef(null);
25 | const timerRef = useRef(null);
26 |
27 | useEffect(() => {
28 | if (contentItems.length === 0) return;
29 |
30 | // Reset states when content changes
31 | setImageLoaded(false);
32 | setImageError(false);
33 |
34 | // Clear any existing timer
35 | if (timerRef.current) {
36 | clearTimeout(timerRef.current);
37 | }
38 |
39 | const currentItem = contentItems[index ?? activeIndex];
40 | if (currentItem.type !== "video") {
41 | timerRef.current = setTimeout(() => {
42 | const nextIndex = ((index ?? activeIndex) + 1) % contentItems.length;
43 | setActiveIndex(nextIndex);
44 | onActiveIndexChange(nextIndex);
45 | }, duration);
46 | }
47 |
48 | return () => {
49 | if (timerRef.current) {
50 | clearTimeout(timerRef.current);
51 | }
52 | };
53 | }, [activeIndex, contentItems, duration, onActiveIndexChange]);
54 |
55 | // If no content items are provided
56 | if (contentItems.length === 0) {
57 | return (
58 |
59 |
60 | No content available
61 |
62 |
63 | );
64 | }
65 |
66 | const renderContent = () => {
67 | const currentItem = contentItems[activeIndex];
68 |
69 | switch (currentItem.type) {
70 | case "video":
71 | return (
72 |