@toife/vue 3.0.7 → 3.1.3

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 (79) hide show
  1. package/README.md +4 -2
  2. package/package.json +4 -7
  3. package/src/components/action/action.html +1 -1
  4. package/src/components/action/action.scss +1 -1
  5. package/src/components/app/app.scss +1 -1
  6. package/src/components/avatar/avatar.scss +1 -1
  7. package/src/components/button/button.scss +1 -1
  8. package/src/components/cable/cable.scss +1 -1
  9. package/src/components/card/card/card.scss +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/divider/divider.md +6 -6
  18. package/src/components/divider/divider.scss +1 -1
  19. package/src/components/divider/divider.type.ts +2 -2
  20. package/src/components/divider/divider.vue +2 -2
  21. package/src/components/divider/index.ts +1 -1
  22. package/src/components/dropdown/dropdown.scss +4 -10
  23. package/src/components/field/outline/outline.html +13 -13
  24. package/src/components/field/outline/outline.scss +97 -36
  25. package/src/components/field/outline/outline.vue +59 -16
  26. package/src/components/form-group/form-group.md +4 -4
  27. package/src/components/form-group/form-group.scss +1 -1
  28. package/src/components/form-group/form-group.type.ts +2 -2
  29. package/src/components/form-group/form-group.vue +2 -2
  30. package/src/components/gesture-indicator/gesture-indicator.scss +1 -1
  31. package/src/components/index.ts +1 -0
  32. package/src/components/layout/cell/cell.html +1 -0
  33. package/src/components/layout/cell/cell.md +47 -0
  34. package/src/components/layout/cell/cell.scss +54 -0
  35. package/src/components/layout/cell/cell.type.ts +19 -0
  36. package/src/components/layout/cell/cell.vue +35 -0
  37. package/src/components/layout/cell/index.ts +2 -0
  38. package/src/components/layout/flex/flex.html +1 -0
  39. package/src/components/layout/flex/flex.scss +59 -0
  40. package/src/components/layout/flex/flex.type.ts +15 -0
  41. package/src/components/layout/flex/flex.vue +34 -0
  42. package/src/components/layout/flex/index.ts +2 -0
  43. package/src/components/layout/grid/grid.html +1 -0
  44. package/src/components/layout/grid/grid.md +50 -0
  45. package/src/components/layout/grid/grid.scss +53 -0
  46. package/src/components/layout/grid/grid.type.ts +12 -0
  47. package/src/components/layout/grid/grid.vue +32 -0
  48. package/src/components/layout/grid/index.ts +2 -0
  49. package/src/components/layout/index.ts +3 -0
  50. package/src/components/modal/modal.scss +1 -1
  51. package/src/components/page/page.scss +1 -1
  52. package/src/components/present/present.scss +1 -1
  53. package/src/components/radio/radio/radio.scss +1 -1
  54. package/src/components/radio/radio-group/index.ts +1 -1
  55. package/src/components/radio/radio-group/radio-group.md +10 -10
  56. package/src/components/radio/radio-group/radio-group.scss +1 -1
  57. package/src/components/radio/radio-group/radio-group.type.ts +2 -2
  58. package/src/components/radio/radio-group/radio-group.vue +2 -2
  59. package/src/components/refresher/refresher.scss +1 -1
  60. package/src/components/route/route-navigator/route-navigator.scss +5 -5
  61. package/src/components/route/route-navigator/route-navigator.vue +26 -20
  62. package/src/components/segmented-field/segmented-field.scss +1 -1
  63. package/src/components/select/index.ts +1 -1
  64. package/src/components/select/select.html +9 -6
  65. package/src/components/select/select.scss +82 -11
  66. package/src/components/select/select.type.ts +14 -1
  67. package/src/components/select/select.vue +38 -8
  68. package/src/components/skeleton/skeleton.scss +1 -1
  69. package/src/components/switch/switch.scss +1 -1
  70. package/src/components/tabs/tabs/tabs.scss +6 -6
  71. package/src/components/toast/toast/toast.scss +1 -1
  72. package/src/components/toast/toast-content/toast-content.scss +1 -1
  73. package/src/components/toolbar/toolbar.html +1 -3
  74. package/src/components/toolbar/toolbar.md +5 -8
  75. package/src/components/toolbar/toolbar.scss +25 -40
  76. package/src/components/toolbar/toolbar.type.ts +1 -1
  77. package/src/components/toolbar/toolbar.vue +4 -4
  78. package/src/factory.ts +6 -0
  79. package/src/utils/style/index.ts +1 -1
