@bubbasfw/ui 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +71 -2
- package/dist/index.cjs +110 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +107 -0
- package/dist/tokens.cjs +25 -0
- package/dist/tokens.d.cts +39 -0
- package/dist/tokens.d.ts +39 -0
- package/dist/tokens.js +23 -0
- package/package.json +85 -4
- package/src/components/Button.tsx +105 -0
- package/src/index.ts +6 -0
- package/src/tokens.ts +23 -0
- package/tailwind-preset.cjs +14 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Howeth and Harp
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,72 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @bubbasfw/ui
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Bubba’s universal React Native design system scaffold. The same NativeWind
|
|
4
|
+
Button uses `Pressable`, `Text`, and `ActivityIndicator` on native platforms and
|
|
5
|
+
React Native Web. This initial package contains the Button and foundation tokens.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Button } from '@bubbasfw/ui';
|
|
9
|
+
|
|
10
|
+
<Button onPress={save} loading={saving} loadingLabel="Saving…">
|
|
11
|
+
Save changes
|
|
12
|
+
</Button>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Variants: `primary`, `secondary`, `outline`, `ghost`. Sizes: `sm`, `md`, `lg`.
|
|
16
|
+
Use `className` for the Pressable and `textClassName` for the label. Native
|
|
17
|
+
Pressable props and refs are forwarded. Loading and disabled states block presses
|
|
18
|
+
and expose accessibility state. Label content should be text or inline native Text.
|
|
19
|
+
|
|
20
|
+
## Consumer setup
|
|
21
|
+
|
|
22
|
+
Baseline: React 19.2.3, React Native 0.86.3, React Native Web 0.21.4,
|
|
23
|
+
NativeWind 4.2.7, Tailwind 3.4.17. Configure the host app for NativeWind and scan
|
|
24
|
+
this package’s source so its utilities are generated:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
pnpm add @bubbasfw/ui nativewind@4.2.7
|
|
28
|
+
pnpm add -D tailwindcss@3.4.17
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Expo apps must also declare NativeWind 4's Babel runtime directly:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
pnpm add react-native-css-interop@0.2.7
|
|
35
|
+
npx expo install react-native-reanimated react-native-worklets react-native-safe-area-context
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The Babel preset emits `react-native-css-interop` imports into app files. Its
|
|
39
|
+
transitive dependency through NativeWind does not resolve from those files in
|
|
40
|
+
pnpm's isolated dependency layout.
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
const path = require('node:path');
|
|
44
|
+
const uiRoot = path.dirname(require.resolve('@bubbasfw/ui/package.json'));
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
content: [
|
|
48
|
+
'./App.{js,jsx,ts,tsx}',
|
|
49
|
+
'./app/**/*.{js,jsx,ts,tsx}',
|
|
50
|
+
'./pages/**/*.{js,jsx,ts,tsx}',
|
|
51
|
+
path.join(uiRoot, 'src/**/*.{ts,tsx}'),
|
|
52
|
+
],
|
|
53
|
+
presets: [
|
|
54
|
+
require('nativewind/preset'),
|
|
55
|
+
require('@bubbasfw/ui/tailwind-preset'),
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Expo requires NativeWind’s Metro/Babel setup and compatible native dependencies.
|
|
61
|
+
Next.js requires React Native Web aliasing, NativeWind’s JSX import source,
|
|
62
|
+
Tailwind 3/PostCSS, and runtime transpilation. App Router interactive consumers
|
|
63
|
+
need a client boundary. See the repository’s
|
|
64
|
+
[complete setup and runnable examples](https://github.com/handh-dev/bubbas-ui#readme).
|
|
65
|
+
|
|
66
|
+
The package exports `@bubbasfw/ui/tokens` without React runtime imports and
|
|
67
|
+
`@bubbasfw/ui/tailwind-preset` for shared color/radius utilities. Platform peers
|
|
68
|
+
are external; React Native Web is optional for native-only consumers.
|
|
69
|
+
|
|
70
|
+
Published releases use public npm access. The code is licensed under
|
|
71
|
+
[MIT](LICENSE). Font and artwork binaries are excluded and retain
|
|
72
|
+
their separate distribution rights. No trademark license is granted.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var reactNative = require('react-native');
|
|
6
|
+
var tailwindMerge = require('tailwind-merge');
|
|
7
|
+
var jsxRuntime = require('nativewind/jsx-runtime');
|
|
8
|
+
|
|
9
|
+
// src/tokens.ts
|
|
10
|
+
var tokens = {
|
|
11
|
+
brandVersion: "1.0.0",
|
|
12
|
+
colors: {
|
|
13
|
+
blue: "#007CE8",
|
|
14
|
+
red: "#CC0000",
|
|
15
|
+
redHover: "#A80000",
|
|
16
|
+
navy: "#03256C",
|
|
17
|
+
night: "#001845",
|
|
18
|
+
ink: "#131B23",
|
|
19
|
+
muted: "#526075",
|
|
20
|
+
line: "#D9E8FB",
|
|
21
|
+
white: "#FFFFFF",
|
|
22
|
+
gold: "#FED766",
|
|
23
|
+
disabled: "#F0F2F5",
|
|
24
|
+
disabledText: "#6B727E"
|
|
25
|
+
},
|
|
26
|
+
spacing: { 1: 4, 2: 8, 3: 12, 4: 16, 6: 24, 8: 32, 12: 48, 16: 64 },
|
|
27
|
+
radius: { control: 4, panel: 12 },
|
|
28
|
+
touchTarget: { minimum: 44, default: 48 }
|
|
29
|
+
};
|
|
30
|
+
var containerVariants = {
|
|
31
|
+
primary: "border-bubba-red bg-bubba-red",
|
|
32
|
+
secondary: "border-bubba-navy bg-bubba-navy",
|
|
33
|
+
outline: "border-bubba-navy bg-transparent",
|
|
34
|
+
ghost: "border-transparent bg-transparent"
|
|
35
|
+
};
|
|
36
|
+
var textVariants = {
|
|
37
|
+
primary: "text-bubba-white",
|
|
38
|
+
secondary: "text-bubba-white",
|
|
39
|
+
outline: "text-bubba-navy",
|
|
40
|
+
ghost: "text-bubba-navy"
|
|
41
|
+
};
|
|
42
|
+
var containerSizes = {
|
|
43
|
+
sm: "min-h-[44px] px-3 py-2",
|
|
44
|
+
md: "min-h-[48px] px-5 py-3",
|
|
45
|
+
lg: "min-h-[56px] px-6 py-4"
|
|
46
|
+
};
|
|
47
|
+
var textSizes = {
|
|
48
|
+
sm: "text-sm",
|
|
49
|
+
md: "text-base",
|
|
50
|
+
lg: "text-lg"
|
|
51
|
+
};
|
|
52
|
+
var Button = react.forwardRef(
|
|
53
|
+
function Button2({
|
|
54
|
+
children,
|
|
55
|
+
variant = "primary",
|
|
56
|
+
size = "md",
|
|
57
|
+
loading = false,
|
|
58
|
+
loadingLabel,
|
|
59
|
+
disabled = false,
|
|
60
|
+
className,
|
|
61
|
+
textClassName,
|
|
62
|
+
accessibilityState,
|
|
63
|
+
"aria-busy": ariaBusy,
|
|
64
|
+
"aria-disabled": ariaDisabled,
|
|
65
|
+
...props
|
|
66
|
+
}, ref) {
|
|
67
|
+
const explicitlyDisabled = disabled || (ariaDisabled ?? accessibilityState?.disabled ?? false);
|
|
68
|
+
const isDisabled = explicitlyDisabled || loading;
|
|
69
|
+
const isBusy = loading || (ariaBusy ?? accessibilityState?.busy ?? false);
|
|
70
|
+
const spinnerColor = explicitlyDisabled ? tokens.colors.disabledText : variant === "primary" || variant === "secondary" ? tokens.colors.white : tokens.colors.navy;
|
|
71
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
72
|
+
reactNative.Pressable,
|
|
73
|
+
{
|
|
74
|
+
...props,
|
|
75
|
+
ref,
|
|
76
|
+
accessibilityRole: "button",
|
|
77
|
+
accessibilityState: { ...accessibilityState, disabled: isDisabled, busy: isBusy },
|
|
78
|
+
"aria-busy": isBusy,
|
|
79
|
+
"aria-disabled": isDisabled,
|
|
80
|
+
disabled: isDisabled,
|
|
81
|
+
className: tailwindMerge.twMerge(
|
|
82
|
+
"min-w-[44px] flex-row items-center justify-center gap-2 rounded-[4px] border active:opacity-80 web:focus-visible:outline web:focus-visible:outline-2 web:focus-visible:outline-offset-2 web:focus-visible:outline-bubba-navy",
|
|
83
|
+
containerVariants[variant],
|
|
84
|
+
containerSizes[size],
|
|
85
|
+
explicitlyDisabled && "border-bubba-disabled bg-bubba-disabled",
|
|
86
|
+
className
|
|
87
|
+
),
|
|
88
|
+
children: [
|
|
89
|
+
loading ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { size: "small", color: spinnerColor, accessible: false }) : null,
|
|
90
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
91
|
+
reactNative.Text,
|
|
92
|
+
{
|
|
93
|
+
className: tailwindMerge.twMerge(
|
|
94
|
+
"font-bold text-center",
|
|
95
|
+
textVariants[variant],
|
|
96
|
+
textSizes[size],
|
|
97
|
+
explicitlyDisabled && "text-bubba-disabledText",
|
|
98
|
+
textClassName
|
|
99
|
+
),
|
|
100
|
+
children: loading && loadingLabel ? loadingLabel : children
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
exports.Button = Button;
|
|
110
|
+
exports.tokens = tokens;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import * as react_native from 'react-native';
|
|
4
|
+
import { PressableProps } from 'react-native';
|
|
5
|
+
export { BubbaTokens, tokens } from './tokens.cjs';
|
|
6
|
+
|
|
7
|
+
type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost';
|
|
8
|
+
type ButtonSize = 'sm' | 'md' | 'lg';
|
|
9
|
+
interface ButtonProps extends Omit<PressableProps, 'children' | 'className'> {
|
|
10
|
+
/** Text content, or inline React Native Text content. */
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
variant?: ButtonVariant;
|
|
13
|
+
size?: ButtonSize;
|
|
14
|
+
loading?: boolean;
|
|
15
|
+
loadingLabel?: string;
|
|
16
|
+
className?: string;
|
|
17
|
+
textClassName?: string;
|
|
18
|
+
}
|
|
19
|
+
/** One native Pressable and Text implementation on iOS, Android, and web. */
|
|
20
|
+
declare const Button: react.ForwardRefExoticComponent<ButtonProps & react.RefAttributes<react_native.View>>;
|
|
21
|
+
|
|
22
|
+
export { Button, type ButtonProps, type ButtonSize, type ButtonVariant };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import * as react_native from 'react-native';
|
|
4
|
+
import { PressableProps } from 'react-native';
|
|
5
|
+
export { BubbaTokens, tokens } from './tokens.js';
|
|
6
|
+
|
|
7
|
+
type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost';
|
|
8
|
+
type ButtonSize = 'sm' | 'md' | 'lg';
|
|
9
|
+
interface ButtonProps extends Omit<PressableProps, 'children' | 'className'> {
|
|
10
|
+
/** Text content, or inline React Native Text content. */
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
variant?: ButtonVariant;
|
|
13
|
+
size?: ButtonSize;
|
|
14
|
+
loading?: boolean;
|
|
15
|
+
loadingLabel?: string;
|
|
16
|
+
className?: string;
|
|
17
|
+
textClassName?: string;
|
|
18
|
+
}
|
|
19
|
+
/** One native Pressable and Text implementation on iOS, Android, and web. */
|
|
20
|
+
declare const Button: react.ForwardRefExoticComponent<ButtonProps & react.RefAttributes<react_native.View>>;
|
|
21
|
+
|
|
22
|
+
export { Button, type ButtonProps, type ButtonSize, type ButtonVariant };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
import { Pressable, ActivityIndicator, Text } from 'react-native';
|
|
4
|
+
import { twMerge } from 'tailwind-merge';
|
|
5
|
+
import { jsxs, jsx } from 'nativewind/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
// src/tokens.ts
|
|
8
|
+
var tokens = {
|
|
9
|
+
brandVersion: "1.0.0",
|
|
10
|
+
colors: {
|
|
11
|
+
blue: "#007CE8",
|
|
12
|
+
red: "#CC0000",
|
|
13
|
+
redHover: "#A80000",
|
|
14
|
+
navy: "#03256C",
|
|
15
|
+
night: "#001845",
|
|
16
|
+
ink: "#131B23",
|
|
17
|
+
muted: "#526075",
|
|
18
|
+
line: "#D9E8FB",
|
|
19
|
+
white: "#FFFFFF",
|
|
20
|
+
gold: "#FED766",
|
|
21
|
+
disabled: "#F0F2F5",
|
|
22
|
+
disabledText: "#6B727E"
|
|
23
|
+
},
|
|
24
|
+
spacing: { 1: 4, 2: 8, 3: 12, 4: 16, 6: 24, 8: 32, 12: 48, 16: 64 },
|
|
25
|
+
radius: { control: 4, panel: 12 },
|
|
26
|
+
touchTarget: { minimum: 44, default: 48 }
|
|
27
|
+
};
|
|
28
|
+
var containerVariants = {
|
|
29
|
+
primary: "border-bubba-red bg-bubba-red",
|
|
30
|
+
secondary: "border-bubba-navy bg-bubba-navy",
|
|
31
|
+
outline: "border-bubba-navy bg-transparent",
|
|
32
|
+
ghost: "border-transparent bg-transparent"
|
|
33
|
+
};
|
|
34
|
+
var textVariants = {
|
|
35
|
+
primary: "text-bubba-white",
|
|
36
|
+
secondary: "text-bubba-white",
|
|
37
|
+
outline: "text-bubba-navy",
|
|
38
|
+
ghost: "text-bubba-navy"
|
|
39
|
+
};
|
|
40
|
+
var containerSizes = {
|
|
41
|
+
sm: "min-h-[44px] px-3 py-2",
|
|
42
|
+
md: "min-h-[48px] px-5 py-3",
|
|
43
|
+
lg: "min-h-[56px] px-6 py-4"
|
|
44
|
+
};
|
|
45
|
+
var textSizes = {
|
|
46
|
+
sm: "text-sm",
|
|
47
|
+
md: "text-base",
|
|
48
|
+
lg: "text-lg"
|
|
49
|
+
};
|
|
50
|
+
var Button = forwardRef(
|
|
51
|
+
function Button2({
|
|
52
|
+
children,
|
|
53
|
+
variant = "primary",
|
|
54
|
+
size = "md",
|
|
55
|
+
loading = false,
|
|
56
|
+
loadingLabel,
|
|
57
|
+
disabled = false,
|
|
58
|
+
className,
|
|
59
|
+
textClassName,
|
|
60
|
+
accessibilityState,
|
|
61
|
+
"aria-busy": ariaBusy,
|
|
62
|
+
"aria-disabled": ariaDisabled,
|
|
63
|
+
...props
|
|
64
|
+
}, ref) {
|
|
65
|
+
const explicitlyDisabled = disabled || (ariaDisabled ?? accessibilityState?.disabled ?? false);
|
|
66
|
+
const isDisabled = explicitlyDisabled || loading;
|
|
67
|
+
const isBusy = loading || (ariaBusy ?? accessibilityState?.busy ?? false);
|
|
68
|
+
const spinnerColor = explicitlyDisabled ? tokens.colors.disabledText : variant === "primary" || variant === "secondary" ? tokens.colors.white : tokens.colors.navy;
|
|
69
|
+
return /* @__PURE__ */ jsxs(
|
|
70
|
+
Pressable,
|
|
71
|
+
{
|
|
72
|
+
...props,
|
|
73
|
+
ref,
|
|
74
|
+
accessibilityRole: "button",
|
|
75
|
+
accessibilityState: { ...accessibilityState, disabled: isDisabled, busy: isBusy },
|
|
76
|
+
"aria-busy": isBusy,
|
|
77
|
+
"aria-disabled": isDisabled,
|
|
78
|
+
disabled: isDisabled,
|
|
79
|
+
className: twMerge(
|
|
80
|
+
"min-w-[44px] flex-row items-center justify-center gap-2 rounded-[4px] border active:opacity-80 web:focus-visible:outline web:focus-visible:outline-2 web:focus-visible:outline-offset-2 web:focus-visible:outline-bubba-navy",
|
|
81
|
+
containerVariants[variant],
|
|
82
|
+
containerSizes[size],
|
|
83
|
+
explicitlyDisabled && "border-bubba-disabled bg-bubba-disabled",
|
|
84
|
+
className
|
|
85
|
+
),
|
|
86
|
+
children: [
|
|
87
|
+
loading ? /* @__PURE__ */ jsx(ActivityIndicator, { size: "small", color: spinnerColor, accessible: false }) : null,
|
|
88
|
+
/* @__PURE__ */ jsx(
|
|
89
|
+
Text,
|
|
90
|
+
{
|
|
91
|
+
className: twMerge(
|
|
92
|
+
"font-bold text-center",
|
|
93
|
+
textVariants[variant],
|
|
94
|
+
textSizes[size],
|
|
95
|
+
explicitlyDisabled && "text-bubba-disabledText",
|
|
96
|
+
textClassName
|
|
97
|
+
),
|
|
98
|
+
children: loading && loadingLabel ? loadingLabel : children
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
export { Button, tokens };
|
package/dist/tokens.cjs
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/tokens.ts
|
|
4
|
+
var tokens = {
|
|
5
|
+
brandVersion: "1.0.0",
|
|
6
|
+
colors: {
|
|
7
|
+
blue: "#007CE8",
|
|
8
|
+
red: "#CC0000",
|
|
9
|
+
redHover: "#A80000",
|
|
10
|
+
navy: "#03256C",
|
|
11
|
+
night: "#001845",
|
|
12
|
+
ink: "#131B23",
|
|
13
|
+
muted: "#526075",
|
|
14
|
+
line: "#D9E8FB",
|
|
15
|
+
white: "#FFFFFF",
|
|
16
|
+
gold: "#FED766",
|
|
17
|
+
disabled: "#F0F2F5",
|
|
18
|
+
disabledText: "#6B727E"
|
|
19
|
+
},
|
|
20
|
+
spacing: { 1: 4, 2: 8, 3: 12, 4: 16, 6: 24, 8: 32, 12: 48, 16: 64 },
|
|
21
|
+
radius: { control: 4, panel: 12 },
|
|
22
|
+
touchTarget: { minimum: 44, default: 48 }
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
exports.tokens = tokens;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Pinned foundations from Bubba's Brand Studio brand release 1.0.0. */
|
|
2
|
+
declare const tokens: {
|
|
3
|
+
readonly brandVersion: "1.0.0";
|
|
4
|
+
readonly colors: {
|
|
5
|
+
readonly blue: "#007CE8";
|
|
6
|
+
readonly red: "#CC0000";
|
|
7
|
+
readonly redHover: "#A80000";
|
|
8
|
+
readonly navy: "#03256C";
|
|
9
|
+
readonly night: "#001845";
|
|
10
|
+
readonly ink: "#131B23";
|
|
11
|
+
readonly muted: "#526075";
|
|
12
|
+
readonly line: "#D9E8FB";
|
|
13
|
+
readonly white: "#FFFFFF";
|
|
14
|
+
readonly gold: "#FED766";
|
|
15
|
+
readonly disabled: "#F0F2F5";
|
|
16
|
+
readonly disabledText: "#6B727E";
|
|
17
|
+
};
|
|
18
|
+
readonly spacing: {
|
|
19
|
+
readonly 1: 4;
|
|
20
|
+
readonly 2: 8;
|
|
21
|
+
readonly 3: 12;
|
|
22
|
+
readonly 4: 16;
|
|
23
|
+
readonly 6: 24;
|
|
24
|
+
readonly 8: 32;
|
|
25
|
+
readonly 12: 48;
|
|
26
|
+
readonly 16: 64;
|
|
27
|
+
};
|
|
28
|
+
readonly radius: {
|
|
29
|
+
readonly control: 4;
|
|
30
|
+
readonly panel: 12;
|
|
31
|
+
};
|
|
32
|
+
readonly touchTarget: {
|
|
33
|
+
readonly minimum: 44;
|
|
34
|
+
readonly default: 48;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
type BubbaTokens = typeof tokens;
|
|
38
|
+
|
|
39
|
+
export { type BubbaTokens, tokens };
|
package/dist/tokens.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Pinned foundations from Bubba's Brand Studio brand release 1.0.0. */
|
|
2
|
+
declare const tokens: {
|
|
3
|
+
readonly brandVersion: "1.0.0";
|
|
4
|
+
readonly colors: {
|
|
5
|
+
readonly blue: "#007CE8";
|
|
6
|
+
readonly red: "#CC0000";
|
|
7
|
+
readonly redHover: "#A80000";
|
|
8
|
+
readonly navy: "#03256C";
|
|
9
|
+
readonly night: "#001845";
|
|
10
|
+
readonly ink: "#131B23";
|
|
11
|
+
readonly muted: "#526075";
|
|
12
|
+
readonly line: "#D9E8FB";
|
|
13
|
+
readonly white: "#FFFFFF";
|
|
14
|
+
readonly gold: "#FED766";
|
|
15
|
+
readonly disabled: "#F0F2F5";
|
|
16
|
+
readonly disabledText: "#6B727E";
|
|
17
|
+
};
|
|
18
|
+
readonly spacing: {
|
|
19
|
+
readonly 1: 4;
|
|
20
|
+
readonly 2: 8;
|
|
21
|
+
readonly 3: 12;
|
|
22
|
+
readonly 4: 16;
|
|
23
|
+
readonly 6: 24;
|
|
24
|
+
readonly 8: 32;
|
|
25
|
+
readonly 12: 48;
|
|
26
|
+
readonly 16: 64;
|
|
27
|
+
};
|
|
28
|
+
readonly radius: {
|
|
29
|
+
readonly control: 4;
|
|
30
|
+
readonly panel: 12;
|
|
31
|
+
};
|
|
32
|
+
readonly touchTarget: {
|
|
33
|
+
readonly minimum: 44;
|
|
34
|
+
readonly default: 48;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
type BubbaTokens = typeof tokens;
|
|
38
|
+
|
|
39
|
+
export { type BubbaTokens, tokens };
|
package/dist/tokens.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/tokens.ts
|
|
2
|
+
var tokens = {
|
|
3
|
+
brandVersion: "1.0.0",
|
|
4
|
+
colors: {
|
|
5
|
+
blue: "#007CE8",
|
|
6
|
+
red: "#CC0000",
|
|
7
|
+
redHover: "#A80000",
|
|
8
|
+
navy: "#03256C",
|
|
9
|
+
night: "#001845",
|
|
10
|
+
ink: "#131B23",
|
|
11
|
+
muted: "#526075",
|
|
12
|
+
line: "#D9E8FB",
|
|
13
|
+
white: "#FFFFFF",
|
|
14
|
+
gold: "#FED766",
|
|
15
|
+
disabled: "#F0F2F5",
|
|
16
|
+
disabledText: "#6B727E"
|
|
17
|
+
},
|
|
18
|
+
spacing: { 1: 4, 2: 8, 3: 12, 4: 16, 6: 24, 8: 32, 12: 48, 16: 64 },
|
|
19
|
+
radius: { control: 4, panel: 12 },
|
|
20
|
+
touchTarget: { minimum: 44, default: 48 }
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export { tokens };
|
package/package.json
CHANGED
|
@@ -1,6 +1,87 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bubbasfw/ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Bubba\u2019s universal React Native and web design system, styled with NativeWind.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"react-native": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"react-native": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"import": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"require": {
|
|
22
|
+
"types": "./dist/index.d.cts",
|
|
23
|
+
"default": "./dist/index.cjs"
|
|
24
|
+
},
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./tokens": {
|
|
28
|
+
"import": {
|
|
29
|
+
"types": "./dist/tokens.d.ts",
|
|
30
|
+
"default": "./dist/tokens.js"
|
|
31
|
+
},
|
|
32
|
+
"require": {
|
|
33
|
+
"types": "./dist/tokens.d.cts",
|
|
34
|
+
"default": "./dist/tokens.cjs"
|
|
35
|
+
},
|
|
36
|
+
"default": "./dist/tokens.js"
|
|
37
|
+
},
|
|
38
|
+
"./tailwind-preset": "./tailwind-preset.cjs",
|
|
39
|
+
"./package.json": "./package.json"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"src",
|
|
44
|
+
"tailwind-preset.cjs",
|
|
45
|
+
"README.md",
|
|
46
|
+
"LICENSE"
|
|
47
|
+
],
|
|
48
|
+
"sideEffects": false,
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"repository": {
|
|
53
|
+
"type": "git",
|
|
54
|
+
"url": "https://github.com/handh-dev/bubbas-ui.git",
|
|
55
|
+
"directory": "packages/ui"
|
|
56
|
+
},
|
|
57
|
+
"homepage": "https://github.com/handh-dev/bubbas-ui#readme",
|
|
58
|
+
"keywords": [
|
|
59
|
+
"react-native",
|
|
60
|
+
"react-native-web",
|
|
61
|
+
"expo",
|
|
62
|
+
"nextjs",
|
|
63
|
+
"nativewind",
|
|
64
|
+
"design-system",
|
|
65
|
+
"bubbas"
|
|
66
|
+
],
|
|
67
|
+
"scripts": {
|
|
68
|
+
"build": "tsup",
|
|
69
|
+
"typecheck": "tsc --noEmit",
|
|
70
|
+
"prepack": "pnpm build"
|
|
71
|
+
},
|
|
72
|
+
"dependencies": {
|
|
73
|
+
"tailwind-merge": "^2.6.1"
|
|
74
|
+
},
|
|
75
|
+
"peerDependencies": {
|
|
76
|
+
"react": "^19.2.3",
|
|
77
|
+
"react-native": "^0.86.3",
|
|
78
|
+
"react-native-web": "^0.21.4",
|
|
79
|
+
"nativewind": "^4.2.7",
|
|
80
|
+
"tailwindcss": "^3.4.17"
|
|
81
|
+
},
|
|
82
|
+
"peerDependenciesMeta": {
|
|
83
|
+
"react-native-web": {
|
|
84
|
+
"optional": true
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { forwardRef, type ElementRef, type ReactNode } from 'react';
|
|
4
|
+
import { ActivityIndicator, Pressable, Text, type PressableProps } from 'react-native';
|
|
5
|
+
import { twMerge } from 'tailwind-merge';
|
|
6
|
+
import { tokens } from '../tokens';
|
|
7
|
+
|
|
8
|
+
export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost';
|
|
9
|
+
export type ButtonSize = 'sm' | 'md' | 'lg';
|
|
10
|
+
|
|
11
|
+
export interface ButtonProps extends Omit<PressableProps, 'children' | 'className'> {
|
|
12
|
+
/** Text content, or inline React Native Text content. */
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
variant?: ButtonVariant;
|
|
15
|
+
size?: ButtonSize;
|
|
16
|
+
loading?: boolean;
|
|
17
|
+
loadingLabel?: string;
|
|
18
|
+
className?: string;
|
|
19
|
+
textClassName?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const containerVariants: Record<ButtonVariant, string> = {
|
|
23
|
+
primary: 'border-bubba-red bg-bubba-red',
|
|
24
|
+
secondary: 'border-bubba-navy bg-bubba-navy',
|
|
25
|
+
outline: 'border-bubba-navy bg-transparent',
|
|
26
|
+
ghost: 'border-transparent bg-transparent',
|
|
27
|
+
};
|
|
28
|
+
const textVariants: Record<ButtonVariant, string> = {
|
|
29
|
+
primary: 'text-bubba-white',
|
|
30
|
+
secondary: 'text-bubba-white',
|
|
31
|
+
outline: 'text-bubba-navy',
|
|
32
|
+
ghost: 'text-bubba-navy',
|
|
33
|
+
};
|
|
34
|
+
const containerSizes: Record<ButtonSize, string> = {
|
|
35
|
+
sm: 'min-h-[44px] px-3 py-2',
|
|
36
|
+
md: 'min-h-[48px] px-5 py-3',
|
|
37
|
+
lg: 'min-h-[56px] px-6 py-4',
|
|
38
|
+
};
|
|
39
|
+
const textSizes: Record<ButtonSize, string> = {
|
|
40
|
+
sm: 'text-sm',
|
|
41
|
+
md: 'text-base',
|
|
42
|
+
lg: 'text-lg',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** One native Pressable and Text implementation on iOS, Android, and web. */
|
|
46
|
+
export const Button = forwardRef<ElementRef<typeof Pressable>, ButtonProps>(
|
|
47
|
+
function Button(
|
|
48
|
+
{
|
|
49
|
+
children,
|
|
50
|
+
variant = 'primary',
|
|
51
|
+
size = 'md',
|
|
52
|
+
loading = false,
|
|
53
|
+
loadingLabel,
|
|
54
|
+
disabled = false,
|
|
55
|
+
className,
|
|
56
|
+
textClassName,
|
|
57
|
+
accessibilityState,
|
|
58
|
+
'aria-busy': ariaBusy,
|
|
59
|
+
'aria-disabled': ariaDisabled,
|
|
60
|
+
...props
|
|
61
|
+
},
|
|
62
|
+
ref,
|
|
63
|
+
) {
|
|
64
|
+
const explicitlyDisabled = disabled || (ariaDisabled ?? accessibilityState?.disabled ?? false);
|
|
65
|
+
const isDisabled = explicitlyDisabled || loading;
|
|
66
|
+
const isBusy = loading || (ariaBusy ?? accessibilityState?.busy ?? false);
|
|
67
|
+
const spinnerColor = explicitlyDisabled
|
|
68
|
+
? tokens.colors.disabledText
|
|
69
|
+
: variant === 'primary' || variant === 'secondary'
|
|
70
|
+
? tokens.colors.white
|
|
71
|
+
: tokens.colors.navy;
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<Pressable
|
|
75
|
+
{...props}
|
|
76
|
+
ref={ref}
|
|
77
|
+
accessibilityRole="button"
|
|
78
|
+
accessibilityState={{ ...accessibilityState, disabled: isDisabled, busy: isBusy }}
|
|
79
|
+
aria-busy={isBusy}
|
|
80
|
+
aria-disabled={isDisabled}
|
|
81
|
+
disabled={isDisabled}
|
|
82
|
+
className={twMerge(
|
|
83
|
+
'min-w-[44px] flex-row items-center justify-center gap-2 rounded-[4px] border active:opacity-80 web:focus-visible:outline web:focus-visible:outline-2 web:focus-visible:outline-offset-2 web:focus-visible:outline-bubba-navy',
|
|
84
|
+
containerVariants[variant],
|
|
85
|
+
containerSizes[size],
|
|
86
|
+
explicitlyDisabled && 'border-bubba-disabled bg-bubba-disabled',
|
|
87
|
+
className,
|
|
88
|
+
)}
|
|
89
|
+
>
|
|
90
|
+
{loading ? <ActivityIndicator size="small" color={spinnerColor} accessible={false} /> : null}
|
|
91
|
+
<Text
|
|
92
|
+
className={twMerge(
|
|
93
|
+
'font-bold text-center',
|
|
94
|
+
textVariants[variant],
|
|
95
|
+
textSizes[size],
|
|
96
|
+
explicitlyDisabled && 'text-bubba-disabledText',
|
|
97
|
+
textClassName,
|
|
98
|
+
)}
|
|
99
|
+
>
|
|
100
|
+
{loading && loadingLabel ? loadingLabel : children}
|
|
101
|
+
</Text>
|
|
102
|
+
</Pressable>
|
|
103
|
+
);
|
|
104
|
+
},
|
|
105
|
+
);
|
package/src/index.ts
ADDED
package/src/tokens.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Pinned foundations from Bubba's Brand Studio brand release 1.0.0. */
|
|
2
|
+
export const tokens = {
|
|
3
|
+
brandVersion: '1.0.0',
|
|
4
|
+
colors: {
|
|
5
|
+
blue: '#007CE8',
|
|
6
|
+
red: '#CC0000',
|
|
7
|
+
redHover: '#A80000',
|
|
8
|
+
navy: '#03256C',
|
|
9
|
+
night: '#001845',
|
|
10
|
+
ink: '#131B23',
|
|
11
|
+
muted: '#526075',
|
|
12
|
+
line: '#D9E8FB',
|
|
13
|
+
white: '#FFFFFF',
|
|
14
|
+
gold: '#FED766',
|
|
15
|
+
disabled: '#F0F2F5',
|
|
16
|
+
disabledText: '#6B727E',
|
|
17
|
+
},
|
|
18
|
+
spacing: { 1: 4, 2: 8, 3: 12, 4: 16, 6: 24, 8: 32, 12: 48, 16: 64 },
|
|
19
|
+
radius: { control: 4, panel: 12 },
|
|
20
|
+
touchTarget: { minimum: 44, default: 48 },
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
export type BubbaTokens = typeof tokens;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const { tokens } = require('./dist/tokens.cjs');
|
|
2
|
+
|
|
3
|
+
/** @type {import('tailwindcss').Config} */
|
|
4
|
+
module.exports = {
|
|
5
|
+
theme: {
|
|
6
|
+
extend: {
|
|
7
|
+
colors: { bubba: tokens.colors },
|
|
8
|
+
borderRadius: {
|
|
9
|
+
'bubba-control': `${tokens.radius.control}px`,
|
|
10
|
+
'bubba-panel': `${tokens.radius.panel}px`,
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
};
|