@toife/vue 3.0.6 → 3.1.2

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.
Files changed (70) hide show
  1. package/README.md +4 -2
  2. package/package.json +4 -8
  3. package/src/components/action/action.scss +1 -1
  4. package/src/components/app/app.scss +1 -1
  5. package/src/components/avatar/avatar.scss +1 -1
  6. package/src/components/button/button.scss +1 -1
  7. package/src/components/cable/cable.scss +1 -1
  8. package/src/components/card/card/card.scss +1 -1
  9. package/src/components/card/card/card.vue +1 -1
  10. package/src/components/card/card-body/card-body.scss +1 -1
  11. package/src/components/card/card-footer/card-footer.scss +1 -1
  12. package/src/components/card/card-header/card-header.scss +1 -1
  13. package/src/components/checkbox/checkbox.scss +1 -1
  14. package/src/components/collapse/collapse.scss +1 -1
  15. package/src/components/container/container.scss +1 -1
  16. package/src/components/decision-modal/decision-modal.scss +6 -1
  17. package/src/components/decision-modal/decision-modal.vue +3 -5
  18. package/src/components/divider/divider.scss +1 -1
  19. package/src/components/dropdown/dropdown.html +6 -0
  20. package/src/components/dropdown/dropdown.scss +68 -0
  21. package/src/components/dropdown/dropdown.type.ts +19 -0
  22. package/src/components/dropdown/dropdown.vue +109 -0
  23. package/src/components/dropdown/index.ts +2 -0
  24. package/src/components/field/field.html +12 -1
  25. package/src/components/field/outline/outline.html +29 -25
  26. package/src/components/field/outline/outline.scss +105 -52
  27. package/src/components/field/outline/outline.vue +59 -16
  28. package/src/components/form-group/form-group.scss +1 -1
  29. package/src/components/gesture-indicator/gesture-indicator.scss +1 -1
  30. package/src/components/index.ts +3 -0
  31. package/src/components/layout/cell/cell.html +1 -0
  32. package/src/components/layout/cell/cell.md +47 -0
  33. package/src/components/layout/cell/cell.scss +54 -0
  34. package/src/components/layout/cell/cell.type.ts +19 -0
  35. package/src/components/layout/cell/cell.vue +35 -0
  36. package/src/components/layout/cell/index.ts +2 -0
  37. package/src/components/layout/flex/flex.html +1 -0
  38. package/src/components/layout/flex/flex.scss +59 -0
  39. package/src/components/layout/flex/flex.type.ts +13 -0
  40. package/src/components/layout/flex/flex.vue +34 -0
  41. package/src/components/layout/flex/index.ts +2 -0
  42. package/src/components/layout/grid/grid.html +1 -0
  43. package/src/components/layout/grid/grid.md +50 -0
  44. package/src/components/layout/grid/grid.scss +53 -0
  45. package/src/components/layout/grid/grid.type.ts +12 -0
  46. package/src/components/layout/grid/grid.vue +32 -0
  47. package/src/components/layout/grid/index.ts +2 -0
  48. package/src/components/layout/index.ts +3 -0
  49. package/src/components/modal/modal.scss +1 -1
  50. package/src/components/page/page.scss +1 -1
  51. package/src/components/present/present.scss +1 -1
  52. package/src/components/radio/radio/radio.scss +1 -1
  53. package/src/components/radio/radio-group/radio-group.scss +1 -1
  54. package/src/components/refresher/refresher.scss +1 -1
  55. package/src/components/route/route-navigator/route-navigator.scss +5 -5
  56. package/src/components/route/route-navigator/route-navigator.vue +26 -20
  57. package/src/components/segmented-field/segmented-field.scss +1 -1
  58. package/src/components/select/index.ts +2 -0
  59. package/src/components/select/select.html +29 -0
  60. package/src/components/select/select.scss +129 -0
  61. package/src/components/select/select.type.ts +45 -0
  62. package/src/components/select/select.vue +119 -0
  63. package/src/components/skeleton/skeleton.scss +1 -1
  64. package/src/components/switch/switch.scss +1 -1
  65. package/src/components/tabs/tabs/tabs.scss +6 -6
  66. package/src/components/toast/toast/toast.scss +1 -1
  67. package/src/components/toast/toast-content/toast-content.scss +1 -1
  68. package/src/components/toolbar/toolbar.scss +1 -1
  69. package/src/factory.ts +10 -0
  70. package/src/utils/style/index.ts +1 -1