@@ -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>
@@ -16,7 +16,7 @@ Groups controls in a horizontal or vertical flex layout; used by `Action` to sta
16
16
  ## Basic usage
17
17
 
18
18
  ```vue
19
- <t-form-group orientation="vertical">
19
+ <t-form-group direction="vertical">
20
20
  <t-button>A</t-button>
21
21
  <t-button>B</t-button>
22
22
  </t-form-group>
@@ -24,9 +24,9 @@ Groups controls in a horizontal or vertical flex layout; used by `Action` to sta
24
24
 
25
25
  ## Props
26
26
 
27
- | Prop | Type | Default | Description |
28
- | ------------- | ---------------------------- | -------------- | ------------ |
29
- | `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Layout axis. |
27
+ | Prop | Type | Default | Description |
28
+ | ----------- | ---------------------------- | -------------- | ------------ |
29
+ | `direction` | `"horizontal" \| "vertical"` | `"horizontal"` | Layout axis. |
30
30
 
31
31
  **Type source:** `src/components/form-group/form-group.type.ts`
32
32
 
@@ -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,5 +1,5 @@
1
- export type FormGroupOrientation = "horizontal" | "vertical";
1
+ export type FormGroupDirection = "horizontal" | "vertical";
2
2
 
3
3
  export type FormGroupProps = {
4
- orientation?: FormGroupOrientation;
4
+ direction?: FormGroupDirection;
5
5
  };
@@ -8,14 +8,14 @@ import { type FormGroupProps } from "./form-group.type";
8
8
  // Component setup (props, emits, injects)
9
9
  // ----------------------------------------------------------------------------
10
10
  const props = withDefaults(defineProps<FormGroupProps>(), {
11
- orientation: "horizontal",
11
+ direction: "horizontal",
12
12
  });
13
13
 
14
14
  // Computed properties
15
15
  // ----------------------------------------------------------------------------
16
16
  const formGroupAttrs = computed(() => {
17
17
  return {
18
- class: [withPrefix("form-group"), props.orientation],
18
+ class: [withPrefix("form-group"), props.direction],
19
19
  };
20
20
  });
21
21
  </script>
