@madeinbruno/atelier 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/README.md CHANGED
@@ -1,3 +1,61 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # @madeinbruno/atelier
2
+
3
+ madeinbruno.com의 서비스들이 같이 쓰는 공용 컴포넌트예요. 옷 만드는 작업실(아틀리에) 컨셉이라 컴포넌트도 택, 라벨, 도장처럼 옷에 붙는 것들로 지어요.
4
+
5
+ ## 설치
6
+
7
+ ```bash
8
+ pnpm add @madeinbruno/atelier
9
+ ```
10
+
11
+ React 18 이상이 필요해요.
12
+
13
+ ## 컴포넌트
14
+
15
+ ### `Tag` — 공용 푸터
16
+
17
+ 흰 옷 택(행택) 모양의 푸터예요. 택 위쪽에 한 줄 박음질이 있고, 옷 택처럼 `MADE IN BRUNO / STYLE / SIZE / CONTACT`와 madeinbruno.com 링크가 들어가요.
18
+
19
+ ```tsx
20
+ import { Tag } from '@madeinbruno/atelier';
21
+ import '@madeinbruno/atelier/styles.css'; // 앱에서 한 번만
22
+
23
+ export default function Footer() {
24
+ return <Tag service="hangoutwithme" background="#d6efdf" />;
25
+ }
26
+ ```
27
+
28
+ | prop | 기본값 | 설명 |
29
+ |---|---|---|
30
+ | `service` (필수) | | 택의 STYLE 줄. 서비스 이름 |
31
+ | `email` | `pbk575@gmail.com` | CONTACT 줄 (mailto 링크) |
32
+ | `href` | `https://madeinbruno.com` | "둘러보기" 링크 |
33
+ | `since` | `EST. 2026` | 택 오른쪽 위 작은 글씨 |
34
+ | `eyebrow`, `message` | "Thanks for stopping by", "이 사이트도 madeinbruno에서 만들었어요." | 택 왼쪽 글. 둘 다 비우면 택만 가운데에 |
35
+ | `background` | `#f4f1ea` | 푸터 바탕. 서비스 바탕색에 맞추기 |
36
+ | `stitch` | `#9a948b` (회색) | 박음질 색 |
37
+
38
+ 색은 CSS 변수(`--mib-ground`, `--mib-stitch`, `--mib-ink`)로도 바꿀 수 있어요. 클래스는 모두 `mib-`로 시작해 서비스 스타일과 겹치지 않아요. 글꼴(Archivo, IBM Plex Mono)은 `styles.css`가 Google Fonts에서 불러와요.
39
+
40
+ ## 개발
41
+
42
+ ```bash
43
+ pnpm --filter @madeinbruno/atelier build # dist/ 생성
44
+ pnpm --filter @madeinbruno/atelier dev # 고칠 때마다 다시 빌드
45
+ ```
46
+
47
+ ## 배포 (npm)
48
+
49
+ 처음 한 번: npm에 `madeinbruno` 조직(무료, 공개 패키지)을 만들고 로그인해요.
50
+
51
+ ```bash
52
+ npm login
53
+ ```
54
+
55
+ 배포할 때마다 `package.json`의 `version`을 올리고:
56
+
57
+ ```bash
58
+ pnpm --filter @madeinbruno/atelier publish --access public
59
+ ```
60
+
61
+ (`prepublishOnly`가 먼저 빌드해요.)
@@ -0,0 +1,30 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ type TagProps = {
5
+ /** 이 서비스의 이름. 택의 STYLE 줄에 들어간다 (예: "hangoutwithme"). */
6
+ service: string;
7
+ /** 연락받을 이메일. 택의 CONTACT 줄에 mailto 링크로 들어간다. */
8
+ email?: string;
9
+ /** "둘러보기" 링크가 가는 곳. */
10
+ href?: string;
11
+ /** 택 오른쪽 위의 작은 글씨. */
12
+ since?: string;
13
+ /** 택 왼쪽에 놓이는 글. 작은 머리글(eyebrow)과 한두 줄 본문. 비우면 택만 가운데에 놓인다. */
14
+ eyebrow?: ReactNode;
15
+ message?: ReactNode;
16
+ /** 푸터 바탕색. 서비스 바탕에 맞춘다 (기본: 옅은 크림). */
17
+ background?: string;
18
+ /** 택 위쪽 박음질 색 (기본: 회색). */
19
+ stitch?: string;
20
+ className?: string;
21
+ };
22
+ /**
23
+ * madeinbruno 공용 푸터: 흰 옷 택(행택).
24
+ * 택 위쪽에 한 줄 박음질, 끈 구멍, 모서리를 깎은 모양이고, 옷 택 형식으로
25
+ * MADE IN BRUNO / STYLE(서비스 이름) / SIZE / CONTACT(이메일)와 madeinbruno.com 링크를 담는다.
26
+ * 스타일은 '@madeinbruno/atelier/styles.css'를 한 번 불러와야 적용된다.
27
+ */
28
+ declare function Tag({ service, email, href, since, eyebrow, message, background, stitch, className, }: TagProps): react.JSX.Element;
29
+
30
+ export { Tag, type TagProps };
package/dist/index.js ADDED
@@ -0,0 +1,49 @@
1
+ // src/Tag.tsx
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ function Tag({
4
+ service,
5
+ email = "pbk575@gmail.com",
6
+ href = "https://madeinbruno.com",
7
+ since = "EST. 2026",
8
+ eyebrow = "Thanks for stopping by",
9
+ message = `\uC774 \uC0AC\uC774\uD2B8\uB3C4 madeinbruno\uC5D0\uC11C \uB9CC\uB4E4\uC5C8\uC5B4\uC694.`,
10
+ background,
11
+ stitch,
12
+ className
13
+ }) {
14
+ const style = {
15
+ ...background ? { "--mib-ground": background } : {},
16
+ ...stitch ? { "--mib-stitch": stitch } : {}
17
+ };
18
+ const hasMessage = Boolean(eyebrow || message);
19
+ const linkLabel = href.replace(/^https?:\/\//, "").replace(/\/$/, "");
20
+ return /* @__PURE__ */ jsx("footer", { className: ["mib-footer", className].filter(Boolean).join(" "), style, children: /* @__PURE__ */ jsxs("div", { className: hasMessage ? "mib-footer__inner" : "mib-footer__inner mib-footer__inner--solo", children: [
21
+ hasMessage && /* @__PURE__ */ jsxs("div", { className: "mib-footer__message", children: [
22
+ eyebrow && /* @__PURE__ */ jsx("p", { className: "mib-footer__eyebrow", children: eyebrow }),
23
+ message && /* @__PURE__ */ jsx("p", { className: "mib-footer__text", children: message })
24
+ ] }),
25
+ /* @__PURE__ */ jsx("div", { className: "mib-tag-hang", children: /* @__PURE__ */ jsx("div", { className: "mib-tag", children: /* @__PURE__ */ jsxs("div", { className: "mib-tag__card", children: [
26
+ /* @__PURE__ */ jsx("span", { className: "mib-tag__stitch", "aria-hidden": "true" }),
27
+ /* @__PURE__ */ jsx("span", { className: "mib-tag__hole", "aria-hidden": "true" }),
28
+ /* @__PURE__ */ jsxs("div", { className: "mib-tag__head", children: [
29
+ /* @__PURE__ */ jsx("span", { className: "mib-tag__brand", children: "MADE IN BRUNO" }),
30
+ /* @__PURE__ */ jsx("span", { className: "mib-tag__since", children: since })
31
+ ] }),
32
+ /* @__PURE__ */ jsxs("dl", { className: "mib-tag__specs", children: [
33
+ /* @__PURE__ */ jsx("dt", { children: "STYLE" }),
34
+ /* @__PURE__ */ jsx("dd", { children: service }),
35
+ /* @__PURE__ */ jsx("dt", { children: "SIZE" }),
36
+ /* @__PURE__ */ jsx("dd", { children: "ONE OF ONE" }),
37
+ /* @__PURE__ */ jsx("dt", { children: "CONTACT" }),
38
+ /* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsx("a", { href: `mailto:${email}`, children: email }) })
39
+ ] }),
40
+ /* @__PURE__ */ jsxs("a", { className: "mib-tag__link", href, children: [
41
+ linkLabel,
42
+ " \uB458\uB7EC\uBCF4\uAE30 \u2192"
43
+ ] })
44
+ ] }) }) })
45
+ ] }) });
46
+ }
47
+ export {
48
+ Tag
49
+ };
package/package.json CHANGED
@@ -1,6 +1,47 @@
1
1
  {
2
2
  "name": "@madeinbruno/atelier",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "madeinbruno.com 서비스들이 같이 쓰는 공용 컴포넌트 (옷 만드는 작업실 컨셉: 택, 라벨, 도장…)",
5
+ "license": "MIT",
6
+ "author": "codingbotPark <pbk575@gmail.com>",
7
+ "homepage": "https://madeinbruno.com",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/codingbotPark/madeinbruno.git",
11
+ "directory": "packages/atelier"
12
+ },
13
+ "type": "module",
14
+ "sideEffects": [
15
+ "*.css"
16
+ ],
17
+ "main": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js"
23
+ },
24
+ "./styles.css": "./styles.css"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "styles.css"
29
+ ],
30
+ "peerDependencies": {
31
+ "react": ">=18"
32
+ },
33
+ "devDependencies": {
34
+ "@types/react": "^19",
35
+ "react": "19.2.3",
36
+ "tsup": "^8.5.0",
37
+ "typescript": "^5"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "scripts": {
43
+ "build": "tsup",
44
+ "dev": "tsup --watch",
45
+ "check": "tsc --noEmit"
46
+ }
6
47
  }
package/styles.css ADDED
@@ -0,0 +1,200 @@
1
+ /* @madeinbruno/atelier 공용 스타일. 쓰는 쪽에서 한 번만 불러온다: import '@madeinbruno/atelier/styles.css'
2
+ 클래스 이름은 모두 mib- 로 시작해 서비스 자체 스타일과 겹치지 않는다.
3
+ 색은 CSS 변수로 바꿀 수 있다: --mib-ground(푸터 바탕), --mib-stitch(박음질), --mib-ink(글씨). */
4
+ @import url('https://fonts.googleapis.com/css2?family=Archivo:wght@500;800&family=IBM+Plex+Mono:wght@400;500&display=swap');
5
+
6
+ /* ───────── Tag: 흰 옷 택 푸터 ───────── */
7
+
8
+ .mib-footer {
9
+ --mib-ground: #f4f1ea;
10
+ --mib-stitch: #9a948b;
11
+ --mib-ink: #1f1d1a;
12
+ --mib-muted: #6b655c;
13
+ box-sizing: border-box;
14
+ width: 100%;
15
+ overflow: hidden;
16
+ padding: 0 24px;
17
+ border-top: 1px solid rgba(31, 29, 26, 0.12);
18
+ background: var(--mib-ground);
19
+ font-family: 'IBM Plex Mono', ui-monospace, monospace;
20
+ color: var(--mib-ink);
21
+ }
22
+
23
+ .mib-footer *,
24
+ .mib-footer *::before,
25
+ .mib-footer *::after {
26
+ box-sizing: border-box;
27
+ }
28
+
29
+ .mib-footer a {
30
+ color: inherit;
31
+ text-decoration: none;
32
+ }
33
+
34
+ .mib-footer a:hover {
35
+ color: #b4442f;
36
+ }
37
+
38
+ .mib-footer__inner {
39
+ display: flex;
40
+ flex-wrap: wrap;
41
+ align-items: center;
42
+ justify-content: space-between;
43
+ gap: 32px 64px;
44
+ max-width: 1120px;
45
+ margin: 0 auto;
46
+ padding-bottom: 56px;
47
+ }
48
+
49
+ .mib-footer__inner--solo {
50
+ justify-content: center;
51
+ }
52
+
53
+ .mib-footer__message {
54
+ display: flex;
55
+ flex: 1 1 300px;
56
+ flex-direction: column;
57
+ gap: 14px;
58
+ max-width: 440px;
59
+ padding-top: 64px;
60
+ }
61
+
62
+ .mib-footer__eyebrow {
63
+ margin: 0;
64
+ font-size: 13px;
65
+ letter-spacing: 0.08em;
66
+ text-transform: uppercase;
67
+ color: var(--mib-muted);
68
+ }
69
+
70
+ .mib-footer__text {
71
+ margin: 0;
72
+ font-family: 'Archivo', system-ui, sans-serif;
73
+ font-size: clamp(20px, 3vw, 28px);
74
+ font-weight: 500;
75
+ line-height: 1.35;
76
+ letter-spacing: -0.01em;
77
+ word-break: keep-all;
78
+ }
79
+
80
+ .mib-tag-hang {
81
+ position: relative;
82
+ flex: 0 1 470px;
83
+ padding-top: 56px;
84
+ }
85
+
86
+ /* 구멍을 축으로 살짝 기울어 매달려 있다. */
87
+ .mib-tag {
88
+ transform: rotate(-4deg);
89
+ transform-origin: 77px 13px;
90
+ filter: drop-shadow(0 10px 18px rgba(31, 29, 26, 0.14));
91
+ }
92
+
93
+ .mib-tag__card {
94
+ position: relative;
95
+ display: flex;
96
+ flex-direction: column;
97
+ gap: 14px;
98
+ min-height: 236px;
99
+ padding: 46px 34px 26px;
100
+ background: #ffffff;
101
+ /* 옷 택처럼 위쪽 두 모서리를 비스듬히 깎는다 */
102
+ clip-path: polygon(18px 0, calc(100% - 18px) 0, 100% 18px, 100% 100%, 0 100%, 0 18px);
103
+ }
104
+
105
+ .mib-tag__stitch {
106
+ position: absolute;
107
+ top: 22px;
108
+ left: 22px;
109
+ right: 22px;
110
+ height: 2px;
111
+ background: repeating-linear-gradient(90deg, var(--mib-stitch) 0 9px, transparent 9px 15px);
112
+ }
113
+
114
+ .mib-tag__hole {
115
+ position: absolute;
116
+ top: 6px;
117
+ left: 70px;
118
+ width: 14px;
119
+ height: 14px;
120
+ border-radius: 50%;
121
+ background: var(--mib-ground);
122
+ box-shadow: inset 0 0 0 3px #d9d2c5;
123
+ }
124
+
125
+ .mib-tag__head {
126
+ display: flex;
127
+ flex-wrap: wrap;
128
+ align-items: baseline;
129
+ justify-content: space-between;
130
+ gap: 4px 12px;
131
+ }
132
+
133
+ .mib-tag__brand {
134
+ font-family: 'Archivo', system-ui, sans-serif;
135
+ font-size: clamp(24px, 7vw, 31px);
136
+ font-weight: 800;
137
+ letter-spacing: 0.02em;
138
+ white-space: nowrap;
139
+ }
140
+
141
+ .mib-tag__since {
142
+ font-size: 12px;
143
+ color: var(--mib-muted);
144
+ }
145
+
146
+ .mib-tag__specs {
147
+ display: grid;
148
+ grid-template-columns: auto minmax(0, 1fr);
149
+ gap: 6px 24px;
150
+ margin: 0;
151
+ padding-top: 14px;
152
+ border-top: 1px solid #e7e1d6;
153
+ font-size: 13px;
154
+ }
155
+
156
+ .mib-tag__specs dt {
157
+ color: var(--mib-muted);
158
+ }
159
+
160
+ .mib-tag__specs dd {
161
+ margin: 0;
162
+ overflow-wrap: anywhere;
163
+ }
164
+
165
+ .mib-tag__link {
166
+ align-self: flex-start;
167
+ margin-top: auto;
168
+ padding-bottom: 2px;
169
+ border-bottom: 1px solid currentColor;
170
+ font-size: 14px;
171
+ font-weight: 500;
172
+ }
173
+
174
+ /* 좁은 화면: 글 아래에 택이 오고, 택은 화면 폭에 맞춘다. */
175
+ @media (max-width: 640px) {
176
+ .mib-footer__inner {
177
+ flex-direction: column;
178
+ align-items: stretch;
179
+ gap: 8px;
180
+ padding-bottom: 40px;
181
+ }
182
+
183
+ .mib-footer__message {
184
+ flex: none;
185
+ padding-top: 48px;
186
+ }
187
+
188
+ .mib-tag-hang {
189
+ flex: none;
190
+ width: 100%;
191
+ max-width: 470px;
192
+ align-self: center;
193
+ padding-top: 24px;
194
+ }
195
+
196
+ .mib-tag__card {
197
+ min-height: 0;
198
+ padding: 44px 24px 24px;
199
+ }
200
+ }