mirror of
https://gitea.gofwd.group/sean/gunbuilder-next-tailwind.git
synced 2025-12-05 18:46:45 -05:00
tailwinds custom theme and tw plus components
This commit is contained in:
232
package-lock.json
generated
232
package-lock.json
generated
@@ -8,6 +8,8 @@
|
||||
"name": "pew-builder-nextjs",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^2.2.4",
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"next": "15.3.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
@@ -225,6 +227,88 @@
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz",
|
||||
"integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/utils": "^0.2.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.2.tgz",
|
||||
"integrity": "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.7.2",
|
||||
"@floating-ui/utils": "^0.2.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/react": {
|
||||
"version": "0.26.28",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz",
|
||||
"integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/react-dom": "^2.1.2",
|
||||
"@floating-ui/utils": "^0.2.8",
|
||||
"tabbable": "^6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/react-dom": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.4.tgz",
|
||||
"integrity": "sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.7.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/utils": {
|
||||
"version": "0.2.10",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
|
||||
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@headlessui/react": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.4.tgz",
|
||||
"integrity": "sha512-lz+OGcAH1dK93rgSMzXmm1qKOJkBUqZf1L4M8TWLNplftQD3IkoEDdUFNfAn4ylsN6WOTVtWaLmvmaHOUk1dTA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "^0.26.16",
|
||||
"@react-aria/focus": "^3.20.2",
|
||||
"@react-aria/interactions": "^3.25.0",
|
||||
"@tanstack/react-virtual": "^3.13.9",
|
||||
"use-sync-external-store": "^1.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18 || ^19 || ^19.0.0-rc",
|
||||
"react-dom": "^18 || ^19 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroicons/react": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz",
|
||||
"integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">= 16 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||
@@ -974,6 +1058,103 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/focus": {
|
||||
"version": "3.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.20.5.tgz",
|
||||
"integrity": "sha512-JpFtXmWQ0Oca7FcvkqgjSyo6xEP7v3oQOLUId6o0xTvm4AD5W0mU2r3lYrbhsJ+XxdUUX4AVR5473sZZ85kU4A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@react-aria/interactions": "^3.25.3",
|
||||
"@react-aria/utils": "^3.29.1",
|
||||
"@react-types/shared": "^3.30.0",
|
||||
"@swc/helpers": "^0.5.0",
|
||||
"clsx": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
|
||||
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/interactions": {
|
||||
"version": "3.25.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.3.tgz",
|
||||
"integrity": "sha512-J1bhlrNtjPS/fe5uJQ+0c7/jiXniwa4RQlP+Emjfc/iuqpW2RhbF9ou5vROcLzWIyaW8tVMZ468J68rAs/aZ5A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@react-aria/ssr": "^3.9.9",
|
||||
"@react-aria/utils": "^3.29.1",
|
||||
"@react-stately/flags": "^3.1.2",
|
||||
"@react-types/shared": "^3.30.0",
|
||||
"@swc/helpers": "^0.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
|
||||
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/ssr": {
|
||||
"version": "3.9.9",
|
||||
"resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.9.tgz",
|
||||
"integrity": "sha512-2P5thfjfPy/np18e5wD4WPt8ydNXhij1jwA8oehxZTFqlgVMGXzcWKxTb4RtJrLFsqPO7RUQTiY8QJk0M4Vy2g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/helpers": "^0.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/utils": {
|
||||
"version": "3.29.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.29.1.tgz",
|
||||
"integrity": "sha512-yXMFVJ73rbQ/yYE/49n5Uidjw7kh192WNN9PNQGV0Xoc7EJUlSOxqhnpHmYTyO0EotJ8fdM1fMH8durHjUSI8g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@react-aria/ssr": "^3.9.9",
|
||||
"@react-stately/flags": "^3.1.2",
|
||||
"@react-stately/utils": "^3.10.7",
|
||||
"@react-types/shared": "^3.30.0",
|
||||
"@swc/helpers": "^0.5.0",
|
||||
"clsx": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
|
||||
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-stately/flags": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.2.tgz",
|
||||
"integrity": "sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/helpers": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-stately/utils": {
|
||||
"version": "3.10.7",
|
||||
"resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.7.tgz",
|
||||
"integrity": "sha512-cWvjGAocvy4abO9zbr6PW6taHgF24Mwy/LbQ4TC4Aq3tKdKDntxyD+sh7AkSRfJRT2ccMVaHVv2+FfHThd3PKQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/helpers": "^0.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-types/shared": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
|
||||
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@rtsao/scc": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
||||
@@ -1003,6 +1184,33 @@
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-virtual": {
|
||||
"version": "3.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz",
|
||||
"integrity": "sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/virtual-core": "3.13.12"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/virtual-core": {
|
||||
"version": "3.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz",
|
||||
"integrity": "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tybys/wasm-util": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz",
|
||||
@@ -2225,6 +2433,15 @@
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
@@ -5887,6 +6104,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/tabbable": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
|
||||
"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.4",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz",
|
||||
@@ -6311,6 +6534,15 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
|
||||
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^2.2.4",
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"next": "15.3.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
import SearchInput from '@/components/SearchInput';
|
||||
|
||||
// AR-15 Build Requirements grouped by main categories
|
||||
const buildGroups = [
|
||||
@@ -333,13 +334,11 @@ export default function BuildPage() {
|
||||
{/* Search Row */}
|
||||
<div className="mb-4 flex justify-end">
|
||||
<div className="w-1/2">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Search Components</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search components..."
|
||||
<SearchInput
|
||||
label="Search Components"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
onChange={setSearchTerm}
|
||||
placeholder="Search components..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -459,23 +458,23 @@ export default function BuildPage() {
|
||||
return (
|
||||
<React.Fragment key={group.name}>
|
||||
{/* Group Header */}
|
||||
<tr className="bg-gray-50">
|
||||
<tr className="bg-gray-800">
|
||||
<td colSpan={7} className="px-6 py-4">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center">
|
||||
<span className="text-blue-600 font-semibold text-sm">
|
||||
<div className="w-10 h-10 bg-blue-500 rounded-lg flex items-center justify-center">
|
||||
<span className="text-white font-semibold text-lg">
|
||||
{group.name === 'Upper Parts' ? '🔫' :
|
||||
group.name === 'Lower Parts' ? '🔧' : '📦'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900">{group.name}</h3>
|
||||
<p className="text-sm text-gray-500">{group.description}</p>
|
||||
<h3 className="text-xl font-bold text-white">{group.name}</h3>
|
||||
<p className="text-gray-300">{group.description}</p>
|
||||
</div>
|
||||
<div className="ml-auto text-right">
|
||||
<div className="text-sm text-gray-500">
|
||||
<div className="text-gray-300 font-medium">
|
||||
{groupComponents.length} components
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import SearchInput from '@/components/SearchInput';
|
||||
|
||||
// Sample build data
|
||||
const sampleBuilds = [
|
||||
@@ -252,13 +253,11 @@ export default function BuildsPage() {
|
||||
{/* Search Row */}
|
||||
<div className="mb-4 flex justify-end">
|
||||
<div className="w-1/2">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Search Builds</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search builds..."
|
||||
<SearchInput
|
||||
label="Search Builds"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
onChange={setSearchTerm}
|
||||
placeholder="Search builds..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,56 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply transition-colors duration-200;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
/* Custom scrollbar for webkit browsers */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
@apply bg-neutral-100 dark:bg-neutral-800;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
@apply bg-neutral-300 dark:bg-neutral-600 rounded-full;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
@apply bg-neutral-400 dark:bg-neutral-500;
|
||||
}
|
||||
|
||||
/* Focus styles for better accessibility */
|
||||
.focus-ring {
|
||||
@apply focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:focus:ring-offset-neutral-900;
|
||||
}
|
||||
|
||||
/* Card styles */
|
||||
.card {
|
||||
@apply bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg shadow-sm hover:shadow-md transition-shadow duration-200;
|
||||
}
|
||||
|
||||
/* Button styles */
|
||||
.btn-primary {
|
||||
@apply bg-primary-600 hover:bg-primary-700 dark:bg-primary-500 dark:hover:bg-primary-600 text-white font-medium py-2 px-4 rounded-lg transition-colors duration-200 focus-ring;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-700 dark:hover:bg-neutral-600 text-neutral-700 dark:text-neutral-300 font-medium py-2 px-4 rounded-lg transition-colors duration-200 focus-ring;
|
||||
}
|
||||
|
||||
/* Input styles */
|
||||
.input-field {
|
||||
@apply w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg bg-white dark:bg-neutral-800 text-neutral-900 dark:text-white placeholder-neutral-500 dark:placeholder-neutral-400 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500 transition-colors duration-200;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,14 @@
|
||||
import "./globals.css";
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import { Navbar } from "@/components/Navbar";
|
||||
import { Inter } from "next/font/google";
|
||||
import Navbar from "@/components/Navbar";
|
||||
import { ThemeProvider } from "@/components/ThemeProvider";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Pew Builder - Firearm Parts Catalog",
|
||||
description: "Build your dream AR-15 with our comprehensive parts catalog and build checklist",
|
||||
title: "Pew Builder - Firearm Parts Catalog & Build Management",
|
||||
description: "Professional firearm parts catalog and AR-15 build management system",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
@@ -24,10 +17,14 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={`${geistSans.variable} ${geistMono.variable}`}>
|
||||
<Navbar />
|
||||
{children}
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body className={`${inter.className} antialiased`}>
|
||||
<ThemeProvider>
|
||||
<div className="min-h-screen bg-neutral-50 dark:bg-neutral-900 transition-colors duration-200">
|
||||
<Navbar />
|
||||
{children}
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
753
src/app/page.tsx
753
src/app/page.tsx
File diff suppressed because it is too large
Load Diff
@@ -2,52 +2,56 @@
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import ThemeSwitcher from './ThemeSwitcher';
|
||||
|
||||
export function Navbar() {
|
||||
export default function Navbar() {
|
||||
const pathname = usePathname();
|
||||
|
||||
const navItems = [
|
||||
{ name: 'Parts Catalog', href: '/' },
|
||||
{ name: 'Build Checklist', href: '/build' },
|
||||
{ name: 'My Builds', href: '/builds' },
|
||||
{ href: '/', label: 'Parts Catalog' },
|
||||
{ href: '/build', label: 'Build Checklist' },
|
||||
{ href: '/builds', label: 'My Builds' },
|
||||
];
|
||||
|
||||
return (
|
||||
<nav className="bg-white shadow-sm border-b">
|
||||
<nav className="bg-white dark:bg-neutral-800 border-b border-neutral-200 dark:border-neutral-700 shadow-sm">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between items-center h-16">
|
||||
{/* Logo/Brand */}
|
||||
{/* Logo */}
|
||||
<div className="flex items-center">
|
||||
<Link href="/" className="text-2xl font-bold text-gray-900">
|
||||
Pew Builder
|
||||
<Link href="/" className="flex items-center space-x-2">
|
||||
<div className="w-8 h-8 bg-primary-600 dark:bg-primary-500 rounded-lg flex items-center justify-center">
|
||||
<span className="text-white font-bold text-lg">🔫</span>
|
||||
</div>
|
||||
<span className="text-xl font-bold text-neutral-900 dark:text-white">
|
||||
Pew Builder
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Navigation Links */}
|
||||
<div className="hidden md:block">
|
||||
<div className="ml-10 flex items-baseline space-x-4">
|
||||
{navItems.map((item) => {
|
||||
const isActive = pathname === item.href;
|
||||
return (
|
||||
<Link
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
isActive
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'text-gray-700 hover:bg-gray-100 hover:text-gray-900'
|
||||
}`}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="hidden md:flex items-center space-x-8">
|
||||
{navItems.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
pathname === item.href
|
||||
? 'bg-primary-100 dark:bg-primary-900 text-primary-700 dark:text-primary-300'
|
||||
: 'text-neutral-600 dark:text-neutral-300 hover:text-neutral-900 dark:hover:text-white hover:bg-neutral-100 dark:hover:bg-neutral-700'
|
||||
}`}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Mobile menu button */}
|
||||
<div className="md:hidden">
|
||||
<button className="text-gray-700 hover:text-gray-900 focus:outline-none focus:text-gray-900">
|
||||
{/* Theme Switcher */}
|
||||
<div className="flex items-center space-x-4">
|
||||
<ThemeSwitcher />
|
||||
|
||||
{/* Mobile menu button */}
|
||||
<button className="md:hidden p-2 rounded-md text-neutral-600 dark:text-neutral-300 hover:text-neutral-900 dark:hover:text-white hover:bg-neutral-100 dark:hover:bg-neutral-700">
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
@@ -63,7 +67,7 @@ export function Navbar() {
|
||||
const isActive = pathname === item.href;
|
||||
return (
|
||||
<Link
|
||||
key={item.name}
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={`block px-3 py-2 rounded-md text-base font-medium transition-colors ${
|
||||
isActive
|
||||
@@ -71,7 +75,7 @@ export function Navbar() {
|
||||
: 'text-gray-700 hover:bg-gray-100 hover:text-gray-900'
|
||||
}`}
|
||||
>
|
||||
{item.name}
|
||||
{item.label}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
|
||||
39
src/components/SearchInput.tsx
Normal file
39
src/components/SearchInput.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { MagnifyingGlassIcon } from '@heroicons/react/20/solid';
|
||||
|
||||
interface SearchInputProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
placeholder?: string;
|
||||
label?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function SearchInput({
|
||||
value,
|
||||
onChange,
|
||||
placeholder = "Search...",
|
||||
label,
|
||||
className = ""
|
||||
}: SearchInputProps) {
|
||||
return (
|
||||
<div className={className}>
|
||||
{label && (
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<MagnifyingGlassIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
52
src/components/ThemeProvider.tsx
Normal file
52
src/components/ThemeProvider.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
|
||||
type Theme = 'light' | 'dark';
|
||||
|
||||
interface ThemeContextType {
|
||||
theme: Theme;
|
||||
toggleTheme: () => void;
|
||||
}
|
||||
|
||||
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
||||
|
||||
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
const [theme, setTheme] = useState<Theme>('light');
|
||||
|
||||
useEffect(() => {
|
||||
// Check for saved theme preference or default to light mode
|
||||
const savedTheme = localStorage.getItem('theme') as Theme;
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
|
||||
if (savedTheme) {
|
||||
setTheme(savedTheme);
|
||||
} else if (prefersDark) {
|
||||
setTheme('dark');
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Update document class and save preference
|
||||
document.documentElement.classList.toggle('dark', theme === 'dark');
|
||||
localStorage.setItem('theme', theme);
|
||||
}, [theme]);
|
||||
|
||||
const toggleTheme = () => {
|
||||
setTheme(prev => prev === 'light' ? 'dark' : 'light');
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, toggleTheme }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useTheme() {
|
||||
const context = useContext(ThemeContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useTheme must be used within a ThemeProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
28
src/components/ThemeSwitcher.tsx
Normal file
28
src/components/ThemeSwitcher.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
'use client';
|
||||
|
||||
import { SunIcon, MoonIcon } from '@heroicons/react/24/outline';
|
||||
import { useTheme } from './ThemeProvider';
|
||||
|
||||
export default function ThemeSwitcher() {
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
className="relative inline-flex h-8 w-14 items-center rounded-full bg-gray-200 dark:bg-gray-700 transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
|
||||
aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-6 w-6 transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out ${
|
||||
theme === 'dark' ? 'translate-x-7' : 'translate-x-1'
|
||||
}`}
|
||||
>
|
||||
{theme === 'light' ? (
|
||||
<SunIcon className="h-6 w-6 text-yellow-500" />
|
||||
) : (
|
||||
<MoonIcon className="h-6 w-6 text-blue-400" />
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,104 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/**/*.{js,ts,jsx,tsx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx}",
|
||||
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
colors: {
|
||||
// Primary brand colors
|
||||
primary: {
|
||||
50: '#f0f9ff',
|
||||
100: '#e0f2fe',
|
||||
200: '#bae6fd',
|
||||
300: '#7dd3fc',
|
||||
400: '#38bdf8',
|
||||
500: '#0ea5e9',
|
||||
600: '#0284c7',
|
||||
700: '#0369a1',
|
||||
800: '#075985',
|
||||
900: '#0c4a6e',
|
||||
950: '#082f49',
|
||||
},
|
||||
// Secondary accent colors
|
||||
accent: {
|
||||
50: '#fef2f2',
|
||||
100: '#fee2e2',
|
||||
200: '#fecaca',
|
||||
300: '#fca5a5',
|
||||
400: '#f87171',
|
||||
500: '#ef4444',
|
||||
600: '#dc2626',
|
||||
700: '#b91c1c',
|
||||
800: '#991b1b',
|
||||
900: '#7f1d1d',
|
||||
950: '#450a0a',
|
||||
},
|
||||
// Neutral grays with warm undertones
|
||||
neutral: {
|
||||
50: '#fafafa',
|
||||
100: '#f5f5f5',
|
||||
200: '#e5e5e5',
|
||||
300: '#d4d4d4',
|
||||
400: '#a3a3a3',
|
||||
500: '#737373',
|
||||
600: '#525252',
|
||||
700: '#404040',
|
||||
800: '#262626',
|
||||
900: '#171717',
|
||||
950: '#0a0a0a',
|
||||
},
|
||||
// Success colors
|
||||
success: {
|
||||
50: '#f0fdf4',
|
||||
100: '#dcfce7',
|
||||
200: '#bbf7d0',
|
||||
300: '#86efac',
|
||||
400: '#4ade80',
|
||||
500: '#22c55e',
|
||||
600: '#16a34a',
|
||||
700: '#15803d',
|
||||
800: '#166534',
|
||||
900: '#14532d',
|
||||
950: '#052e16',
|
||||
},
|
||||
// Warning colors
|
||||
warning: {
|
||||
50: '#fffbeb',
|
||||
100: '#fef3c7',
|
||||
200: '#fde68a',
|
||||
300: '#fcd34d',
|
||||
400: '#fbbf24',
|
||||
500: '#f59e0b',
|
||||
600: '#d97706',
|
||||
700: '#b45309',
|
||||
800: '#92400e',
|
||||
900: '#78350f',
|
||||
950: '#451a03',
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', 'sans-serif'],
|
||||
display: ['Inter', 'system-ui', 'sans-serif'],
|
||||
},
|
||||
animation: {
|
||||
'fade-in': 'fadeIn 0.5s ease-in-out',
|
||||
'slide-up': 'slideUp 0.3s ease-out',
|
||||
},
|
||||
keyframes: {
|
||||
fadeIn: {
|
||||
'0%': { opacity: '0' },
|
||||
'100%': { opacity: '1' },
|
||||
},
|
||||
slideUp: {
|
||||
'0%': { transform: 'translateY(10px)', opacity: '0' },
|
||||
'100%': { transform: 'translateY(0)', opacity: '1' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
Reference in New Issue
Block a user