@@ -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";
@@ -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,15 @@
1
+ export type FlexDirection = "row" | "column" | "row-reverse" | "column-reverse";
2
+
3
+ export type FlexOption = {
4
+ breakpoint?: string;
5
+ gap?: string | number;
6
+ direction?: FlexDirection;
7
+ wrap?: string;
8
+ justify?: string;
9
+ align?: string;
10
+ alignContent?: string;
11
+ };
12
+
13
+ export type FlexProps = {
14
+ options?: FlexOption[];
15
+ };
@@ -0,0 +1,34 @@
1
+ <style lang="scss" src="./flex.scss" scoped></style>
2
+ <template src="./flex.html"></template>
3
+ <script lang="ts" setup>
4
+ import { computed } from "vue";
5
+ import { withPrefix, property } from "../../../utils";
6
+ import type { FlexProps } from "./flex.type";
7
+
8
+ // Component setup (props, emits, injects)
9
+ // ----------------------------------------------------------------------------
10
+ const props = withDefaults(defineProps<FlexProps>(), {
11
+ options: () => [],
12
+ });
13
+
14
+ // Computed properties
15
+ // ----------------------------------------------------------------------------
16
+ const flexAttrs = computed(() => {
17
+ const style = props.options.map((option) => {
18
+ return {
19
+ [property(["flex", "gap", option?.breakpoint || ""])]:
20
+ typeof option.gap === "number" ? `${option.gap}px` : option.gap,
21
+ [property(["flex", "direction", option?.breakpoint || ""])]: option.direction,
22
+ [property(["flex", "wrap", option?.breakpoint || ""])]: option.wrap,
23
+ [property(["flex", "justify", option?.breakpoint || ""])]: option.justify,
24
+ [property(["flex", "align", option?.breakpoint || ""])]: option.align,
25
+ [property(["flex", "align-content", option?.breakpoint || ""])]: option.alignContent,
26
+ };
27
+ });
28
+
29
+ return {
30
+ class: [withPrefix("flex")],
31
+ style,
32
+ };
33
+ });
34
+ </script>
@@ -0,0 +1,2 @@
1
+ export { default as Flex } from "./flex.vue";
2
+ export * from "./flex.type";
@@ -0,0 +1 @@
1
+ <div v-bind="gridAttrs"><slot /></div>
@@ -0,0 +1,50 @@
1
+ # `<t-grid>`
2
+
3
+ > Default prefix is `t-` — change via `createToife({ prefix: "..." })`.
4
+
5
+ ## Description
6
+
7
+ CSS Grid wrapper with theme-based default gap. Use with `<t-grid-item>` for placement.
8
+
9
+ ## Requirements / dependencies
10
+
11
+ | Item | Notes |
12
+ | ------------------- | ------------------------------- |
13
+ | Vue | ^3.5 |
14
+ | `@toife/sass-layer` | Theme tokens for default `gap`. |
15
+
16
+ ## Basic usage
17
+
18
+ ```vue
19
+ <t-grid :columns="3">
20
+ <t-grid-item>One</t-grid-item>
21
+ <t-grid-item>Two</t-grid-item>
22
+ <t-grid-item>Three</t-grid-item>
23
+ </t-grid>
24
+ ```
25
+
26
+ ## Props
27
+
28
+ | Prop | Type | Default | Description |
29
+ | ---------------- | ------------------ | ------- | --------------------------------------------------------------- |
30
+ | `columns` | `number \| string` | `1` | Column count (`repeat(n, 1fr)`) or raw `grid-template-columns`. |
31
+ | `rows` | `number \| string` | — | `grid-template-rows`. |
32
+ | `gap` | `string` | — | Sets `gap` (overrides SCSS default when non-empty). |
33
+ | `columnGap` | `string` | — | `column-gap`. |
34
+ | `rowGap` | `string` | — | `row-gap`. |
35
+ | `alignItems` | `string` | — | `align-items`. |
36
+ | `justifyItems` | `string` | — | `justify-items`. |
37
+ | `alignContent` | `string` | — | `align-content`. |
38
+ | `justifyContent` | `string` | — | `justify-content`. |
39
+ | `autoFlow` | `string` | — | `grid-auto-flow`. |
40
+
41
+ ## Slots
42
+
43
+ | Slot | Description |
44
+ | --------- | ----------- |
45
+ | `default` | Grid cells. |
46
+
47
+ ## See also
48
+
49
+ - Source: `src/components/grid/grid`
50
+ - `<t-grid-item>`
@@ -0,0 +1,53 @@
1
+ @use "@toife/sass-layer" as sass;
2
+ @use "sass:map";
3
+ @use "sass:list";
4
+
5
+ /// Class name
6
+ $grid: sass.fn-naming-prefix("grid");
7
+
8
+ /// Base tokens (breakpoint "")
9
+ $grid-gap: sass.fn-naming-var("grid", "gap");
10
+ $grid-columns: sass.fn-naming-var("grid", "columns");
11
+ $grid-rows: sass.fn-naming-var("grid", "rows");
12
+ $grid-auto-flow: sass.fn-naming-var("grid", "auto-flow");
13
+
14
+ .#{$grid} {
15
+ display: grid;
16
+ box-sizing: border-box;
17
+ width: 100%;
18
+ min-height: 0;
19
+ gap: #{$grid-gap};
20
+ grid-template-columns: #{$grid-columns};
21
+ grid-template-rows: #{$grid-rows};
22
+ grid-auto-flow: #{$grid-auto-flow};
23
+
24
+ $bp-names: map.keys(sass.$breakpoints);
25
+ $bp-count: list.length($bp-names);
26
+
27
+ @for $i from 1 through $bp-count {
28
+ $bp-name: list.nth($bp-names, $i);
29
+ $bp-min: map.get(sass.$breakpoints, $bp-name);
30
+
31
+ @media (min-width: $bp-min) {
32
+ gap: #{sass.fn-naming-cascade-dvar(("grid", "gap"), $bp-names, $i, $grid-gap)};
33
+ grid-template-columns: #{sass.fn-naming-cascade-dvar(
34
+ ("grid", "columns"),
35
+ $bp-names,
36
+ $i,
37
+ $grid-columns
38
+ )};
39
+ grid-template-rows: #{sass.fn-naming-cascade-dvar(
40
+ ("grid", "rows"),
41
+ $bp-names,
42
+ $i,
43
+ $grid-rows
44
+ )};
45
+ grid-auto-flow: #{sass.fn-naming-cascade-dvar(
46
+ ("grid", "auto-flow"),
47
+ $bp-names,
48
+ $i,
49
+ $grid-auto-flow
50
+ )};
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,12 @@
1
+ export type GridOption = {
2
+ breakpoint?: string;
3
+ gap?: string | number;
4
+ columns?: string;
5
+ rows?: string;
6
+ autoFlow?: string;
7
+ };
8
+
9
+ // Type definitions
10
+ export type GridProps = {
11
+ options?: GridOption[];
12
+ };
@@ -0,0 +1,32 @@
1
+ <style lang="scss" src="./grid.scss" scoped></style>
2
+ <template src="./grid.html"></template>
3
+ <script lang="ts" setup>
4
+ import { computed } from "vue";
5
+ import { withPrefix, property } from "../../../utils";
6
+ import type { GridProps } from "./grid.type";
7
+
8
+ // Component setup (props, emits, injects)
9
+ // ----------------------------------------------------------------------------
10
+ const props = withDefaults(defineProps<GridProps>(), {
11
+ options: () => [],
12
+ });
13
+
14
+ // Computed properties
15
+ // ----------------------------------------------------------------------------
16
+ const gridAttrs = computed(() => {
17
+ const style = props.options.map((option) => {
18
+ return {
19
+ [property(["grid", "gap", option?.breakpoint || ""])]:
20
+ typeof option.gap === "number" ? `${option.gap}px` : option.gap,
21
+ [property(["grid", "columns", option?.breakpoint || ""])]: option.columns,
22
+ [property(["grid", "rows", option?.breakpoint || ""])]: option.rows,
23
+ [property(["grid", "auto-flow", option?.breakpoint || ""])]: option.autoFlow,
24
+ };
25
+ });
26
+
27
+ return {
28
+ class: [withPrefix("grid")],
29
+ style,
30
+ };
31
+ });
32
+ </script>
@@ -0,0 +1,2 @@
1
+ export { default as Grid } from "./grid.vue";
2
+ export * from "./grid.type";
@@ -0,0 +1,3 @@
1
+ export * from "./grid";
2
+ export * from "./flex";
3
+ export * from "./cell";
@@ -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
  $modal: sass.fn-naming-prefix("modal");
@@ -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
  $page: sass.fn-naming-prefix("page");
@@ -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
  $present: sass.fn-naming-prefix("present");
@@ -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
  $radio: sass.fn-naming-prefix("radio");