@@ -1,13 +1,12 @@
1
- @use "@toife/sass-layer-generator" as sass;
1
+ @use "@toife/sass-layer" as sass;
2
2
 
3
3
  // Class name
4
4
  $field: sass.fn-naming-prefix("field");
5
5
  $field-content: sass.fn-naming-prefix("field-content");
6
6
  $field-input: sass.fn-naming-prefix("field-input");
7
+ $field-input-editable: sass.fn-naming-prefix("field-input-editable");
7
8
  $field-message: sass.fn-naming-prefix("field-message");
8
9
  $field-help: sass.fn-naming-prefix("field-help");
9
- $field-direction-left: sass.fn-naming-prefix("direction", "left");
10
- $field-direction-right: sass.fn-naming-prefix("direction", "right");
11
10
 
12
11
  // Property name - layer: field
13
12
  $field-border-color: sass.fn-naming-var("field", "border-color");
@@ -30,6 +29,9 @@ $size-line-height: sass.fn-naming-var("line-height");
30
29
 
31
30
  $transition-duration: sass.fn-naming-var("motion", "duration");
32
31
 
32
+ $direction-text-align: sass.fn-naming-var("text-align");
33
+ $direction-justify-content: sass.fn-naming-var("justify-content");
34
+
33
35
  .#{$field} {
34
36
  width: 100%;
35
37
  box-shadow: none !important;
@@ -50,10 +52,13 @@ $transition-duration: sass.fn-naming-var("motion", "duration");
50
52
  line-height: 0;
51
53
  height: fit-content;
52
54
  position: relative;
55
+ display: flex;
56
+ justify-content: center;
57
+ align-items: center;
53
58
  }
54
59
 
