feat: set up automated npm publishing for @soroban/abacus-react package
- Add semantic-release configuration for abacus-react package with scope-based versioning - Create GitHub Actions workflow for automated publishing to npm - Configure package-specific semantic versioning with conventional commits - Add release scripts and update README with publishing documentation - Update root release config to exclude abacus-react scope from monorepo releases - Package releases are triggered by commits with scope 'abacus-react' BREAKING CHANGE: abacus-react package now has independent versioning from monorepo
This commit is contained in:
parent
9d7cfefb69
commit
dd80d29c97
|
|
@ -148,7 +148,8 @@
|
|||
"Bash(awk:*)",
|
||||
"Bash(gh release list:*)",
|
||||
"Bash(gh release view:*)",
|
||||
"Bash(git pull:*)"
|
||||
"Bash(git pull:*)",
|
||||
"WebFetch(domain:antialias.github.io)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
name: Publish @soroban/abacus-react
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'packages/abacus-react/**'
|
||||
- '.github/workflows/publish-abacus-react.yml'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish abacus-react package
|
||||
runs-on: ubuntu-latest
|
||||
if: "!contains(github.event.head_commit.message, '[skip ci]')"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8.0.0
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build abacus-react package
|
||||
run: pnpm --filter @soroban/abacus-react build
|
||||
|
||||
- name: Run tests
|
||||
run: pnpm --filter @soroban/abacus-react test:run
|
||||
|
||||
- name: Semantic Release
|
||||
working-directory: packages/abacus-react
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npx semantic-release
|
||||
|
|
@ -1,8 +1,37 @@
|
|||
{
|
||||
"branches": ["main"],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
[
|
||||
"@semantic-release/commit-analyzer",
|
||||
{
|
||||
"preset": "conventionalcommits",
|
||||
"releaseRules": [
|
||||
{ "type": "feat", "scope": "!abacus-react", "release": "minor" },
|
||||
{ "type": "fix", "scope": "!abacus-react", "release": "patch" },
|
||||
{ "type": "perf", "scope": "!abacus-react", "release": "patch" },
|
||||
{ "type": "refactor", "scope": "!abacus-react", "release": "patch" },
|
||||
{ "breaking": true, "scope": "!abacus-react", "release": "major" },
|
||||
{ "scope": "abacus-react", "release": false }
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/release-notes-generator",
|
||||
{
|
||||
"preset": "conventionalcommits",
|
||||
"presetConfig": {
|
||||
"types": [
|
||||
{ "type": "feat", "section": "Features" },
|
||||
{ "type": "fix", "section": "Bug Fixes" },
|
||||
{ "type": "perf", "section": "Performance Improvements" },
|
||||
{ "type": "refactor", "section": "Code Refactoring" },
|
||||
{ "type": "docs", "section": "Documentation" },
|
||||
{ "type": "style", "section": "Styles" },
|
||||
{ "type": "test", "section": "Tests" }
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/changelog",
|
||||
{
|
||||
|
|
|
|||
|
|
@ -36,11 +36,11 @@ export function MemoryGrid() {
|
|||
|
||||
return (
|
||||
<div className={css({
|
||||
padding: '20px',
|
||||
padding: { base: '12px', sm: '16px', md: '20px' },
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '20px'
|
||||
gap: { base: '12px', sm: '16px', md: '20px' }
|
||||
})}>
|
||||
|
||||
{/* Game Info Header */}
|
||||
|
|
@ -50,37 +50,43 @@ export function MemoryGrid() {
|
|||
alignItems: 'center',
|
||||
width: '100%',
|
||||
maxWidth: '800px',
|
||||
padding: '16px 24px',
|
||||
padding: { base: '12px 16px', sm: '14px 20px', md: '16px 24px' },
|
||||
background: 'linear-gradient(135deg, rgba(255,255,255,0.9), rgba(248,250,252,0.9))',
|
||||
borderRadius: '16px',
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
|
||||
border: '1px solid rgba(255,255,255,0.8)'
|
||||
})}>
|
||||
|
||||
<div className={css({ display: 'flex', alignItems: 'center', gap: '20px' })}>
|
||||
<div className={css({
|
||||
display: 'grid',
|
||||
gridTemplateColumns: { base: 'repeat(3, 1fr)', sm: 'repeat(3, auto)' },
|
||||
gap: { base: '8px', sm: '12px', md: '20px' },
|
||||
justifyContent: { base: 'stretch', sm: 'center' },
|
||||
width: { base: '100%', sm: 'auto' }
|
||||
})}>
|
||||
<div className={css({ textAlign: 'center' })}>
|
||||
<div className={css({ fontSize: '24px', fontWeight: 'bold', color: 'blue.600' })}>
|
||||
<div className={css({ fontSize: { base: '18px', sm: '20px', md: '24px' }, fontWeight: 'bold', color: 'blue.600' })}>
|
||||
{state.matchedPairs}
|
||||
</div>
|
||||
<div className={css({ fontSize: '12px', color: 'gray.600' })}>
|
||||
<div className={css({ fontSize: { base: '10px', sm: '11px', md: '12px' }, color: 'gray.600' })}>
|
||||
Matched
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={css({ textAlign: 'center' })}>
|
||||
<div className={css({ fontSize: '24px', fontWeight: 'bold', color: 'purple.600' })}>
|
||||
<div className={css({ fontSize: { base: '18px', sm: '20px', md: '24px' }, fontWeight: 'bold', color: 'purple.600' })}>
|
||||
{state.moves}
|
||||
</div>
|
||||
<div className={css({ fontSize: '12px', color: 'gray.600' })}>
|
||||
<div className={css({ fontSize: { base: '10px', sm: '11px', md: '12px' }, color: 'gray.600' })}>
|
||||
Moves
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={css({ textAlign: 'center' })}>
|
||||
<div className={css({ fontSize: '24px', fontWeight: 'bold', color: 'green.600' })}>
|
||||
<div className={css({ fontSize: { base: '18px', sm: '20px', md: '24px' }, fontWeight: 'bold', color: 'green.600' })}>
|
||||
{state.totalPairs}
|
||||
</div>
|
||||
<div className={css({ fontSize: '12px', color: 'gray.600' })}>
|
||||
<div className={css({ fontSize: { base: '10px', sm: '11px', md: '12px' }, color: 'gray.600' })}>
|
||||
Total Pairs
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -26,32 +26,36 @@ export function MemoryPairsGame() {
|
|||
ref={gameRef}
|
||||
className={css({
|
||||
minHeight: '100vh',
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
padding: '20px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
position: 'relative'
|
||||
})}>
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
padding: { base: '12px', sm: '16px', md: '20px' },
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
position: 'relative'
|
||||
})}>
|
||||
{/* Note: Fullscreen restore prompt removed - client-side navigation preserves fullscreen */}
|
||||
|
||||
<header className={css({
|
||||
textAlign: 'center',
|
||||
marginBottom: '30px'
|
||||
marginBottom: { base: '16px', sm: '20px', md: '30px' },
|
||||
px: { base: '4', md: '0' }
|
||||
})}>
|
||||
<h1 className={css({
|
||||
fontSize: '48px',
|
||||
fontSize: { base: '24px', sm: '32px', md: '40px', lg: '48px' },
|
||||
fontWeight: 'bold',
|
||||
color: 'white',
|
||||
textShadow: '2px 2px 4px rgba(0,0,0,0.3)',
|
||||
marginBottom: '10px'
|
||||
marginBottom: { base: '6px', md: '10px' },
|
||||
lineHeight: { base: '1.2', md: '1.1' }
|
||||
})}>
|
||||
Memory Pairs Challenge
|
||||
</h1>
|
||||
<p className={css({
|
||||
fontSize: '18px',
|
||||
fontSize: { base: '14px', sm: '16px', md: '18px' },
|
||||
color: 'rgba(255,255,255,0.9)',
|
||||
maxWidth: '600px'
|
||||
maxWidth: '600px',
|
||||
lineHeight: { base: '1.4', md: '1.3' },
|
||||
display: { base: 'none', sm: 'block' }
|
||||
})}>
|
||||
Match pairs of abacus representations with their numerical values, or find complement pairs that add up to 5 or 10!
|
||||
</p>
|
||||
|
|
@ -61,10 +65,13 @@ export function MemoryPairsGame() {
|
|||
width: '100%',
|
||||
maxWidth: '1200px',
|
||||
background: 'rgba(255,255,255,0.95)',
|
||||
borderRadius: '20px',
|
||||
padding: '40px',
|
||||
borderRadius: { base: '12px', md: '20px' },
|
||||
padding: { base: '16px', sm: '24px', md: '32px', lg: '40px' },
|
||||
boxShadow: '0 10px 30px rgba(0,0,0,0.2)',
|
||||
minHeight: '500px'
|
||||
minHeight: { base: '60vh', md: '500px' },
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
})}>
|
||||
{state.gamePhase === 'setup' && <SetupPhase />}
|
||||
{state.gamePhase === 'playing' && <GamePhase />}
|
||||
|
|
|
|||
|
|
@ -47,13 +47,13 @@ export function SetupPhase() {
|
|||
const getButtonStyles = (isSelected: boolean, variant: 'primary' | 'secondary' | 'difficulty' = 'primary') => {
|
||||
const baseStyles = {
|
||||
border: 'none',
|
||||
borderRadius: '16px',
|
||||
padding: '16px 24px',
|
||||
fontSize: '16px',
|
||||
borderRadius: { base: '12px', md: '16px' },
|
||||
padding: { base: '12px 16px', sm: '14px 20px', md: '16px 24px' },
|
||||
fontSize: { base: '14px', sm: '15px', md: '16px' },
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
minWidth: '160px',
|
||||
minWidth: { base: '120px', sm: '140px', md: '160px' },
|
||||
textAlign: 'center' as const,
|
||||
position: 'relative' as const,
|
||||
overflow: 'hidden' as const,
|
||||
|
|
@ -130,13 +130,16 @@ export function SetupPhase() {
|
|||
return (
|
||||
<div className={css({
|
||||
textAlign: 'center',
|
||||
padding: '40px 20px',
|
||||
padding: { base: '20px 16px', sm: '24px 20px', md: '40px 20px' },
|
||||
maxWidth: '800px',
|
||||
margin: '0 auto'
|
||||
margin: '0 auto',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
})}>
|
||||
<h2 className={css({
|
||||
fontSize: '36px',
|
||||
marginBottom: '16px',
|
||||
fontSize: { base: '20px', sm: '24px', md: '32px', lg: '36px' },
|
||||
marginBottom: { base: '12px', md: '16px' },
|
||||
color: 'gray.800',
|
||||
fontWeight: 'bold'
|
||||
})}>
|
||||
|
|
@ -144,46 +147,48 @@ export function SetupPhase() {
|
|||
</h2>
|
||||
|
||||
<p className={css({
|
||||
fontSize: '18px',
|
||||
fontSize: { base: '14px', sm: '16px', md: '18px' },
|
||||
color: 'gray.600',
|
||||
marginBottom: '40px',
|
||||
lineHeight: '1.6'
|
||||
marginBottom: { base: '24px', sm: '32px', md: '40px' },
|
||||
lineHeight: '1.6',
|
||||
display: { base: 'none', sm: 'block' }
|
||||
})}>
|
||||
Configure your memory challenge. Choose your preferred mode, game type, and difficulty level.
|
||||
</p>
|
||||
|
||||
<div className={css({
|
||||
display: 'grid',
|
||||
gap: '32px',
|
||||
margin: '0 auto'
|
||||
gap: { base: '20px', sm: '24px', md: '32px' },
|
||||
margin: '0 auto',
|
||||
flex: 1
|
||||
})}>
|
||||
|
||||
{/* Current Player Setup */}
|
||||
<div className={css({
|
||||
background: 'linear-gradient(135deg, #f3f4f6, #e5e7eb)',
|
||||
rounded: '2xl',
|
||||
p: '6',
|
||||
rounded: { base: 'xl', md: '2xl' },
|
||||
p: { base: '4', md: '6' },
|
||||
border: '2px solid',
|
||||
borderColor: 'gray.300'
|
||||
})}>
|
||||
<h3 className={css({
|
||||
fontSize: '20px',
|
||||
fontSize: { base: '16px', sm: '18px', md: '20px' },
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.700',
|
||||
mb: '3',
|
||||
mb: { base: '2', md: '3' },
|
||||
textAlign: 'center'
|
||||
})}>
|
||||
🎮 Current Setup
|
||||
</h3>
|
||||
<div className={css({
|
||||
fontSize: '16px',
|
||||
fontSize: { base: '14px', md: '16px' },
|
||||
color: 'gray.700',
|
||||
textAlign: 'center'
|
||||
})}>
|
||||
<p>
|
||||
<strong>{activePlayerCount}</strong> player{activePlayerCount !== 1 ? 's' : ''} selected
|
||||
</p>
|
||||
<p className={css({ fontSize: '14px', color: 'gray.600', mt: '1' })}>
|
||||
<p className={css({ fontSize: { base: '12px', md: '14px' }, color: 'gray.600', mt: '1' })}>
|
||||
{activePlayerCount === 1
|
||||
? 'Solo challenge mode - focus & memory'
|
||||
: `${activePlayerCount}-player battle mode - compete for the most pairs`
|
||||
|
|
@ -211,31 +216,34 @@ export function SetupPhase() {
|
|||
<div>
|
||||
<label className={css({
|
||||
display: 'block',
|
||||
fontSize: '20px',
|
||||
fontSize: { base: '16px', sm: '18px', md: '20px' },
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '16px',
|
||||
marginBottom: { base: '12px', md: '16px' },
|
||||
color: 'gray.700'
|
||||
})}>
|
||||
Game Type
|
||||
</label>
|
||||
<div className={css({
|
||||
display: 'flex',
|
||||
gap: '12px',
|
||||
justifyContent: 'center',
|
||||
flexWrap: 'wrap'
|
||||
display: 'grid',
|
||||
gridTemplateColumns: {
|
||||
base: '1fr',
|
||||
sm: 'repeat(2, 1fr)'
|
||||
},
|
||||
gap: { base: '8px', sm: '10px', md: '12px' },
|
||||
justifyItems: 'stretch'
|
||||
})}>
|
||||
<button
|
||||
className={getButtonStyles(state.gameType === 'abacus-numeral', 'secondary')}
|
||||
onClick={() => setGameType('abacus-numeral')}
|
||||
>
|
||||
<div className={css({ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '6px' })}>
|
||||
<div className={css({ fontSize: '28px', display: 'flex', alignItems: 'center', gap: '8px' })}>
|
||||
<div className={css({ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: { base: '4px', md: '6px' } })}>
|
||||
<div className={css({ fontSize: { base: '20px', sm: '24px', md: '28px' }, display: 'flex', alignItems: 'center', gap: { base: '4px', md: '8px' } })}>
|
||||
<span>🧮</span>
|
||||
<span className={css({ fontSize: '20px' })}>↔️</span>
|
||||
<span className={css({ fontSize: { base: '16px', md: '20px' } })}>↔️</span>
|
||||
<span>🔢</span>
|
||||
</div>
|
||||
<div className={css({ fontWeight: 'bold' })}>Abacus-Numeral</div>
|
||||
<div className={css({ fontSize: '12px', opacity: 0.8, textAlign: 'center' })}>
|
||||
<div className={css({ fontWeight: 'bold', fontSize: { base: '12px', sm: '13px', md: '14px' } })}>Abacus-Numeral</div>
|
||||
<div className={css({ fontSize: { base: '10px', sm: '11px', md: '12px' }, opacity: 0.8, textAlign: 'center', display: { base: 'none', sm: 'block' } })}>
|
||||
Match visual patterns<br/>with numbers
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -244,23 +252,25 @@ export function SetupPhase() {
|
|||
className={getButtonStyles(state.gameType === 'complement-pairs', 'secondary')}
|
||||
onClick={() => setGameType('complement-pairs')}
|
||||
>
|
||||
<div className={css({ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '6px' })}>
|
||||
<div className={css({ fontSize: '28px', display: 'flex', alignItems: 'center', gap: '8px' })}>
|
||||
<div className={css({ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: { base: '4px', md: '6px' } })}>
|
||||
<div className={css({ fontSize: { base: '20px', sm: '24px', md: '28px' }, display: 'flex', alignItems: 'center', gap: { base: '4px', md: '8px' } })}>
|
||||
<span>🤝</span>
|
||||
<span className={css({ fontSize: '20px' })}>➕</span>
|
||||
<span className={css({ fontSize: { base: '16px', md: '20px' } })}>➕</span>
|
||||
<span>🔟</span>
|
||||
</div>
|
||||
<div className={css({ fontWeight: 'bold' })}>Complement Pairs</div>
|
||||
<div className={css({ fontSize: '12px', opacity: 0.8, textAlign: 'center' })}>
|
||||
<div className={css({ fontWeight: 'bold', fontSize: { base: '12px', sm: '13px', md: '14px' } })}>Complement Pairs</div>
|
||||
<div className={css({ fontSize: { base: '10px', sm: '11px', md: '12px' }, opacity: 0.8, textAlign: 'center', display: { base: 'none', sm: 'block' } })}>
|
||||
Find number friends<br/>that add to 5 or 10
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<p className={css({
|
||||
fontSize: '14px',
|
||||
fontSize: { base: '12px', md: '14px' },
|
||||
color: 'gray.500',
|
||||
marginTop: '8px'
|
||||
marginTop: { base: '6px', md: '8px' },
|
||||
textAlign: 'center',
|
||||
display: { base: 'none', sm: 'block' }
|
||||
})}>
|
||||
{state.gameType === 'abacus-numeral'
|
||||
? 'Match abacus representations with their numerical values'
|
||||
|
|
@ -273,18 +283,21 @@ export function SetupPhase() {
|
|||
<div>
|
||||
<label className={css({
|
||||
display: 'block',
|
||||
fontSize: '20px',
|
||||
fontSize: { base: '16px', sm: '18px', md: '20px' },
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '16px',
|
||||
marginBottom: { base: '12px', md: '16px' },
|
||||
color: 'gray.700'
|
||||
})}>
|
||||
Difficulty ({state.difficulty} pairs)
|
||||
</label>
|
||||
<div className={css({
|
||||
display: 'flex',
|
||||
gap: '12px',
|
||||
justifyContent: 'center',
|
||||
flexWrap: 'wrap'
|
||||
display: 'grid',
|
||||
gridTemplateColumns: {
|
||||
base: 'repeat(2, 1fr)',
|
||||
sm: 'repeat(4, 1fr)'
|
||||
},
|
||||
gap: { base: '8px', sm: '10px', md: '12px' },
|
||||
justifyItems: 'stretch'
|
||||
})}>
|
||||
{([6, 8, 12, 15] as const).map(difficulty => {
|
||||
const difficultyInfo = {
|
||||
|
|
@ -377,15 +390,15 @@ export function SetupPhase() {
|
|||
)}
|
||||
|
||||
{/* Start Game Button */}
|
||||
<div className={css({ marginTop: '20px' })}>
|
||||
<div className={css({ marginTop: { base: '16px', md: '20px' } })}>
|
||||
<button
|
||||
className={css({
|
||||
background: 'linear-gradient(135deg, #ff6b6b 0%, #ee5a24 50%, #ff9ff3 100%)',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '60px',
|
||||
padding: '20px 60px',
|
||||
fontSize: '28px',
|
||||
borderRadius: { base: '20px', sm: '30px', md: '60px' },
|
||||
padding: { base: '16px 32px', sm: '18px 40px', md: '20px 60px' },
|
||||
fontSize: { base: '18px', sm: '22px', md: '28px' },
|
||||
fontWeight: 'black',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
|
|
@ -393,6 +406,7 @@ export function SetupPhase() {
|
|||
textShadow: '0 2px 4px rgba(0,0,0,0.3)',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
width: { base: '100%', sm: 'auto' },
|
||||
_before: {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
|
|
@ -404,7 +418,7 @@ export function SetupPhase() {
|
|||
transition: 'left 0.6s ease',
|
||||
},
|
||||
_hover: {
|
||||
transform: 'translateY(-5px) scale(1.05)',
|
||||
transform: { base: 'translateY(-2px)', md: 'translateY(-5px) scale(1.05)' },
|
||||
boxShadow: '0 15px 40px rgba(255, 107, 107, 0.6), inset 0 2px 0 rgba(255,255,255,0.3)',
|
||||
background: 'linear-gradient(135deg, #ff5252 0%, #dd2c00 50%, #e91e63 100%)',
|
||||
_before: {
|
||||
|
|
@ -412,7 +426,7 @@ export function SetupPhase() {
|
|||
}
|
||||
},
|
||||
_active: {
|
||||
transform: 'translateY(-2px) scale(1.02)',
|
||||
transform: 'translateY(-1px) scale(1.01)',
|
||||
}
|
||||
})}
|
||||
onClick={handleStartGame}
|
||||
|
|
@ -420,16 +434,16 @@ export function SetupPhase() {
|
|||
<div className={css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '12px',
|
||||
gap: { base: '8px', md: '12px' },
|
||||
justifyContent: 'center'
|
||||
})}>
|
||||
<span className={css({
|
||||
fontSize: '32px',
|
||||
fontSize: { base: '20px', sm: '24px', md: '32px' },
|
||||
animation: 'bounce 2s infinite'
|
||||
})}>🚀</span>
|
||||
<span>START GAME</span>
|
||||
<span className={css({
|
||||
fontSize: '32px',
|
||||
fontSize: { base: '20px', sm: '24px', md: '32px' },
|
||||
animation: 'bounce 2s infinite',
|
||||
animationDelay: '0.5s'
|
||||
})}>🎮</span>
|
||||
|
|
|
|||
|
|
@ -644,8 +644,8 @@ export function EnhancedChampionArena({ onGameModeChange, onConfigurePlayer, cla
|
|||
<SortableContext items={availablePlayers.map(p => p.id)} strategy={rectSortingStrategy}>
|
||||
<DroppableZone
|
||||
id="roster"
|
||||
title="Available"
|
||||
subtitle="Tap to add"
|
||||
title="🎯 Available Champions"
|
||||
subtitle="Drag champions here to remove from arena"
|
||||
isEmpty={availablePlayers.length === 0}
|
||||
>
|
||||
{availablePlayers.map(player => (
|
||||
|
|
@ -672,8 +672,8 @@ export function EnhancedChampionArena({ onGameModeChange, onConfigurePlayer, cla
|
|||
<SortableContext items={arenaPlayers.map(p => p.id)} strategy={rectSortingStrategy}>
|
||||
<DroppableZone
|
||||
id="arena"
|
||||
title="Arena"
|
||||
subtitle="Drop here"
|
||||
title="🏟️ Arena"
|
||||
subtitle="1 champion = Solo • 2 = Battle • 3+ = Tournament"
|
||||
isEmpty={arenaPlayers.length === 0}
|
||||
>
|
||||
{arenaPlayers.map(player => (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"branches": ["main"],
|
||||
"repositoryUrl": "https://github.com/antialias/soroban-abacus-flashcards",
|
||||
"tagFormat": "abacus-react-v${version}",
|
||||
"plugins": [
|
||||
[
|
||||
"@semantic-release/commit-analyzer",
|
||||
{
|
||||
"preset": "conventionalcommits",
|
||||
"releaseRules": [
|
||||
{ "type": "feat", "scope": "abacus-react", "release": "minor" },
|
||||
{ "type": "fix", "scope": "abacus-react", "release": "patch" },
|
||||
{ "type": "perf", "scope": "abacus-react", "release": "patch" },
|
||||
{ "type": "refactor", "scope": "abacus-react", "release": "patch" },
|
||||
{ "breaking": true, "scope": "abacus-react", "release": "major" }
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/release-notes-generator",
|
||||
{
|
||||
"preset": "conventionalcommits",
|
||||
"presetConfig": {
|
||||
"types": [
|
||||
{ "type": "feat", "section": "Features" },
|
||||
{ "type": "fix", "section": "Bug Fixes" },
|
||||
{ "type": "perf", "section": "Performance Improvements" },
|
||||
{ "type": "refactor", "section": "Code Refactoring" },
|
||||
{ "type": "docs", "section": "Documentation" },
|
||||
{ "type": "style", "section": "Styles" },
|
||||
{ "type": "test", "section": "Tests" }
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/changelog",
|
||||
{
|
||||
"changelogFile": "CHANGELOG.md"
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/npm",
|
||||
{
|
||||
"npmPublish": true
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/github",
|
||||
{
|
||||
"assets": [
|
||||
{
|
||||
"path": "CHANGELOG.md",
|
||||
"label": "Changelog"
|
||||
},
|
||||
{
|
||||
"path": "dist/**/*",
|
||||
"label": "Distribution files"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/git",
|
||||
{
|
||||
"assets": [
|
||||
"CHANGELOG.md",
|
||||
"package.json"
|
||||
],
|
||||
"message": "chore(abacus-react): release v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
|
@ -375,6 +375,54 @@ import {
|
|||
// All interfaces fully typed for excellent developer experience
|
||||
```
|
||||
|
||||
## Publishing and Versioning
|
||||
|
||||
This package uses [semantic-release](https://semantic-release.gitbook.io/) for automated publishing to npm. Versions are determined by conventional commit messages:
|
||||
|
||||
### Commit Message Format
|
||||
|
||||
Use these prefixes for commits that affect the `packages/abacus-react` directory:
|
||||
|
||||
```bash
|
||||
# New features (minor version bump)
|
||||
feat(abacus-react): add new bead animation system
|
||||
|
||||
# Bug fixes (patch version bump)
|
||||
fix(abacus-react): resolve gesture detection issue
|
||||
|
||||
# Performance improvements (patch version bump)
|
||||
perf(abacus-react): optimize bead rendering
|
||||
|
||||
# Breaking changes (major version bump)
|
||||
feat(abacus-react)!: change callback signature
|
||||
# or
|
||||
feat(abacus-react): redesign API
|
||||
|
||||
BREAKING CHANGE: callback functions now receive different parameters
|
||||
```
|
||||
|
||||
### Release Process
|
||||
|
||||
1. **Automatic**: Releases happen automatically when changes are pushed to `main` branch
|
||||
2. **Manual testing**: Run `pnpm release:dry-run` to test release without publishing
|
||||
3. **Version tags**: Releases are tagged as `abacus-react-v1.2.3` (separate from monorepo versions)
|
||||
|
||||
### Development Commands
|
||||
|
||||
```bash
|
||||
# Build the package
|
||||
pnpm build
|
||||
|
||||
# Run tests
|
||||
pnpm test:run
|
||||
|
||||
# Run Storybook locally
|
||||
pnpm storybook
|
||||
|
||||
# Test release process (dry run)
|
||||
pnpm release:dry-run
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions welcome! Please see our contributing guidelines and feel free to submit issues or pull requests.
|
||||
|
|
|
|||
|
|
@ -28,7 +28,9 @@
|
|||
"storybook": "storybook dev -p 6007",
|
||||
"build-storybook": "storybook build",
|
||||
"clean": "rm -rf dist storybook-static",
|
||||
"generate-examples": "tsx generate-examples.js"
|
||||
"generate-examples": "tsx generate-examples.js",
|
||||
"release": "semantic-release",
|
||||
"release:dry-run": "semantic-release --dry-run"
|
||||
},
|
||||
"keywords": [
|
||||
"react",
|
||||
|
|
@ -51,6 +53,9 @@
|
|||
"@radix-ui/react-tooltip": "^1.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@semantic-release/changelog": "^6.0.0",
|
||||
"@semantic-release/git": "^10.0.0",
|
||||
"@semantic-release/github": "^9.0.0",
|
||||
"@storybook/addon-actions": "^7.6.0",
|
||||
"@storybook/addon-controls": "^7.6.0",
|
||||
"@storybook/addon-docs": "^7.6.0",
|
||||
|
|
@ -68,10 +73,12 @@
|
|||
"@types/react-dom": "^18.2.0",
|
||||
"@vitejs/plugin-react": "^5.0.2",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"conventional-changelog-conventionalcommits": "^7.0.0",
|
||||
"jest-environment-jsdom": "^30.1.2",
|
||||
"jsdom": "^27.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"semantic-release": "^22.0.0",
|
||||
"storybook": "^7.6.0",
|
||||
"tsx": "^4.20.5",
|
||||
"typescript": "^5.0.0",
|
||||
|
|
|
|||
|
|
@ -225,6 +225,15 @@ importers:
|
|||
specifier: ^10.3.0
|
||||
version: 10.3.0(react@18.2.0)
|
||||
devDependencies:
|
||||
'@semantic-release/changelog':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0(semantic-release@22.0.0)
|
||||
'@semantic-release/git':
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0(semantic-release@22.0.0)
|
||||
'@semantic-release/github':
|
||||
specifier: ^9.0.0
|
||||
version: 9.0.0(semantic-release@22.0.0)
|
||||
'@storybook/addon-actions':
|
||||
specifier: ^7.6.0
|
||||
version: 7.6.0
|
||||
|
|
@ -276,6 +285,9 @@ importers:
|
|||
'@vitest/ui':
|
||||
specifier: ^3.2.4
|
||||
version: 3.2.4(vitest@1.0.0)
|
||||
conventional-changelog-conventionalcommits:
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
jest-environment-jsdom:
|
||||
specifier: ^30.1.2
|
||||
version: 30.1.2
|
||||
|
|
@ -288,6 +300,9 @@ importers:
|
|||
react-dom:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0(react@18.2.0)
|
||||
semantic-release:
|
||||
specifier: ^22.0.0
|
||||
version: 22.0.0(typescript@5.0.2)
|
||||
storybook:
|
||||
specifier: ^7.6.0
|
||||
version: 7.6.0
|
||||
|
|
|
|||
Loading…
Reference in New Issue