55
- // Input
56
- .#{$field-input} {
60
+ // Input shell wrapping contenteditable (flex layout); must not apply display:flex to <input>.
61
+ div.#{$field-input} {
57
62
  padding: calc(#{$spacing-y} * #{$size-coefficient-y})
58
63
  calc(#{$spacing-x} * #{$size-coefficient-x});
59
64
  position: relative;
@@ -61,14 +66,11 @@ $transition-duration: sass.fn-naming-var("motion", "duration");
61
66
  z-index: 2;
62
67
  min-height: calc(#{$field-line} * #{$size-height} - 2 * #{$border-width});
63
68
  max-height: calc(#{$field-max-line} * #{$size-height} - 2 * #{$border-width});
64
- color: rgb(#{$app-color});
65
- caret-color: rgb(#{$app-color});
66
- border: none;
67
- user-select: all;
68
- font-size: #{$size-font-size};
69
- line-height: #{$size-line-height};
70
69
  box-sizing: border-box;
71
70
  width: 100%;
71
+ flex: 1;
72
+ display: flex;
73
+ align-items: center;
72
74
  transition:
73
75
  box-shadow #{$transition-duration} ease,
74
76
  border-color #{$transition-duration} ease,
@@ -76,13 +78,44 @@ $transition-duration: sass.fn-naming-var("motion", "duration");
76
78
  color #{$transition-duration} ease,
77
79
  border-radius #{$transition-duration} ease;
78
80
 
79
- &:focus,
81
+ &:focus-within,
80
82
  &:active,
81
83
  &:hover {
82
84
  outline: none;
83
- border: none;
84
85
  }
85
86
 
87
+ &::placeholder {
88
+ color: rgba(#{$app-color}, 0.5);
89
+ }
90
+ }
91
+
92
+ // Native password <input> (single node, not wrapped).
93
+ input.#{$field-input} {
94
+ padding: calc(#{$spacing-y} * #{$size-coefficient-y})
95
+ calc(#{$spacing-x} * #{$size-coefficient-x});
96
+ position: relative;
97
+ background-color: transparent;
98
+ z-index: 2;
99
+ min-height: calc(#{$field-line} * #{$size-height} - 2 * #{$border-width});
100
+ max-height: calc(#{$field-max-line} * #{$size-height} - 2 * #{$border-width});
101
+ box-sizing: border-box;
102
+ width: 100%;
103
+ flex: 1;
104
+ color: rgb(#{$app-color});
105
+ caret-color: rgb(#{$app-color});
106
+ border: none;
107
+ -webkit-user-select: text;
108
+ user-select: text;
109
+ font-size: #{$size-font-size};
110
+ line-height: #{$size-line-height};
111
+ overflow: auto;
112
+ transition:
113
+ box-shadow #{$transition-duration} ease,
114
+ border-color #{$transition-duration} ease,
115
+ background-color #{$transition-duration} ease,
116
+ color #{$transition-duration} ease,
117
+ border-radius #{$transition-duration} ease;
118
+
86
119
  // Firefox
87
120
  -moz-appearance: textfield;
88
121
  appearance: textfield;
@@ -94,11 +127,46 @@ $transition-duration: sass.fn-naming-var("motion", "duration");
94
127
  margin: 0;
95
128
  }
96
129
 
130
+ &:focus,
131
+ &:active,
132
+ &:hover {
133
+ outline: none;
134
+ border: none;
135
+ }
136
+
97
137
  &::placeholder {
98
138
  color: rgba(#{$app-color}, 0.5);
99
139
  }
100
140
  }
101
141
 
142
+ .#{$field-input-editable} {
143
+ flex: 1 1 auto;
144
+ min-width: 0;
145
+ min-height: 0;
146
+ display: block;
147
+ position: relative;
148
+ z-index: 2;
149
+ background-color: transparent;
150
+ color: rgb(#{$app-color});
151
+ caret-color: rgb(#{$app-color});
152
+ border: none;
153
+ // user-select:all breaks caret placement on many mobile browsers for contenteditable.
154
+ -webkit-user-select: text;
155
+ user-select: text;
156
+ font-size: #{$size-font-size};
157
+ line-height: #{$size-line-height};
158
+ box-sizing: border-box;
159
+ width: 100%;
160
+ overflow: auto;
161
+ outline: none;
162
+
163
+ &:focus,
164
+ &:active {
165
+ outline: none;
166
+ border: none;
167
+ }
168
+ }
169
+
102
170
  // Message
103
171
  .#{$field-message} {
104
172
  color: rgb(#{$field-background-color});
@@ -125,33 +193,34 @@ $transition-duration: sass.fn-naming-var("motion", "duration");
125
193
  border-radius #{$transition-duration} ease;
126
194
  }
127
195
 
128
- // State
129
- &.active {
130
- .#{$field-content} {
131
- border-color: rgb(#{$field-border-color});
132
- box-shadow: 0 0 0 calc(#{$border-width} * 1.5) rgb(#{$field-border-color});
133
- }
134
- }
135
-
136
196
  &.focus {
137
197
  &.shadow {
138
198
  .#{$field-content} {
139
199
  box-shadow: 0 0 0 0.25rem rgb(#{$field-border-color}, 0.25);
140
200
  }
141
201
  }
202
+
203
+ &:not(.shadow) {
204
+ .#{$field-content} {
205
+ box-shadow: 0 0 0 calc(#{$border-width} * 1.5) rgb(#{$field-border-color});
206
+ }
207
+ }
142
208
  }
143
209
 
144
210
  &.disabled {
145
211
  opacity: 0.5;
146
212
  cursor: not-allowed;
147
- .#{$field-input} {
213
+ div.#{$field-input},
214
+ input.#{$field-input},
215
+ .#{$field-input-editable} {
148
216
  cursor: not-allowed;
217
+ -webkit-user-select: none;
149
218
  user-select: none;
150
219
  }
151
220
  }
152
221
 
153
222
  &.empty:not(.typing) {
154
- .#{$field-input} {
223
+ div.#{$field-input} {
155
224
  &::before {
156
225
  content: attr(placeholder);
157
226
  color: rgba(#{$app-color}, 0.5);
@@ -165,7 +234,6 @@ $transition-duration: sass.fn-naming-var("motion", "duration");
165
234
  display: block;
166
235
  white-space: pre-wrap;
167
236
  word-break: break-word;
168
- padding: inherit;
169
237
  line-height: inherit;
170
238
  font-size: inherit;
171
239
  display: flex;
@@ -176,51 +244,36 @@ $transition-duration: sass.fn-naming-var("motion", "duration");
176
244
 
177
245
  // Type
178
246
  &.paragraph {
179
- .#{$field-input} {
247
+ div.#{$field-input} {
248
+ align-items: flex-start;
249
+ }
250
+
251
+ .#{$field-input-editable} {
180
252
  white-space: pre-wrap;
181
253
  word-break: break-word;
182
- display: flex;
183
- align-items: start;
184
- overflow: auto;
185
254
  }
186
255
  }
187
256
 
188
257
  &.text {
189
- .#{$field-input} {
258
+ .#{$field-input-editable},
259
+ input.#{$field-input} {
190
260
  white-space: nowrap;
191
- display: flex;
192
- align-items: center;
193
261
  }
194
262
  }
195
263
 
196
264
  &.password {
197
- .#{$field-input} {
265
+ input.#{$field-input} {
198
266
  white-space: nowrap;
199
- display: flex;
200
- align-items: center;
201
267
  }
202
268
  }
203
269
 
204
270
  // Direction
205
- &.#{$field-direction-left} {
206
- .#{$field-input} {
207
- justify-content: start;
208
- text-align: left;
209
- }
210
-
211
- .#{$field-input}::before {
212
- left: 0;
213
- }
271
+ div.#{$field-input} {
272
+ justify-content: #{$direction-justify-content};
214
273
  }
215
274
 
216
- &.#{$field-direction-right} {
217
- .#{$field-input} {
218
- justify-content: end;
219
- text-align: right;
220
- }
221
-
222
- .#{$field-input}::before {
223
- right: 0;
224
- }
275
+ .#{$field-input-editable},
276
+ input.#{$field-input} {
277
+ text-align: #{$direction-text-align};
225
278
  }
226
279
  }
@@ -1,7 +1,7 @@
1
1
  <style lang="scss" src="./outline.scss" scoped></style>
2
2
  <template src="./outline.html"></template>
3
3
  <script lang="ts" setup>
4
- import { computed, nextTick, ref, watch } from "vue";
4
+ import { computed, nextTick, onMounted, ref, watch } from "vue";
5
5
  import type { FieldProps, FieldEmit } from "../field.type";
6
6
  import { property, withPrefix } from "../../../utils";
7
7
 
@@ -48,7 +48,6 @@ const fieldAttrs = computed(() => {
48
48
  props.type,
49
49
  {
50
50
  disabled: props.disabled,
51
- active: isFocus.value || props.message,
52
51
  focus: isFocus.value,
53
52
  shadow: props.shadow,
54
53
  empty: !content.value,
@@ -67,16 +66,32 @@ const fieldContentAttrs = computed(() => ({
67
66
  class: [withPrefix("field-content")],
68
67
  }));
69
68
 
70
- const fieldInputAttrs = computed(() => ({
69
+ /** Outer shell: layout, placeholder pseudo-element (must stay off the contenteditable node on iOS). */
70
+ const fieldInputShellAttrs = computed(() => ({
71
+ class: [withPrefix("field-input")],
72
+ placeholder: props.placeholder,
73
+ }));
74
+
75
+ /** contenteditable as the literal string "true"|"false" improves mobile WebKit behavior vs boolean. */
76
+ const fieldEditableAttrs = computed(() => ({
77
+ class: [withPrefix("field-input-editable")],
78
+ name: props.name,
79
+ id: props.id,
80
+ contenteditable:
81
+ props.type === "password" ? undefined : props.disabled || props.readonly ? "false" : "true",
82
+ autocomplete: props.autocomplete,
83
+ tabindex: props.readonly ? 0 : props.disabled ? -1 : props.tabindex,
84
+ }));
85
+
86
+ const fieldPasswordInputAttrs = computed(() => ({
71
87
  class: [withPrefix("field-input")],
72
88
  name: props.name,
73
89
  id: props.id,
74
90
  placeholder: props.placeholder,
75
- contenteditable: props.type !== "password" ? !props.disabled && !props.readonly : undefined,
76
91
  autocomplete: props.autocomplete,
77
- tabindex: props.disabled ? -1 : props.tabindex,
78
- readonly: props.type === "password" ? props.readonly : undefined,
79
- disabled: props.type === "password" ? props.disabled : undefined,
92
+ tabindex: props.readonly ? 0 : props.disabled ? -1 : props.tabindex,
93
+ readonly: props.readonly,
94
+ disabled: props.disabled,
80
95
  }));
81
96
 
82
97
  const fieldMessageAttrs = computed(() => ({
@@ -206,9 +221,42 @@ const onCompositionEnd = (ev: CompositionEvent) => {
206
221
  });
207
222
  };
208
223
 
224
+ const normalizeText = (s: string) => s.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
225
+
226
+ const ensureEditableCanReceiveCaret = (el: HTMLElement) => {
227
+ // iOS Safari often refuses to show caret / accept input in an empty contenteditable.
228
+ if (!el.textContent?.length && el.childNodes.length === 0) {
229
+ el.appendChild(document.createElement("br"));
230
+ }
231
+ };
232
+
233
+ const syncContentFromModel = async () => {
234
+ const el = contentRef.value;
235
+ if (!el || props.type === "password") return;
236
+ if (isComposing.value) return;
237
+
238
+ const next = normalizeText(content.value ?? "");
239
+ const current = normalizeText(el.innerText);
240
+ if (current === next) return;
241
+
242
+ if (isFocus.value) {
243
+ const saved = caret.value;
244
+ el.innerText = next;
245
+ await nextTick();
246
+ el.focus();
247
+ setCaretOffset(el, Math.min(saved, next.length));
248
+ } else {
249
+ el.innerText = next;
250
+ }
251
+ };
252
+
209
253
  const onFocus = (ev: FocusEvent) => {
210
254
  if (props.disabled) return;
211
255
  isFocus.value = true;
256
+ const el = contentRef.value;
257
+ if (el && props.type !== "password") {
258
+ ensureEditableCanReceiveCaret(el);
259
+ }
212
260
  emit("focus", ev);
213
261
  };
214
262
 
@@ -230,14 +278,9 @@ const onBeforeinput = (ev: Event) => {
230
278
  // ----------------------------------------------------------------------------
231
279
  watch(
232
280
  () => content.value,
233
- async (val) => {
234
- if (!isFocus.value || isComposing.value) return;
235
- const el = contentRef.value;
236
- if (!el) return;
237
- await nextTick();
238
- el.focus();
239
- setCaretOffset(el, caret.value);
240
- },
241
- { immediate: true }
281
+ () => void syncContentFromModel(),
282
+ { flush: "post" }
242
283
  );
284
+
285
+ onMounted(() => void syncContentFromModel());
243
286
  </script>
@@ -1,4 +1,4 @@
1
- @use "@toife/sass-layer-generator" as sass;
1
+ @use "@toife/sass-layer" as sass;
2
2
 
3
3
  // Classes
4
4
  $form-group: sass.fn-naming-prefix("form-group");
@@ -1,4 +1,4 @@
1
- @use "@toife/sass-layer-generator" as sass;
1
+ @use "@toife/sass-layer" as sass;
2
2
 
3
3
  // Class name
4
4
  $gesture-indicator: sass.fn-naming-prefix("gesture-indicator");
@@ -2,6 +2,7 @@ export * from "./app";
2
2
  export * from "./present";
3
3
  export * from "./modal";
4
4
  export * from "./gesture-indicator";
5
+ export * from "./layout";
5
6
  export * from "./button";
6
7
  export * from "./cable";
7
8
  export * from "./toolbar";
@@ -25,3 +26,5 @@ export * from "./route";
25
26
  export * from "./page";
26
27
  export * from "./collapse";
27
28
  export * from "./form-group";
29
+ export * from "./dropdown";
30
+ export * from "./select";
@@ -0,0 +1 @@
1
+ <div v-bind="cellAttrs"><slot /></div>
@@ -0,0 +1,47 @@
1
+ # `<t-grid-item>`
2
+
3
+ > Default prefix is `t-` — change via `createToife({ prefix: "..." })`.
4
+
5
+ ## Description
6
+
7
+ Cell inside `<t-grid>`. Placement uses `grid-column` / `grid-row` via props or raw CSS strings.
8
+
9
+ ## Requirements / dependencies
10
+
11
+ | Item | Notes |
12
+ | ------ | ---------------------------------------------- |
13
+ | Vue | ^3.5 |
14
+ | Parent | `<t-grid>` (or any `display: grid` container). |
15
+
16
+ ## Basic usage
17
+
18
+ ```vue
19
+ <t-grid :columns="3">
20
+ <t-grid-item :column-span="2">Wide</t-grid-item>
21
+ <t-grid-item>Narrow</t-grid-item>
22
+ </t-grid>
23
+ ```
24
+
25
+ ## Props
26
+
27
+ | Prop | Type | Description |
28
+ | ------------- | -------- | --------------------------------------------- |
29
+ | `column` | `string` | Raw `grid-column` (overrides span/start/end). |
30
+ | `row` | `string` | Raw `grid-row`. |
31
+ | `columnSpan` | `number` | `span n` on the column axis. |
32
+ | `rowSpan` | `number` | `span n` on the row axis. |
33
+ | `columnStart` | `number` | Grid line start (column). |
34
+ | `columnEnd` | `number` | Grid line end (column). |
35
+ | `rowStart` | `number` | Grid line start (row). |
36
+ | `rowEnd` | `number` | Grid line end (row). |
37
+
38
+ ## Slots
39
+
40
+ | Slot | Description |
41
+ | --------- | ------------- |
42
+ | `default` | Cell content. |
43
+
44
+ ## See also
45
+
46
+ - Source: `src/components/grid/grid-item`
47
+ - `<t-grid>`
@@ -0,0 +1,54 @@
1
+ @use "@toife/sass-layer" as sass;
2
+ @use "sass:map";
3
+ @use "sass:list";
4
+
5
+ // Class name
6
+ $cell: sass.fn-naming-prefix("cell");
7
+
8
+ // Base tokens (breakpoint "")
9
+ $cell-row: sass.fn-naming-var("cell", "row");
10
+ $cell-column: sass.fn-naming-var("cell", "column");
11
+ $cell-justify: sass.fn-naming-var("cell", "justify");
12
+ $cell-align: sass.fn-naming-var("cell", "align");
13
+ $cell-grow: sass.fn-naming-var("cell", "grow");
14
+ $cell-shrink: sass.fn-naming-var("cell", "shrink");
15
+ $cell-basis: sass.fn-naming-var("cell", "basis");
16
+ $cell-order: sass.fn-naming-var("cell", "order");
17
+
18
+ .#{$cell} {
19
+ display: grid;
20
+ box-sizing: border-box;
21
+ min-width: 0;
22
+ grid-column: #{$cell-column};
23
+ grid-row: #{$cell-row};
24
+ justify-self: #{$cell-justify};
25
+ align-self: #{$cell-align};
26
+ flex-grow: #{$cell-grow};
27
+ flex-shrink: #{$cell-shrink};
28
+ flex-basis: #{$cell-basis};
29
+ order: #{$cell-order};
30
+
31
+ $bp-names: map.keys(sass.$breakpoints);
32
+ $bp-count: list.length($bp-names);
33
+
34
+ @for $i from 1 through $bp-count {
35
+ $bp-name: list.nth($bp-names, $i);
36
+ $bp-min: map.get(sass.$breakpoints, $bp-name);
37
+
38
+ @media (min-width: $bp-min) {
39
+ grid-column: #{sass.fn-naming-cascade-dvar(("cell", "column"), $bp-names, $i, $cell-column)};
40
+ grid-row: #{sass.fn-naming-cascade-dvar(("cell", "row"), $bp-names, $i, $cell-row)};
41
+ justify-self: #{sass.fn-naming-cascade-dvar(
42
+ ("cell", "justify"),
43
+ $bp-names,
44
+ $i,
45
+ $cell-justify
46
+ )};
47
+ align-self: #{sass.fn-naming-cascade-dvar(("cell", "align"), $bp-names, $i, $cell-align)};
48
+ flex-grow: #{sass.fn-naming-cascade-dvar(("cell", "grow"), $bp-names, $i, $cell-grow)};
49
+ flex-shrink: #{sass.fn-naming-cascade-dvar(("cell", "shrink"), $bp-names, $i, $cell-shrink)};
50
+ flex-basis: #{sass.fn-naming-cascade-dvar(("cell", "basis"), $bp-names, $i, $cell-basis)};
51
+ order: #{sass.fn-naming-cascade-dvar(("cell", "order"), $bp-names, $i, $cell-order)};
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,19 @@
1
+ export type CellJustify = "start" | "end" | "center" | "stretch";
2
+ export type CellAlign = "start" | "end" | "center" | "stretch";
3
+
4
+ export type CellOption = {
5
+ breakpoint?: string;
6
+ row?: number;
7
+ column?: number;
8
+ justify?: CellJustify;
9
+ align?: CellAlign;
10
+ grow?: number;
11
+ shrink?: number;
12
+ basis?: string;
13
+ order?: number;
14
+ };
15
+
16
+ // Type definitions
17
+ export type CellProps = {
18
+ options?: CellOption[];
19
+ };
@@ -0,0 +1,35 @@
1
+ <style lang="scss" src="./cell.scss" scoped></style>
2
+ <template src="./cell.html"></template>
3
+ <script lang="ts" setup>
4
+ import { computed } from "vue";
5
+ import { withPrefix, property } from "../../../utils";
6
+ import type { CellProps } from "./cell.type";
7
+
8
+ // Component setup (props, emits, injects)
9
+ // ----------------------------------------------------------------------------
10
+ const props = withDefaults(defineProps<CellProps>(), {
11
+ options: () => [],
12
+ });
13
+
14
+ // Computed properties
15
+ // ----------------------------------------------------------------------------
16
+ const cellAttrs = computed(() => {
17
+ const style = props.options.map((option) => {
18
+ return {
19
+ [property(["cell", "row", option?.breakpoint || ""])]: option.row,
20
+ [property(["cell", "column", option?.breakpoint || ""])]: option.column,
21
+ [property(["cell", "justify", option?.breakpoint || ""])]: option.justify,
22
+ [property(["cell", "align", option?.breakpoint || ""])]: option.align,
23
+ [property(["cell", "flex-grow", option?.breakpoint || ""])]: option.grow,
24
+ [property(["cell", "flex-shrink", option?.breakpoint || ""])]: option.shrink,
25
+ [property(["cell", "flex-basis", option?.breakpoint || ""])]: option.basis,
26
+ [property(["cell", "order", option?.breakpoint || ""])]: option.order,
27
+ };
28
+ });
29
+
30
+ return {
31
+ class: [withPrefix("cell")],
32
+ style,
33
+ };
34
+ });
35
+ </script>
@@ -0,0 +1,2 @@
1
+ export { default as Cell } from "./cell.vue";
2
+ export * from "./cell.type";
@@ -0,0 +1 @@
1
+ <div v-bind="flexAttrs"><slot /></div>
@@ -0,0 +1,59 @@
1
+ @use "@toife/sass-layer" as sass;
2
+ @use "sass:map";
3
+ @use "sass:list";
4
+
5
+ /// Class name
6
+ $flex: sass.fn-naming-prefix("flex");
7
+
8
+ /// Base tokens (breakpoint "")
9
+ $flex-gap: sass.fn-naming-var("flex", "gap");
10
+ $flex-direction: sass.fn-naming-var("flex", "direction");
11
+ $flex-wrap: sass.fn-naming-var("flex", "wrap");
12
+ $flex-justify: sass.fn-naming-var("flex", "justify");
13
+ $flex-align: sass.fn-naming-var("flex", "align");
14
+ $flex-align-content: sass.fn-naming-var("flex", "align-content");
15
+
16
+ .#{$flex} {
17
+ display: flex;
18
+ box-sizing: border-box;
19
+ width: 100%;
20
+ min-height: 0;
21
+ gap: #{$flex-gap};
22
+ flex-direction: #{$flex-direction};
23
+ flex-wrap: #{$flex-wrap};
24
+ justify-content: #{$flex-justify};
25
+ align-items: #{$flex-align};
26
+ align-content: #{$flex-align-content};
27
+
28
+ $bp-names: map.keys(sass.$breakpoints);
29
+ $bp-count: list.length($bp-names);
30
+
31
+ @for $i from 1 through $bp-count {
32
+ $bp-name: list.nth($bp-names, $i);
33
+ $bp-min: map.get(sass.$breakpoints, $bp-name);
34
+
35
+ @media (min-width: $bp-min) {
36
+ gap: #{sass.fn-naming-cascade-dvar(("flex", "gap"), $bp-names, $i, $flex-gap)};
37
+ flex-direction: #{sass.fn-naming-cascade-dvar(
38
+ ("flex", "direction"),
39
+ $bp-names,
40
+ $i,
41
+ $flex-direction
42
+ )};
43
+ flex-wrap: #{sass.fn-naming-cascade-dvar(("flex", "wrap"), $bp-names, $i, $flex-wrap)};
44
+ justify-content: #{sass.fn-naming-cascade-dvar(
45
+ ("flex", "justify"),
46
+ $bp-names,
47
+ $i,
48
+ $flex-justify
49
+ )};
50
+ align-items: #{sass.fn-naming-cascade-dvar(("flex", "align"), $bp-names, $i, $flex-align)};
51
+ align-content: #{sass.fn-naming-cascade-dvar(
52
+ ("flex", "align-content"),
53
+ $bp-names,
54
+ $i,
55
+ $flex-align-content
56
+ )};
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,13 @@
1
+ export type FlexOption = {
2
+ breakpoint?: string;
3
+ gap?: string | number;
4
+ direction?: string;
5
+ wrap?: string;
6
+ justify?: string;
7
+ align?: string;
8
+ alignContent?: string;
9
+ };
10
+
11
+ export type FlexProps = {
12
+ options?: FlexOption[];
13
+ };